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