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