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/MediaErrors.h>
28 #include <libsonivox/eas_reverb.h>
29 #include <watchdog/Watchdog.h>
30 
31 namespace android {
32 
33 // how many Sonivox output buffers to aggregate into one MediaBuffer
34 static const int NUM_COMBINE_BUFFERS = 4;
35 
36 class MidiSource : public MediaTrackHelper {
37 
38 public:
39     MidiSource(
40             MidiEngine &engine,
41             AMediaFormat *trackMetadata);
42 
43     virtual media_status_t start();
44     virtual media_status_t stop();
45     virtual media_status_t getFormat(AMediaFormat *);
46 
47     virtual media_status_t read(
48             MediaBufferHelper **buffer, const ReadOptions *options = NULL);
49 
50 protected:
51     virtual ~MidiSource();
52 
53 private:
54     MidiEngine &mEngine;
55     AMediaFormat *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,AMediaFormat * trackMetadata)70 MidiSource::MidiSource(
71         MidiEngine &engine,
72         AMediaFormat *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()90 media_status_t MidiSource::start()
91 {
92     ALOGV("MidiSource::start");
93 
94     CHECK(!mStarted);
95     mStarted = true;
96     mEngine.allocateBuffers(mBufferGroup);
97     return AMEDIA_OK;
98 }
99 
stop()100 media_status_t MidiSource::stop()
101 {
102     ALOGV("MidiSource::stop");
103 
104     CHECK(mStarted);
105     mStarted = false;
106     mEngine.releaseBuffers();
107 
108     return AMEDIA_OK;
109 }
110 
getFormat(AMediaFormat * meta)111 media_status_t MidiSource::getFormat(AMediaFormat *meta)
112 {
113     return AMediaFormat_copy(meta, mTrackMetadata);
114 }
115 
read(MediaBufferHelper ** outBuffer,const ReadOptions * options)116 media_status_t MidiSource::read(
117         MediaBufferHelper **outBuffer, const ReadOptions *options)
118 {
119     ALOGV("MidiSource::read");
120 
121     MediaBufferHelper *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 ? AMEDIA_OK : AMEDIA_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 using namespace std::chrono_literals;
145 static constexpr auto kTimeout = 10s;
146 
MidiEngine(CDataSource * dataSource,AMediaFormat * fileMetadata,AMediaFormat * trackMetadata)147 MidiEngine::MidiEngine(CDataSource *dataSource,
148         AMediaFormat *fileMetadata,
149         AMediaFormat *trackMetadata) :
150             mEasData(NULL),
151             mEasHandle(NULL),
152             mEasConfig(NULL),
153             mIsInitialized(false) {
154     Watchdog watchdog(kTimeout);
155 
156     mIoWrapper = new MidiIoWrapper(dataSource);
157     // spin up a new EAS engine
158     EAS_I32 temp;
159     EAS_RESULT result = EAS_Init(&mEasData);
160 
161     if (result == EAS_SUCCESS) {
162         result = EAS_OpenFile(mEasData, mIoWrapper->getLocator(), &mEasHandle);
163     }
164     if (result == EAS_SUCCESS) {
165         result = EAS_Prepare(mEasData, mEasHandle);
166     }
167     if (result == EAS_SUCCESS) {
168         result = EAS_ParseMetaData(mEasData, mEasHandle, &temp);
169     }
170 
171     if (result != EAS_SUCCESS) {
172         return;
173     }
174 
175     if (fileMetadata != NULL) {
176         AMediaFormat_setString(fileMetadata, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_MIDI);
177     }
178 
179     if (trackMetadata != NULL) {
180         AMediaFormat_setString(trackMetadata, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_RAW);
181         AMediaFormat_setInt64(
182                 trackMetadata, AMEDIAFORMAT_KEY_DURATION, 1000ll * temp); // milli->micro
183         mEasConfig = EAS_Config();
184         AMediaFormat_setInt32(
185                 trackMetadata, AMEDIAFORMAT_KEY_SAMPLE_RATE, mEasConfig->sampleRate);
186         AMediaFormat_setInt32(
187                 trackMetadata, AMEDIAFORMAT_KEY_CHANNEL_COUNT, mEasConfig->numChannels);
188         AMediaFormat_setInt32(
189                 trackMetadata, AMEDIAFORMAT_KEY_PCM_ENCODING, kAudioEncodingPcm16bit);
190     }
191     mIsInitialized = true;
192 }
193 
~MidiEngine()194 MidiEngine::~MidiEngine() {
195     Watchdog watchdog(kTimeout);
196 
197     if (mEasHandle) {
198         EAS_CloseFile(mEasData, mEasHandle);
199     }
200     if (mEasData) {
201         EAS_Shutdown(mEasData);
202     }
203     delete mIoWrapper;
204 }
205 
initCheck()206 status_t MidiEngine::initCheck() {
207     return mIsInitialized ? OK : UNKNOWN_ERROR;
208 }
209 
allocateBuffers(MediaBufferGroupHelper * group)210 status_t MidiEngine::allocateBuffers(MediaBufferGroupHelper *group) {
211     // select reverb preset and enable
212     EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER);
213     EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE);
214 
215     int bufsize = sizeof(EAS_PCM)
216             * mEasConfig->mixBufferSize * mEasConfig->numChannels * NUM_COMBINE_BUFFERS;
217     ALOGV("using %d byte buffer", bufsize);
218     mGroup = group;
219     mGroup->add_buffer(bufsize);
220     return OK;
221 }
222 
releaseBuffers()223 status_t MidiEngine::releaseBuffers() {
224     return OK;
225 }
226 
seekTo(int64_t positionUs)227 status_t MidiEngine::seekTo(int64_t positionUs) {
228     Watchdog watchdog(kTimeout);
229 
230     ALOGV("seekTo %lld", (long long)positionUs);
231     EAS_RESULT result = EAS_Locate(mEasData, mEasHandle, positionUs / 1000, false);
232     return result == EAS_SUCCESS ? OK : UNKNOWN_ERROR;
233 }
234 
readBuffer()235 MediaBufferHelper* MidiEngine::readBuffer() {
236     Watchdog watchdog(kTimeout);
237 
238     EAS_STATE state;
239     EAS_State(mEasData, mEasHandle, &state);
240     if ((state == EAS_STATE_STOPPED) || (state == EAS_STATE_ERROR)) {
241         return NULL;
242     }
243     MediaBufferHelper *buffer;
244     status_t err = mGroup->acquire_buffer(&buffer);
245     if (err != OK) {
246         ALOGE("readBuffer: no buffer");
247         return NULL;
248     }
249     EAS_I32 timeMs;
250     EAS_GetLocation(mEasData, mEasHandle, &timeMs);
251     int64_t timeUs = 1000ll * timeMs;
252     AMediaFormat *meta = buffer->meta_data();
253     AMediaFormat_setInt64(meta, AMEDIAFORMAT_KEY_TIME_US, timeUs);
254     AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, 1);
255 
256     EAS_PCM* p = (EAS_PCM*) buffer->data();
257     int numBytesOutput = 0;
258     for (int i = 0; i < NUM_COMBINE_BUFFERS; i++) {
259         EAS_I32 numRendered;
260         EAS_RESULT result = EAS_Render(mEasData, p, mEasConfig->mixBufferSize, &numRendered);
261         if (result != EAS_SUCCESS) {
262             ALOGE("EAS_Render() returned %ld, numBytesOutput = %d", result, numBytesOutput);
263             buffer->release();
264             return NULL; // Stop processing to prevent infinite loops.
265         }
266         p += numRendered * mEasConfig->numChannels;
267         numBytesOutput += numRendered * mEasConfig->numChannels * sizeof(EAS_PCM);
268     }
269     buffer->set_range(0, numBytesOutput);
270     ALOGV("readBuffer: returning %zd in buffer %p", buffer->range_length(), buffer);
271     return buffer;
272 }
273 
274 
275 // MidiExtractor
276 
MidiExtractor(CDataSource * dataSource)277 MidiExtractor::MidiExtractor(
278         CDataSource *dataSource)
279     : mDataSource(dataSource),
280       mInitCheck(false)
281 {
282     ALOGV("MidiExtractor ctor");
283     mFileMetadata = AMediaFormat_new();
284     mTrackMetadata = AMediaFormat_new();
285     mEngine = new MidiEngine(mDataSource, mFileMetadata, mTrackMetadata);
286     mInitCheck = mEngine->initCheck();
287 }
288 
~MidiExtractor()289 MidiExtractor::~MidiExtractor()
290 {
291     ALOGV("MidiExtractor dtor");
292     AMediaFormat_delete(mFileMetadata);
293     AMediaFormat_delete(mTrackMetadata);
294     delete mEngine;
295 }
296 
countTracks()297 size_t MidiExtractor::countTracks()
298 {
299     return mInitCheck == OK ? 1 : 0;
300 }
301 
getTrack(size_t index)302 MediaTrackHelper *MidiExtractor::getTrack(size_t index)
303 {
304     if (mInitCheck != OK || index > 0) {
305         return NULL;
306     }
307     return new MidiSource(*mEngine, mTrackMetadata);
308 }
309 
getTrackMetaData(AMediaFormat * meta,size_t index,uint32_t)310 media_status_t MidiExtractor::getTrackMetaData(
311         AMediaFormat *meta,
312         size_t index, uint32_t /* flags */) {
313     ALOGV("MidiExtractor::getTrackMetaData");
314     if (mInitCheck != OK || index > 0) {
315         return AMEDIA_ERROR_UNKNOWN;
316     }
317     return AMediaFormat_copy(meta, mTrackMetadata);
318 }
319 
getMetaData(AMediaFormat * meta)320 media_status_t MidiExtractor::getMetaData(AMediaFormat *meta)
321 {
322     ALOGV("MidiExtractor::getMetaData");
323     return AMediaFormat_copy(meta, mFileMetadata);
324 }
325 
326 // Sniffer
327 
SniffMidi(CDataSource * source,float * confidence)328 bool SniffMidi(CDataSource *source, float *confidence)
329 {
330     MidiEngine p(source, NULL, NULL);
331     if (p.initCheck() == OK) {
332         *confidence = 0.8;
333         ALOGV("SniffMidi: yes");
334         return true;
335     }
336     ALOGV("SniffMidi: no");
337     return false;
338 
339 }
340 
341 static const char *extensions[] = {
342     "imy",
343     "mid",
344     "midi",
345     "mxmf",
346     "ota",
347     "rtttl",
348     "rtx",
349     "smf",
350     "xmf",
351     NULL
352 };
353 
354 extern "C" {
355 // This is the only symbol that needs to be exported
356 __attribute__ ((visibility ("default")))
GETEXTRACTORDEF()357 ExtractorDef GETEXTRACTORDEF() {
358     return {
359         EXTRACTORDEF_VERSION,
360         UUID("ef6cca0a-f8a2-43e6-ba5f-dfcd7c9a7ef2"),
361         1,
362         "MIDI Extractor",
363         {
364             .v3 = {
365                 [](
366                 CDataSource *source,
367                 float *confidence,
368                 void **,
369                 FreeMetaFunc *) -> CreatorFunc {
370                     if (SniffMidi(source, confidence)) {
371                         return [](
372                                 CDataSource *source,
373                                 void *) -> CMediaExtractor* {
374                             return wrap(new MidiExtractor(source));};
375                     }
376                     return NULL;
377                 },
378                 extensions
379             }
380         },
381     };
382 }
383 
384 } // extern "C"
385 
386 }  // namespace android
387