1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "MidiExtractor"
19 #include <utils/Log.h>
20 
21 #include "MidiExtractor.h"
22 
23 #include <media/MidiIoWrapper.h>
24 #include <media/stagefright/foundation/ADebug.h>
25 #include <media/stagefright/MediaBufferGroup.h>
26 #include <media/stagefright/MediaDefs.h>
27 #include <media/stagefright/MetaData.h>
28 #include <media/MediaTrack.h>
29 #include <libsonivox/eas_reverb.h>
30 
31 namespace android {
32 
33 // how many Sonivox output buffers to aggregate into one MediaBufferBase
34 static const int NUM_COMBINE_BUFFERS = 4;
35 
36 class MidiSource : public MediaTrack {
37 
38 public:
39     MidiSource(
40             MidiEngine &engine,
41             MetaDataBase &trackMetadata);
42 
43     virtual status_t start(MetaDataBase *params);
44     virtual status_t stop();
45     virtual status_t getFormat(MetaDataBase&);
46 
47     virtual status_t read(
48             MediaBufferBase **buffer, const ReadOptions *options = NULL);
49 
50 protected:
51     virtual ~MidiSource();
52 
53 private:
54     MidiEngine &mEngine;
55     MetaDataBase &mTrackMetadata;
56     bool mInitCheck;
57     bool mStarted;
58 
59     status_t init();
60 
61     // no copy constructor or assignment
62     MidiSource(const MidiSource &);
63     MidiSource &operator=(const MidiSource &);
64 
65 };
66 
67 
68 // Midisource
69 
MidiSource(MidiEngine & engine,MetaDataBase & trackMetadata)70 MidiSource::MidiSource(
71         MidiEngine &engine,
72         MetaDataBase &trackMetadata)
73     : mEngine(engine),
74       mTrackMetadata(trackMetadata),
75       mInitCheck(false),
76       mStarted(false)
77 {
78     ALOGV("MidiSource ctor");
79     mInitCheck = init();
80 }
81 
~MidiSource()82 MidiSource::~MidiSource()
83 {
84     ALOGV("MidiSource dtor");
85     if (mStarted) {
86         stop();
87     }
88 }
89 
start(MetaDataBase *)90 status_t MidiSource::start(MetaDataBase * /* params */)
91 {
92     ALOGV("MidiSource::start");
93 
94     CHECK(!mStarted);
95     mStarted = true;
96     mEngine.allocateBuffers();
97     return OK;
98 }
99 
stop()100 status_t MidiSource::stop()
101 {
102     ALOGV("MidiSource::stop");
103 
104     CHECK(mStarted);
105     mStarted = false;
106     mEngine.releaseBuffers();
107 
108     return OK;
109 }
110 
getFormat(MetaDataBase & meta)111 status_t MidiSource::getFormat(MetaDataBase &meta)
112 {
113     meta = mTrackMetadata;
114     return OK;
115 }
116 
read(MediaBufferBase ** outBuffer,const ReadOptions * options)117 status_t MidiSource::read(
118         MediaBufferBase **outBuffer, const ReadOptions *options)
119 {
120     ALOGV("MidiSource::read");
121     MediaBufferBase *buffer;
122     // process an optional seek request
123     int64_t seekTimeUs;
124     ReadOptions::SeekMode mode;
125     if ((NULL != options) && options->getSeekTo(&seekTimeUs, &mode)) {
126         if (seekTimeUs <= 0LL) {
127             seekTimeUs = 0LL;
128         }
129         mEngine.seekTo(seekTimeUs);
130     }
131     buffer = mEngine.readBuffer();
132     *outBuffer = buffer;
133     ALOGV("MidiSource::read %p done", this);
134     return buffer != NULL ? (status_t) OK : (status_t) ERROR_END_OF_STREAM;
135 }
136 
init()137 status_t MidiSource::init()
138 {
139     ALOGV("MidiSource::init");
140     return OK;
141 }
142 
143 // MidiEngine
144 
MidiEngine(DataSourceBase * dataSource,MetaDataBase * fileMetadata,MetaDataBase * trackMetadata)145 MidiEngine::MidiEngine(DataSourceBase *dataSource,
146         MetaDataBase *fileMetadata,
147         MetaDataBase *trackMetadata) :
148             mGroup(NULL),
149             mEasData(NULL),
150             mEasHandle(NULL),
151             mEasConfig(NULL),
152             mIsInitialized(false) {
153     mIoWrapper = new MidiIoWrapper(dataSource);
154     // spin up a new EAS engine
155     EAS_I32 temp;
156     EAS_RESULT result = EAS_Init(&mEasData);
157 
158     if (result == EAS_SUCCESS) {
159         result = EAS_OpenFile(mEasData, mIoWrapper->getLocator(), &mEasHandle);
160     }
161     if (result == EAS_SUCCESS) {
162         result = EAS_Prepare(mEasData, mEasHandle);
163     }
164     if (result == EAS_SUCCESS) {
165         result = EAS_ParseMetaData(mEasData, mEasHandle, &temp);
166     }
167 
168     if (result != EAS_SUCCESS) {
169         return;
170     }
171 
172     if (fileMetadata != NULL) {
173         fileMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MIDI);
174     }
175 
176     if (trackMetadata != NULL) {
177         trackMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
178         trackMetadata->setInt64(kKeyDuration, 1000ll * temp); // milli->micro
179         mEasConfig = EAS_Config();
180         trackMetadata->setInt32(kKeySampleRate, mEasConfig->sampleRate);
181         trackMetadata->setInt32(kKeyChannelCount, mEasConfig->numChannels);
182         trackMetadata->setInt32(kKeyPcmEncoding, kAudioEncodingPcm16bit);
183     }
184     mIsInitialized = true;
185 }
186 
~MidiEngine()187 MidiEngine::~MidiEngine() {
188     if (mEasHandle) {
189         EAS_CloseFile(mEasData, mEasHandle);
190     }
191     if (mEasData) {
192         EAS_Shutdown(mEasData);
193     }
194     delete mGroup;
195     delete mIoWrapper;
196 }
197 
initCheck()198 status_t MidiEngine::initCheck() {
199     return mIsInitialized ? OK : UNKNOWN_ERROR;
200 }
201 
allocateBuffers()202 status_t MidiEngine::allocateBuffers() {
203     // select reverb preset and enable
204     EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER);
205     EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE);
206 
207     mGroup = new MediaBufferGroup;
208     int bufsize = sizeof(EAS_PCM)
209             * mEasConfig->mixBufferSize * mEasConfig->numChannels * NUM_COMBINE_BUFFERS;
210     ALOGV("using %d byte buffer", bufsize);
211     mGroup->add_buffer(MediaBufferBase::Create(bufsize));
212     return OK;
213 }
214 
releaseBuffers()215 status_t MidiEngine::releaseBuffers() {
216     delete mGroup;
217     mGroup = NULL;
218     return OK;
219 }
220 
seekTo(int64_t positionUs)221 status_t MidiEngine::seekTo(int64_t positionUs) {
222     ALOGV("seekTo %lld", (long long)positionUs);
223     EAS_RESULT result = EAS_Locate(mEasData, mEasHandle, positionUs / 1000, false);
224     return result == EAS_SUCCESS ? OK : UNKNOWN_ERROR;
225 }
226 
readBuffer()227 MediaBufferBase* MidiEngine::readBuffer() {
228     EAS_STATE state;
229     EAS_State(mEasData, mEasHandle, &state);
230     if ((state == EAS_STATE_STOPPED) || (state == EAS_STATE_ERROR)) {
231         return NULL;
232     }
233     MediaBufferBase *buffer;
234     status_t err = mGroup->acquire_buffer(&buffer);
235     if (err != OK) {
236         ALOGE("readBuffer: no buffer");
237         return NULL;
238     }
239     EAS_I32 timeMs;
240     EAS_GetLocation(mEasData, mEasHandle, &timeMs);
241     int64_t timeUs = 1000ll * timeMs;
242     buffer->meta_data().setInt64(kKeyTime, timeUs);
243 
244     EAS_PCM* p = (EAS_PCM*) buffer->data();
245     int numBytesOutput = 0;
246     for (int i = 0; i < NUM_COMBINE_BUFFERS; i++) {
247         EAS_I32 numRendered;
248         EAS_RESULT result = EAS_Render(mEasData, p, mEasConfig->mixBufferSize, &numRendered);
249         if (result != EAS_SUCCESS) {
250             ALOGE("EAS_Render returned %ld", result);
251             break;
252         }
253         p += numRendered * mEasConfig->numChannels;
254         numBytesOutput += numRendered * mEasConfig->numChannels * sizeof(EAS_PCM);
255     }
256     buffer->set_range(0, numBytesOutput);
257     ALOGV("readBuffer: returning %zd in buffer %p", buffer->range_length(), buffer);
258     return buffer;
259 }
260 
261 
262 // MidiExtractor
263 
MidiExtractor(DataSourceBase * dataSource)264 MidiExtractor::MidiExtractor(
265         DataSourceBase *dataSource)
266     : mDataSource(dataSource),
267       mInitCheck(false)
268 {
269     ALOGV("MidiExtractor ctor");
270     mEngine = new MidiEngine(mDataSource, &mFileMetadata, &mTrackMetadata);
271     mInitCheck = mEngine->initCheck();
272 }
273 
~MidiExtractor()274 MidiExtractor::~MidiExtractor()
275 {
276     ALOGV("MidiExtractor dtor");
277 }
278 
countTracks()279 size_t MidiExtractor::countTracks()
280 {
281     return mInitCheck == OK ? 1 : 0;
282 }
283 
getTrack(size_t index)284 MediaTrack *MidiExtractor::getTrack(size_t index)
285 {
286     if (mInitCheck != OK || index > 0) {
287         return NULL;
288     }
289     return new MidiSource(*mEngine, mTrackMetadata);
290 }
291 
getTrackMetaData(MetaDataBase & meta,size_t index,uint32_t)292 status_t MidiExtractor::getTrackMetaData(
293         MetaDataBase &meta,
294         size_t index, uint32_t /* flags */) {
295     ALOGV("MidiExtractor::getTrackMetaData");
296     if (mInitCheck != OK || index > 0) {
297         return UNKNOWN_ERROR;
298     }
299     meta = mTrackMetadata;
300     return OK;
301 }
302 
getMetaData(MetaDataBase & meta)303 status_t MidiExtractor::getMetaData(MetaDataBase &meta)
304 {
305     ALOGV("MidiExtractor::getMetaData");
306     meta = mFileMetadata;
307     return OK;
308 }
309 
310 // Sniffer
311 
SniffMidi(DataSourceBase * source,float * confidence)312 bool SniffMidi(DataSourceBase *source, float *confidence)
313 {
314     MidiEngine p(source, NULL, NULL);
315     if (p.initCheck() == OK) {
316         *confidence = 0.8;
317         ALOGV("SniffMidi: yes");
318         return true;
319     }
320     ALOGV("SniffMidi: no");
321     return false;
322 
323 }
324 
325 extern "C" {
326 // This is the only symbol that needs to be exported
327 __attribute__ ((visibility ("default")))
GETEXTRACTORDEF()328 MediaExtractor::ExtractorDef GETEXTRACTORDEF() {
329     return {
330         MediaExtractor::EXTRACTORDEF_VERSION,
331         UUID("ef6cca0a-f8a2-43e6-ba5f-dfcd7c9a7ef2"),
332         1,
333         "MIDI Extractor",
334         [](
335                 DataSourceBase *source,
336                 float *confidence,
337                 void **,
338                 MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc {
339             if (SniffMidi(source, confidence)) {
340                 return [](
341                         DataSourceBase *source,
342                         void *) -> MediaExtractor* {
343                     return new MidiExtractor(source);};
344             }
345             return NULL;
346         }
347     };
348 }
349 
350 } // extern "C"
351 
352 }  // namespace android
353