1 /* MidiFile.cpp
2 **
3 ** Copyright 2007, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 ** http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17
18 //#define LOG_NDEBUG 0
19 #define LOG_TAG "MidiFile"
20 #include "utils/Log.h"
21
22 #include <stdio.h>
23 #include <assert.h>
24 #include <limits.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <sched.h>
28 #include <utils/threads.h>
29 #include <libsonivox/eas_reverb.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33
34 #include <system/audio.h>
35
36 #include "MidiFile.h"
37
38 // ----------------------------------------------------------------------------
39
40 namespace android {
41
42 // ----------------------------------------------------------------------------
43
44 // The midi engine buffers are a bit small (128 frames), so we batch them up
45 static const int NUM_BUFFERS = 4;
46
47 // TODO: Determine appropriate return codes
48 static status_t ERROR_NOT_OPEN = -1;
49 static status_t ERROR_OPEN_FAILED = -2;
50 static status_t ERROR_EAS_FAILURE = -3;
51 static status_t ERROR_ALLOCATE_FAILED = -4;
52
53 static const S_EAS_LIB_CONFIG* pLibConfig = NULL;
54
MidiFile()55 MidiFile::MidiFile() :
56 mEasData(NULL), mEasHandle(NULL), mAudioBuffer(NULL),
57 mPlayTime(-1), mDuration(-1), mState(EAS_STATE_ERROR),
58 mStreamType(AUDIO_STREAM_MUSIC), mLoop(false), mExit(false),
59 mPaused(false), mRender(false), mTid(-1)
60 {
61 ALOGV("constructor");
62
63 mFileLocator.path = NULL;
64 mFileLocator.fd = -1;
65 mFileLocator.offset = 0;
66 mFileLocator.length = 0;
67
68 // get the library configuration and do sanity check
69 if (pLibConfig == NULL)
70 pLibConfig = EAS_Config();
71 if ((pLibConfig == NULL) || (LIB_VERSION != pLibConfig->libVersion)) {
72 ALOGE("EAS library/header mismatch");
73 goto Failed;
74 }
75
76 // initialize EAS library
77 if (EAS_Init(&mEasData) != EAS_SUCCESS) {
78 ALOGE("EAS_Init failed");
79 goto Failed;
80 }
81
82 // select reverb preset and enable
83 EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER);
84 EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE);
85
86 // create playback thread
87 {
88 Mutex::Autolock l(mMutex);
89 mThread = new MidiFileThread(this);
90 mThread->run("midithread", ANDROID_PRIORITY_AUDIO);
91 mCondition.wait(mMutex);
92 ALOGV("thread started");
93 }
94
95 // indicate success
96 if (mTid > 0) {
97 ALOGV(" render thread(%d) started", mTid);
98 mState = EAS_STATE_READY;
99 }
100
101 Failed:
102 return;
103 }
104
initCheck()105 status_t MidiFile::initCheck()
106 {
107 if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE;
108 return NO_ERROR;
109 }
110
~MidiFile()111 MidiFile::~MidiFile() {
112 ALOGV("MidiFile destructor");
113 release();
114 }
115
setDataSource(const sp<IMediaHTTPService> &,const char * path,const KeyedVector<String8,String8> *)116 status_t MidiFile::setDataSource(
117 const sp<IMediaHTTPService> & /*httpService*/,
118 const char* path,
119 const KeyedVector<String8, String8> *) {
120 ALOGV("MidiFile::setDataSource url=%s", path);
121 Mutex::Autolock lock(mMutex);
122
123 // file still open?
124 if (mEasHandle) {
125 reset_nosync();
126 }
127
128 // open file and set paused state
129 mFileLocator.path = strdup(path);
130 mFileLocator.fd = -1;
131 mFileLocator.offset = 0;
132 mFileLocator.length = 0;
133 EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
134 if (result == EAS_SUCCESS) {
135 updateState();
136 }
137
138 if (result != EAS_SUCCESS) {
139 ALOGE("EAS_OpenFile failed: [%d]", (int)result);
140 mState = EAS_STATE_ERROR;
141 return ERROR_OPEN_FAILED;
142 }
143
144 mState = EAS_STATE_OPEN;
145 mPlayTime = 0;
146 return NO_ERROR;
147 }
148
setDataSource(int fd,int64_t offset,int64_t length)149 status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length)
150 {
151 ALOGV("MidiFile::setDataSource fd=%d", fd);
152 Mutex::Autolock lock(mMutex);
153
154 // file still open?
155 if (mEasHandle) {
156 reset_nosync();
157 }
158
159 // open file and set paused state
160 mFileLocator.fd = dup(fd);
161 mFileLocator.offset = offset;
162 mFileLocator.length = length;
163 EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
164 updateState();
165
166 if (result != EAS_SUCCESS) {
167 ALOGE("EAS_OpenFile failed: [%d]", (int)result);
168 mState = EAS_STATE_ERROR;
169 return ERROR_OPEN_FAILED;
170 }
171
172 mState = EAS_STATE_OPEN;
173 mPlayTime = 0;
174 return NO_ERROR;
175 }
176
prepare()177 status_t MidiFile::prepare()
178 {
179 ALOGV("MidiFile::prepare");
180 Mutex::Autolock lock(mMutex);
181 if (!mEasHandle) {
182 return ERROR_NOT_OPEN;
183 }
184 EAS_RESULT result;
185 if ((result = EAS_Prepare(mEasData, mEasHandle)) != EAS_SUCCESS) {
186 ALOGE("EAS_Prepare failed: [%ld]", result);
187 return ERROR_EAS_FAILURE;
188 }
189 updateState();
190 return NO_ERROR;
191 }
192
prepareAsync()193 status_t MidiFile::prepareAsync()
194 {
195 ALOGV("MidiFile::prepareAsync");
196 status_t ret = prepare();
197
198 // don't hold lock during callback
199 if (ret == NO_ERROR) {
200 sendEvent(MEDIA_PREPARED);
201 } else {
202 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, ret);
203 }
204 return ret;
205 }
206
start()207 status_t MidiFile::start()
208 {
209 ALOGV("MidiFile::start");
210 Mutex::Autolock lock(mMutex);
211 if (!mEasHandle) {
212 return ERROR_NOT_OPEN;
213 }
214
215 // resuming after pause?
216 if (mPaused) {
217 if (EAS_Resume(mEasData, mEasHandle) != EAS_SUCCESS) {
218 return ERROR_EAS_FAILURE;
219 }
220 mPaused = false;
221 updateState();
222 }
223
224 mRender = true;
225 if (mState == EAS_STATE_PLAY) {
226 sendEvent(MEDIA_STARTED);
227 }
228
229 // wake up render thread
230 ALOGV(" wakeup render thread");
231 mCondition.signal();
232 return NO_ERROR;
233 }
234
stop()235 status_t MidiFile::stop()
236 {
237 ALOGV("MidiFile::stop");
238 Mutex::Autolock lock(mMutex);
239 if (!mEasHandle) {
240 return ERROR_NOT_OPEN;
241 }
242 if (!mPaused && (mState != EAS_STATE_STOPPED)) {
243 EAS_RESULT result = EAS_Pause(mEasData, mEasHandle);
244 if (result != EAS_SUCCESS) {
245 ALOGE("EAS_Pause returned error %ld", result);
246 return ERROR_EAS_FAILURE;
247 }
248 }
249 mPaused = false;
250 sendEvent(MEDIA_STOPPED);
251 return NO_ERROR;
252 }
253
seekTo(int position)254 status_t MidiFile::seekTo(int position)
255 {
256 ALOGV("MidiFile::seekTo %d", position);
257 // hold lock during EAS calls
258 {
259 Mutex::Autolock lock(mMutex);
260 if (!mEasHandle) {
261 return ERROR_NOT_OPEN;
262 }
263 EAS_RESULT result;
264 if ((result = EAS_Locate(mEasData, mEasHandle, position, false))
265 != EAS_SUCCESS)
266 {
267 ALOGE("EAS_Locate returned %ld", result);
268 return ERROR_EAS_FAILURE;
269 }
270 EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
271 }
272 sendEvent(MEDIA_SEEK_COMPLETE);
273 return NO_ERROR;
274 }
275
pause()276 status_t MidiFile::pause()
277 {
278 ALOGV("MidiFile::pause");
279 Mutex::Autolock lock(mMutex);
280 if (!mEasHandle) {
281 return ERROR_NOT_OPEN;
282 }
283 if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR;
284 if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) {
285 return ERROR_EAS_FAILURE;
286 }
287 mPaused = true;
288 sendEvent(MEDIA_PAUSED);
289 return NO_ERROR;
290 }
291
isPlaying()292 bool MidiFile::isPlaying()
293 {
294 ALOGV("MidiFile::isPlaying, mState=%d", int(mState));
295 if (!mEasHandle || mPaused) return false;
296 return (mState == EAS_STATE_PLAY);
297 }
298
getCurrentPosition(int * position)299 status_t MidiFile::getCurrentPosition(int* position)
300 {
301 ALOGV("MidiFile::getCurrentPosition");
302 if (!mEasHandle) {
303 ALOGE("getCurrentPosition(): file not open");
304 return ERROR_NOT_OPEN;
305 }
306 if (mPlayTime < 0) {
307 ALOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime);
308 return ERROR_EAS_FAILURE;
309 }
310 *position = mPlayTime;
311 return NO_ERROR;
312 }
313
getDuration(int * duration)314 status_t MidiFile::getDuration(int* duration)
315 {
316
317 ALOGV("MidiFile::getDuration");
318 {
319 Mutex::Autolock lock(mMutex);
320 if (!mEasHandle) return ERROR_NOT_OPEN;
321 *duration = mDuration;
322 }
323
324 // if no duration cached, get the duration
325 // don't need a lock here because we spin up a new engine
326 if (*duration < 0) {
327 EAS_I32 temp;
328 EAS_DATA_HANDLE easData = NULL;
329 EAS_HANDLE easHandle = NULL;
330 EAS_RESULT result = EAS_Init(&easData);
331 if (result == EAS_SUCCESS) {
332 result = EAS_OpenFile(easData, &mFileLocator, &easHandle);
333 }
334 if (result == EAS_SUCCESS) {
335 result = EAS_Prepare(easData, easHandle);
336 }
337 if (result == EAS_SUCCESS) {
338 result = EAS_ParseMetaData(easData, easHandle, &temp);
339 }
340 if (easHandle) {
341 EAS_CloseFile(easData, easHandle);
342 }
343 if (easData) {
344 EAS_Shutdown(easData);
345 }
346
347 if (result != EAS_SUCCESS) {
348 return ERROR_EAS_FAILURE;
349 }
350
351 // cache successful result
352 mDuration = *duration = int(temp);
353 }
354
355 return NO_ERROR;
356 }
357
release()358 status_t MidiFile::release()
359 {
360 ALOGV("MidiFile::release");
361 Mutex::Autolock l(mMutex);
362 reset_nosync();
363
364 // wait for render thread to exit
365 mExit = true;
366 mCondition.signal();
367
368 // wait for thread to exit
369 if (mAudioBuffer) {
370 mCondition.wait(mMutex);
371 }
372
373 // release resources
374 if (mEasData) {
375 EAS_Shutdown(mEasData);
376 mEasData = NULL;
377 }
378 return NO_ERROR;
379 }
380
reset()381 status_t MidiFile::reset()
382 {
383 ALOGV("MidiFile::reset");
384 Mutex::Autolock lock(mMutex);
385 return reset_nosync();
386 }
387
388 // call only with mutex held
reset_nosync()389 status_t MidiFile::reset_nosync()
390 {
391 ALOGV("MidiFile::reset_nosync");
392 sendEvent(MEDIA_STOPPED);
393 // close file
394 if (mEasHandle) {
395 EAS_CloseFile(mEasData, mEasHandle);
396 mEasHandle = NULL;
397 }
398 if (mFileLocator.path) {
399 free((void*)mFileLocator.path);
400 mFileLocator.path = NULL;
401 }
402 if (mFileLocator.fd >= 0) {
403 close(mFileLocator.fd);
404 }
405 mFileLocator.fd = -1;
406 mFileLocator.offset = 0;
407 mFileLocator.length = 0;
408
409 mPlayTime = -1;
410 mDuration = -1;
411 mLoop = false;
412 mPaused = false;
413 mRender = false;
414 return NO_ERROR;
415 }
416
setLooping(int loop)417 status_t MidiFile::setLooping(int loop)
418 {
419 ALOGV("MidiFile::setLooping");
420 Mutex::Autolock lock(mMutex);
421 if (!mEasHandle) {
422 return ERROR_NOT_OPEN;
423 }
424 loop = loop ? -1 : 0;
425 if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) {
426 return ERROR_EAS_FAILURE;
427 }
428 return NO_ERROR;
429 }
430
createOutputTrack()431 status_t MidiFile::createOutputTrack() {
432 if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels,
433 CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 2 /*bufferCount*/) != NO_ERROR) {
434 ALOGE("mAudioSink open failed");
435 return ERROR_OPEN_FAILED;
436 }
437 return NO_ERROR;
438 }
439
render()440 int MidiFile::render() {
441 EAS_RESULT result = EAS_FAILURE;
442 EAS_I32 count;
443 int temp;
444 bool audioStarted = false;
445
446 ALOGV("MidiFile::render");
447
448 // allocate render buffer
449 mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS];
450 if (!mAudioBuffer) {
451 ALOGE("mAudioBuffer allocate failed");
452 goto threadExit;
453 }
454
455 // signal main thread that we started
456 {
457 Mutex::Autolock l(mMutex);
458 mTid = gettid();
459 ALOGV("render thread(%d) signal", mTid);
460 mCondition.signal();
461 }
462
463 while (1) {
464 mMutex.lock();
465
466 // nothing to render, wait for client thread to wake us up
467 while (!mRender && !mExit)
468 {
469 ALOGV("MidiFile::render - signal wait");
470 mCondition.wait(mMutex);
471 ALOGV("MidiFile::render - signal rx'd");
472 }
473 if (mExit) {
474 mMutex.unlock();
475 break;
476 }
477
478 // render midi data into the input buffer
479 //ALOGV("MidiFile::render - rendering audio");
480 int num_output = 0;
481 EAS_PCM* p = mAudioBuffer;
482 for (int i = 0; i < NUM_BUFFERS; i++) {
483 result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count);
484 if (result != EAS_SUCCESS) {
485 ALOGE("EAS_Render returned %ld", result);
486 }
487 p += count * pLibConfig->numChannels;
488 num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM);
489 }
490
491 // update playback state and position
492 // ALOGV("MidiFile::render - updating state");
493 EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
494 EAS_State(mEasData, mEasHandle, &mState);
495 mMutex.unlock();
496
497 // create audio output track if necessary
498 if (!mAudioSink->ready()) {
499 ALOGV("MidiFile::render - create output track");
500 if (createOutputTrack() != NO_ERROR)
501 goto threadExit;
502 }
503
504 // Write data to the audio hardware
505 // ALOGV("MidiFile::render - writing to audio output");
506 if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) {
507 ALOGE("Error in writing:%d",temp);
508 return temp;
509 }
510
511 // start audio output if necessary
512 if (!audioStarted) {
513 //ALOGV("MidiFile::render - starting audio");
514 mAudioSink->start();
515 audioStarted = true;
516 }
517
518 // still playing?
519 if ((mState == EAS_STATE_STOPPED) || (mState == EAS_STATE_ERROR) ||
520 (mState == EAS_STATE_PAUSED))
521 {
522 switch(mState) {
523 case EAS_STATE_STOPPED:
524 {
525 ALOGV("MidiFile::render - stopped");
526 sendEvent(MEDIA_PLAYBACK_COMPLETE);
527 break;
528 }
529 case EAS_STATE_ERROR:
530 {
531 ALOGE("MidiFile::render - error");
532 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN);
533 break;
534 }
535 case EAS_STATE_PAUSED:
536 ALOGV("MidiFile::render - paused");
537 break;
538 default:
539 break;
540 }
541 mAudioSink->stop();
542 audioStarted = false;
543 mRender = false;
544 }
545 }
546
547 threadExit:
548 mAudioSink.clear();
549 if (mAudioBuffer) {
550 delete [] mAudioBuffer;
551 mAudioBuffer = NULL;
552 }
553 mMutex.lock();
554 mTid = -1;
555 mCondition.signal();
556 mMutex.unlock();
557 return result;
558 }
559
560 } // end namespace android
561