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