xref: /aosp_15_r20/frameworks/av/media/module/extractors/midi/MidiExtractor.cpp (revision ec779b8e0859a360c3d303172224686826e6e0e1)
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 <com_android_media_extractor_flags.h>
24 #include <media/MidiIoWrapper.h>
25 #include <media/stagefright/foundation/ADebug.h>
26 #include <media/stagefright/MediaBufferGroup.h>
27 #include <media/stagefright/MediaDefs.h>
28 #include <media/stagefright/MediaErrors.h>
29 #include <libsonivox/eas_reverb.h>
30 #include <watchdog/Watchdog.h>
31 
32 namespace android {
33 
34 // how many Sonivox output buffers to aggregate into one MediaBuffer
35 static const int NUM_COMBINE_BUFFERS = 4;
36 
37 class MidiSource : public MediaTrackHelper {
38 
39 public:
40     MidiSource(
41             MidiEngine &engine,
42             AMediaFormat *trackMetadata);
43 
44     virtual media_status_t start();
45     virtual media_status_t stop();
46     virtual media_status_t getFormat(AMediaFormat *);
47 
48     virtual media_status_t read(
49             MediaBufferHelper **buffer, const ReadOptions *options = NULL);
50 
51 protected:
52     virtual ~MidiSource();
53 
54 private:
55     MidiEngine &mEngine;
56     AMediaFormat *mTrackMetadata;
57     bool mInitCheck;
58     bool mStarted;
59 
60     status_t init();
61 
62     // no copy constructor or assignment
63     MidiSource(const MidiSource &);
64     MidiSource &operator=(const MidiSource &);
65 
66 };
67 
68 
69 // Midisource
70 
MidiSource(MidiEngine & engine,AMediaFormat * trackMetadata)71 MidiSource::MidiSource(
72         MidiEngine &engine,
73         AMediaFormat *trackMetadata)
74     : mEngine(engine),
75       mTrackMetadata(trackMetadata),
76       mInitCheck(false),
77       mStarted(false)
78 {
79     ALOGV("MidiSource ctor");
80     mInitCheck = init();
81 }
82 
~MidiSource()83 MidiSource::~MidiSource()
84 {
85     ALOGV("MidiSource dtor");
86     if (mStarted) {
87         stop();
88     }
89 }
90 
start()91 media_status_t MidiSource::start()
92 {
93     ALOGV("MidiSource::start");
94 
95     CHECK(!mStarted);
96     mStarted = true;
97     mEngine.allocateBuffers(mBufferGroup);
98     return AMEDIA_OK;
99 }
100 
stop()101 media_status_t MidiSource::stop()
102 {
103     ALOGV("MidiSource::stop");
104 
105     CHECK(mStarted);
106     mStarted = false;
107     mEngine.releaseBuffers();
108 
109     return AMEDIA_OK;
110 }
111 
getFormat(AMediaFormat * meta)112 media_status_t MidiSource::getFormat(AMediaFormat *meta)
113 {
114     return AMediaFormat_copy(meta, mTrackMetadata);
115 }
116 
read(MediaBufferHelper ** outBuffer,const ReadOptions * options)117 media_status_t MidiSource::read(
118         MediaBufferHelper **outBuffer, const ReadOptions *options)
119 {
120     ALOGV("MidiSource::read");
121 
122     MediaBufferHelper *buffer;
123     // process an optional seek request
124     int64_t seekTimeUs;
125     ReadOptions::SeekMode mode;
126     if ((NULL != options) && options->getSeekTo(&seekTimeUs, &mode)) {
127         if (seekTimeUs <= 0LL) {
128             seekTimeUs = 0LL;
129         }
130         mEngine.seekTo(seekTimeUs);
131     }
132     buffer = mEngine.readBuffer();
133     *outBuffer = buffer;
134     ALOGV("MidiSource::read %p done", this);
135     return buffer != NULL ? AMEDIA_OK : AMEDIA_ERROR_END_OF_STREAM;
136 }
137 
init()138 status_t MidiSource::init()
139 {
140     ALOGV("MidiSource::init");
141     return OK;
142 }
143 
144 // MidiEngine
145 using namespace std::chrono_literals;
146 static constexpr auto kTimeout = 10s;
147 
MidiEngine(CDataSource * dataSource,AMediaFormat * fileMetadata,AMediaFormat * trackMetadata)148 MidiEngine::MidiEngine(CDataSource *dataSource,
149         AMediaFormat *fileMetadata,
150         AMediaFormat *trackMetadata) :
151             mEasData(NULL),
152             mEasHandle(NULL),
153             mEasConfig(NULL),
154             mIsInitialized(false) {
155     Watchdog watchdog(kTimeout);
156 
157     mIoWrapper = new MidiIoWrapper(dataSource);
158     // spin up a new EAS engine
159     EAS_I32 temp;
160     EAS_RESULT result = EAS_Init(&mEasData);
161 
162     if (result == EAS_SUCCESS) {
163         result = EAS_OpenFile(mEasData, mIoWrapper->getLocator(), &mEasHandle);
164     }
165     if (result == EAS_SUCCESS) {
166         result = EAS_Prepare(mEasData, mEasHandle);
167     }
168     if (result == EAS_SUCCESS) {
169         result = EAS_ParseMetaData(mEasData, mEasHandle, &temp);
170     }
171 
172     if (result != EAS_SUCCESS) {
173         return;
174     }
175 
176     if (fileMetadata != NULL) {
177         AMediaFormat_setString(fileMetadata, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_MIDI);
178     }
179 
180     if (trackMetadata != NULL) {
181         AMediaFormat_setString(trackMetadata, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_RAW);
182         AMediaFormat_setInt64(
183                 trackMetadata, AMEDIAFORMAT_KEY_DURATION, 1000ll * temp); // milli->micro
184         mEasConfig = EAS_Config();
185         AMediaFormat_setInt32(
186                 trackMetadata, AMEDIAFORMAT_KEY_SAMPLE_RATE, mEasConfig->sampleRate);
187         AMediaFormat_setInt32(
188                 trackMetadata, AMEDIAFORMAT_KEY_CHANNEL_COUNT, mEasConfig->numChannels);
189         AMediaFormat_setInt32(
190                 trackMetadata, AMEDIAFORMAT_KEY_PCM_ENCODING, kAudioEncodingPcm16bit);
191     }
192     mIsInitialized = true;
193 }
194 
~MidiEngine()195 MidiEngine::~MidiEngine() {
196     Watchdog watchdog(kTimeout);
197 
198     if (mEasHandle) {
199         EAS_CloseFile(mEasData, mEasHandle);
200     }
201     if (mEasData) {
202         EAS_Shutdown(mEasData);
203     }
204     delete mIoWrapper;
205 }
206 
initCheck()207 status_t MidiEngine::initCheck() {
208     return mIsInitialized ? OK : UNKNOWN_ERROR;
209 }
210 
allocateBuffers(MediaBufferGroupHelper * group)211 status_t MidiEngine::allocateBuffers(MediaBufferGroupHelper *group) {
212     // select reverb preset and enable
213     EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER);
214     EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE);
215 
216     int bufsize = sizeof(EAS_PCM)
217             * mEasConfig->mixBufferSize * mEasConfig->numChannels * NUM_COMBINE_BUFFERS;
218     ALOGV("using %d byte buffer", bufsize);
219     mGroup = group;
220     mGroup->add_buffer(bufsize);
221     return OK;
222 }
223 
releaseBuffers()224 status_t MidiEngine::releaseBuffers() {
225     return OK;
226 }
227 
seekTo(int64_t positionUs)228 status_t MidiEngine::seekTo(int64_t positionUs) {
229     Watchdog watchdog(kTimeout);
230 
231     ALOGV("seekTo %lld", (long long)positionUs);
232     EAS_RESULT result = EAS_Locate(mEasData, mEasHandle, positionUs / 1000, false);
233     return result == EAS_SUCCESS ? OK : UNKNOWN_ERROR;
234 }
235 
readBuffer()236 MediaBufferHelper* MidiEngine::readBuffer() {
237     Watchdog watchdog(kTimeout);
238 
239     EAS_STATE state;
240     EAS_State(mEasData, mEasHandle, &state);
241     if ((state == EAS_STATE_STOPPED) || (state == EAS_STATE_ERROR)) {
242         return NULL;
243     }
244     MediaBufferHelper *buffer = nullptr;
245     status_t err = mGroup->acquire_buffer(&buffer);
246     if (err != OK || buffer == nullptr) {
247         ALOGE("readBuffer: no buffer");
248         return NULL;
249     }
250     EAS_I32 timeMs;
251     EAS_GetLocation(mEasData, mEasHandle, &timeMs);
252     int64_t timeUs = 1000ll * timeMs;
253     AMediaFormat *meta = buffer->meta_data();
254     AMediaFormat_setInt64(meta, AMEDIAFORMAT_KEY_TIME_US, timeUs);
255     AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, 1);
256 
257     EAS_PCM* p = (EAS_PCM*) buffer->data();
258     int numBytesOutput = 0;
259     for (int i = 0; i < NUM_COMBINE_BUFFERS; i++) {
260         EAS_I32 numRendered;
261         EAS_RESULT result = EAS_Render(mEasData, p, mEasConfig->mixBufferSize, &numRendered);
262         if (result != EAS_SUCCESS) {
263             ALOGE("EAS_Render() returned %ld, numBytesOutput = %d", result, numBytesOutput);
264             buffer->release();
265             return NULL; // Stop processing to prevent infinite loops.
266         }
267         p += numRendered * mEasConfig->numChannels;
268         numBytesOutput += numRendered * mEasConfig->numChannels * sizeof(EAS_PCM);
269     }
270     buffer->set_range(0, numBytesOutput);
271     ALOGV("readBuffer: returning %zd in buffer %p", buffer->range_length(), buffer);
272     return buffer;
273 }
274 
275 
276 // MidiExtractor
277 
MidiExtractor(CDataSource * dataSource)278 MidiExtractor::MidiExtractor(
279         CDataSource *dataSource)
280     : mDataSource(dataSource),
281       mInitCheck(false)
282 {
283     ALOGV("MidiExtractor ctor");
284     mFileMetadata = AMediaFormat_new();
285     mTrackMetadata = AMediaFormat_new();
286     mEngine = new MidiEngine(mDataSource, mFileMetadata, mTrackMetadata);
287     mInitCheck = mEngine->initCheck();
288 }
289 
~MidiExtractor()290 MidiExtractor::~MidiExtractor()
291 {
292     ALOGV("MidiExtractor dtor");
293     AMediaFormat_delete(mFileMetadata);
294     AMediaFormat_delete(mTrackMetadata);
295     delete mEngine;
296 }
297 
countTracks()298 size_t MidiExtractor::countTracks()
299 {
300     return mInitCheck == OK ? 1 : 0;
301 }
302 
getTrack(size_t index)303 MediaTrackHelper *MidiExtractor::getTrack(size_t index)
304 {
305     if (mInitCheck != OK || index > 0) {
306         return NULL;
307     }
308     return new MidiSource(*mEngine, mTrackMetadata);
309 }
310 
getTrackMetaData(AMediaFormat * meta,size_t index,uint32_t)311 media_status_t MidiExtractor::getTrackMetaData(
312         AMediaFormat *meta,
313         size_t index, uint32_t /* flags */) {
314     ALOGV("MidiExtractor::getTrackMetaData");
315     if (mInitCheck != OK || index > 0) {
316         return AMEDIA_ERROR_UNKNOWN;
317     }
318     return AMediaFormat_copy(meta, mTrackMetadata);
319 }
320 
getMetaData(AMediaFormat * meta)321 media_status_t MidiExtractor::getMetaData(AMediaFormat *meta)
322 {
323     ALOGV("MidiExtractor::getMetaData");
324     return AMediaFormat_copy(meta, mFileMetadata);
325 }
326 
startsWith(const uint8_t * buf,size_t size,const char * pattern,size_t patternSize)327 static bool startsWith(const uint8_t *buf, size_t size, const char *pattern, size_t patternSize) {
328     if (size < patternSize) {
329         return false;
330     }
331     return (memcmp(buf, pattern, patternSize) == 0);
332 }
333 
isValidMThd(const uint8_t * buf,size_t size)334 static bool isValidMThd(const uint8_t *buf, size_t size) {
335     return startsWith(buf, size, "MThd", 4);
336 }
337 
isValidXmf(const uint8_t * buf,size_t size)338 static bool isValidXmf(const uint8_t *buf, size_t size) {
339     return startsWith(buf, size, "XMF_", 4);
340 }
341 
isValidImelody(const uint8_t * buf,size_t size)342 static bool isValidImelody(const uint8_t *buf, size_t size) {
343     return startsWith(buf, size, "BEGIN:IMELODY", 13);
344 }
345 
isValidRtttl(const uint8_t * buf,size_t size)346 static bool isValidRtttl(const uint8_t *buf, size_t size) {
347     #define RTTTL_MAX_TITLE_LEN 32
348     // rtttl starts with the following:
349     // <title>:<type>=<value>
350     //
351     // Where:
352     // - <title>: Up to 32 characters
353     // - <type>: Single character indicating:
354     //     'd' for duration
355     //     'o' for octave
356     //     'b' for beats per minute
357     // - <value>: Corresponding value for the type
358     if (size < 4) {
359         return false;
360     }
361     for (size_t i = 0; i < RTTTL_MAX_TITLE_LEN && i < size; i++) {
362         if (buf[i] == ':') {
363             if (i < (size - 3) && buf[i + 2] == '=') {
364                 return true;
365             }
366             break;
367         }
368     }
369     return false;
370 }
371 
isValidOta(const uint8_t * buf,size_t size)372 static bool isValidOta(const uint8_t *buf, size_t size) {
373     #define OTA_RINGTONE 0x25
374     #define OTA_SOUND 0x1d
375     #define OTA_UNICODE 0x22
376 
377     // ota starts with the following:
378     // <cmdLen><cmd1><cmd2>..<cmdN>
379     //
380     // Where:
381     // - <cmdLen>: Single character command length
382     // - <cmd1>: Single character (OTA_RINGTONE << 1)
383     // - <cmd2>: Single character (OTA_SOUND << 1) or (OTA_UNICODE << 1)
384     //           and so on with last cmd being (0x1d << 1)
385 
386     if (size < 3) {
387         return false;
388     }
389 
390     uint8_t cmdLen = buf[0];
391     if (cmdLen < 2) {
392         return false;
393     }
394 
395     if ((buf[1] >> 1) != OTA_RINGTONE) {
396         return false;
397     }
398     cmdLen--;
399 
400     size_t i = 2;
401     while(cmdLen && i < size) {
402         switch(buf[i] >> 1) {
403             case OTA_SOUND:
404                 return true;
405             case OTA_UNICODE:
406                 break;
407             default:
408                 return false;
409         }
410         cmdLen--;
411         i++;
412     }
413 
414     return false;
415 }
416 
SniffMidiLegacy(CDataSource * source,float * confidence)417 bool SniffMidiLegacy(CDataSource *source, float *confidence) {
418     MidiEngine p(source, NULL, NULL);
419     if (p.initCheck() == OK) {
420         *confidence = 0.8;
421         ALOGV("SniffMidi: yes");
422         return true;
423     }
424     ALOGV("SniffMidi: no");
425     return false;
426 }
427 
SniffMidiEfficiently(CDataSource * source,float * confidence)428 bool SniffMidiEfficiently(CDataSource *source, float *confidence) {
429     uint8_t header[128];
430     int filled = source->readAt(source->handle, 0, header, sizeof(header));
431 
432     if (isValidMThd(header, filled)) {
433         *confidence = 0.80;
434         ALOGV("SniffMidi: yes, MThd");
435         return true;
436     }
437     if (isValidXmf(header, filled)) {
438         *confidence = 0.80;
439         ALOGV("SniffMidi: yes, XMF_");
440         return true;
441     }
442     if (isValidImelody(header, filled)) {
443         *confidence = 0.80;
444         ALOGV("SniffMidi: yes, imelody");
445         return true;
446     }
447     if (isValidRtttl(header, filled)) {
448         *confidence = 0.80;
449         ALOGV("SniffMidi: yes, rtttl");
450         return true;
451     }
452     if (isValidOta(header, filled)) {
453         *confidence = 0.80;
454         ALOGV("SniffMidi: yes, ota");
455         return true;
456     }
457     ALOGV("SniffMidi: no");
458     return false;
459 }
460 
461 // Sniffer
SniffMidi(CDataSource * source,float * confidence)462 bool SniffMidi(CDataSource *source, float *confidence) {
463     if(com::android::media::extractor::flags::extractor_sniff_midi_optimizations()) {
464         return SniffMidiEfficiently(source, confidence);
465     }
466     return SniffMidiLegacy(source, confidence);
467 }
468 
469 static const char *extensions[] = {
470     "imy",
471     "mid",
472     "midi",
473     "mxmf",
474     "ota",
475     "rtttl",
476     "rtx",
477     "smf",
478     "xmf",
479     NULL
480 };
481 
482 extern "C" {
483 // This is the only symbol that needs to be exported
484 __attribute__ ((visibility ("default")))
GETEXTRACTORDEF()485 ExtractorDef GETEXTRACTORDEF() {
486     return {
487         EXTRACTORDEF_VERSION,
488         UUID("ef6cca0a-f8a2-43e6-ba5f-dfcd7c9a7ef2"),
489         1,
490         "MIDI Extractor",
491         {
492             .v3 = {
493                 [](
494                 CDataSource *source,
495                 float *confidence,
496                 void **,
497                 FreeMetaFunc *) -> CreatorFunc {
498                     if (SniffMidi(source, confidence)) {
499                         return [](
500                                 CDataSource *source,
501                                 void *) -> CMediaExtractor* {
502                             return wrap(new MidiExtractor(source));};
503                     }
504                     return NULL;
505                 },
506                 extensions
507             }
508         },
509     };
510 }
511 
512 } // extern "C"
513 
514 }  // namespace android
515