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 char * path,const KeyedVector<String8,String8> *)116 status_t MidiFile::setDataSource(
117 const char* path, const KeyedVector<String8, String8> *) {
118 ALOGV("MidiFile::setDataSource url=%s", path);
119 Mutex::Autolock lock(mMutex);
120
121 // file still open?
122 if (mEasHandle) {
123 reset_nosync();
124 }
125
126 // open file and set paused state
127 mFileLocator.path = strdup(path);
128 mFileLocator.fd = -1;
129 mFileLocator.offset = 0;
130 mFileLocator.length = 0;
131 EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
132 if (result == EAS_SUCCESS) {
133 updateState();
134 }
135
136 if (result != EAS_SUCCESS) {
137 ALOGE("EAS_OpenFile failed: [%d]", (int)result);
138 mState = EAS_STATE_ERROR;
139 return ERROR_OPEN_FAILED;
140 }
141
142 mState = EAS_STATE_OPEN;
143 mPlayTime = 0;
144 return NO_ERROR;
145 }
146
setDataSource(int fd,int64_t offset,int64_t length)147 status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length)
148 {
149 ALOGV("MidiFile::setDataSource fd=%d", fd);
150 Mutex::Autolock lock(mMutex);
151
152 // file still open?
153 if (mEasHandle) {
154 reset_nosync();
155 }
156
157 // open file and set paused state
158 mFileLocator.fd = dup(fd);
159 mFileLocator.offset = offset;
160 mFileLocator.length = length;
161 EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
162 updateState();
163
164 if (result != EAS_SUCCESS) {
165 ALOGE("EAS_OpenFile failed: [%d]", (int)result);
166 mState = EAS_STATE_ERROR;
167 return ERROR_OPEN_FAILED;
168 }
169
170 mState = EAS_STATE_OPEN;
171 mPlayTime = 0;
172 return NO_ERROR;
173 }
174
prepare()175 status_t MidiFile::prepare()
176 {
177 ALOGV("MidiFile::prepare");
178 Mutex::Autolock lock(mMutex);
179 if (!mEasHandle) {
180 return ERROR_NOT_OPEN;
181 }
182 EAS_RESULT result;
183 if ((result = EAS_Prepare(mEasData, mEasHandle)) != EAS_SUCCESS) {
184 ALOGE("EAS_Prepare failed: [%ld]", result);
185 return ERROR_EAS_FAILURE;
186 }
187 updateState();
188 return NO_ERROR;
189 }
190
prepareAsync()191 status_t MidiFile::prepareAsync()
192 {
193 ALOGV("MidiFile::prepareAsync");
194 status_t ret = prepare();
195
196 // don't hold lock during callback
197 if (ret == NO_ERROR) {
198 sendEvent(MEDIA_PREPARED);
199 } else {
200 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, ret);
201 }
202 return ret;
203 }
204
start()205 status_t MidiFile::start()
206 {
207 ALOGV("MidiFile::start");
208 Mutex::Autolock lock(mMutex);
209 if (!mEasHandle) {
210 return ERROR_NOT_OPEN;
211 }
212
213 // resuming after pause?
214 if (mPaused) {
215 if (EAS_Resume(mEasData, mEasHandle) != EAS_SUCCESS) {
216 return ERROR_EAS_FAILURE;
217 }
218 mPaused = false;
219 updateState();
220 }
221
222 mRender = true;
223
224 // wake up render thread
225 ALOGV(" wakeup render thread");
226 mCondition.signal();
227 return NO_ERROR;
228 }
229
stop()230 status_t MidiFile::stop()
231 {
232 ALOGV("MidiFile::stop");
233 Mutex::Autolock lock(mMutex);
234 if (!mEasHandle) {
235 return ERROR_NOT_OPEN;
236 }
237 if (!mPaused && (mState != EAS_STATE_STOPPED)) {
238 EAS_RESULT result = EAS_Pause(mEasData, mEasHandle);
239 if (result != EAS_SUCCESS) {
240 ALOGE("EAS_Pause returned error %ld", result);
241 return ERROR_EAS_FAILURE;
242 }
243 }
244 mPaused = false;
245 return NO_ERROR;
246 }
247
seekTo(int position)248 status_t MidiFile::seekTo(int position)
249 {
250 ALOGV("MidiFile::seekTo %d", position);
251 // hold lock during EAS calls
252 {
253 Mutex::Autolock lock(mMutex);
254 if (!mEasHandle) {
255 return ERROR_NOT_OPEN;
256 }
257 EAS_RESULT result;
258 if ((result = EAS_Locate(mEasData, mEasHandle, position, false))
259 != EAS_SUCCESS)
260 {
261 ALOGE("EAS_Locate returned %ld", result);
262 return ERROR_EAS_FAILURE;
263 }
264 EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
265 }
266 sendEvent(MEDIA_SEEK_COMPLETE);
267 return NO_ERROR;
268 }
269
pause()270 status_t MidiFile::pause()
271 {
272 ALOGV("MidiFile::pause");
273 Mutex::Autolock lock(mMutex);
274 if (!mEasHandle) {
275 return ERROR_NOT_OPEN;
276 }
277 if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR;
278 if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) {
279 return ERROR_EAS_FAILURE;
280 }
281 mPaused = true;
282 return NO_ERROR;
283 }
284
isPlaying()285 bool MidiFile::isPlaying()
286 {
287 ALOGV("MidiFile::isPlaying, mState=%d", int(mState));
288 if (!mEasHandle || mPaused) return false;
289 return (mState == EAS_STATE_PLAY);
290 }
291
getCurrentPosition(int * position)292 status_t MidiFile::getCurrentPosition(int* position)
293 {
294 ALOGV("MidiFile::getCurrentPosition");
295 if (!mEasHandle) {
296 ALOGE("getCurrentPosition(): file not open");
297 return ERROR_NOT_OPEN;
298 }
299 if (mPlayTime < 0) {
300 ALOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime);
301 return ERROR_EAS_FAILURE;
302 }
303 *position = mPlayTime;
304 return NO_ERROR;
305 }
306
getDuration(int * duration)307 status_t MidiFile::getDuration(int* duration)
308 {
309
310 ALOGV("MidiFile::getDuration");
311 {
312 Mutex::Autolock lock(mMutex);
313 if (!mEasHandle) return ERROR_NOT_OPEN;
314 *duration = mDuration;
315 }
316
317 // if no duration cached, get the duration
318 // don't need a lock here because we spin up a new engine
319 if (*duration < 0) {
320 EAS_I32 temp;
321 EAS_DATA_HANDLE easData = NULL;
322 EAS_HANDLE easHandle = NULL;
323 EAS_RESULT result = EAS_Init(&easData);
324 if (result == EAS_SUCCESS) {
325 result = EAS_OpenFile(easData, &mFileLocator, &easHandle);
326 }
327 if (result == EAS_SUCCESS) {
328 result = EAS_Prepare(easData, easHandle);
329 }
330 if (result == EAS_SUCCESS) {
331 result = EAS_ParseMetaData(easData, easHandle, &temp);
332 }
333 if (easHandle) {
334 EAS_CloseFile(easData, easHandle);
335 }
336 if (easData) {
337 EAS_Shutdown(easData);
338 }
339
340 if (result != EAS_SUCCESS) {
341 return ERROR_EAS_FAILURE;
342 }
343
344 // cache successful result
345 mDuration = *duration = int(temp);
346 }
347
348 return NO_ERROR;
349 }
350
release()351 status_t MidiFile::release()
352 {
353 ALOGV("MidiFile::release");
354 Mutex::Autolock l(mMutex);
355 reset_nosync();
356
357 // wait for render thread to exit
358 mExit = true;
359 mCondition.signal();
360
361 // wait for thread to exit
362 if (mAudioBuffer) {
363 mCondition.wait(mMutex);
364 }
365
366 // release resources
367 if (mEasData) {
368 EAS_Shutdown(mEasData);
369 mEasData = NULL;
370 }
371 return NO_ERROR;
372 }
373
reset()374 status_t MidiFile::reset()
375 {
376 ALOGV("MidiFile::reset");
377 Mutex::Autolock lock(mMutex);
378 return reset_nosync();
379 }
380
381 // call only with mutex held
reset_nosync()382 status_t MidiFile::reset_nosync()
383 {
384 ALOGV("MidiFile::reset_nosync");
385 // close file
386 if (mEasHandle) {
387 EAS_CloseFile(mEasData, mEasHandle);
388 mEasHandle = NULL;
389 }
390 if (mFileLocator.path) {
391 free((void*)mFileLocator.path);
392 mFileLocator.path = NULL;
393 }
394 if (mFileLocator.fd >= 0) {
395 close(mFileLocator.fd);
396 }
397 mFileLocator.fd = -1;
398 mFileLocator.offset = 0;
399 mFileLocator.length = 0;
400
401 mPlayTime = -1;
402 mDuration = -1;
403 mLoop = false;
404 mPaused = false;
405 mRender = false;
406 return NO_ERROR;
407 }
408
setLooping(int loop)409 status_t MidiFile::setLooping(int loop)
410 {
411 ALOGV("MidiFile::setLooping");
412 Mutex::Autolock lock(mMutex);
413 if (!mEasHandle) {
414 return ERROR_NOT_OPEN;
415 }
416 loop = loop ? -1 : 0;
417 if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) {
418 return ERROR_EAS_FAILURE;
419 }
420 return NO_ERROR;
421 }
422
createOutputTrack()423 status_t MidiFile::createOutputTrack() {
424 if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels,
425 CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 2) != NO_ERROR) {
426 ALOGE("mAudioSink open failed");
427 return ERROR_OPEN_FAILED;
428 }
429 return NO_ERROR;
430 }
431
render()432 int MidiFile::render() {
433 EAS_RESULT result = EAS_FAILURE;
434 EAS_I32 count;
435 int temp;
436 bool audioStarted = false;
437
438 ALOGV("MidiFile::render");
439
440 // allocate render buffer
441 mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS];
442 if (!mAudioBuffer) {
443 ALOGE("mAudioBuffer allocate failed");
444 goto threadExit;
445 }
446
447 // signal main thread that we started
448 {
449 Mutex::Autolock l(mMutex);
450 mTid = gettid();
451 ALOGV("render thread(%d) signal", mTid);
452 mCondition.signal();
453 }
454
455 while (1) {
456 mMutex.lock();
457
458 // nothing to render, wait for client thread to wake us up
459 while (!mRender && !mExit)
460 {
461 ALOGV("MidiFile::render - signal wait");
462 mCondition.wait(mMutex);
463 ALOGV("MidiFile::render - signal rx'd");
464 }
465 if (mExit) {
466 mMutex.unlock();
467 break;
468 }
469
470 // render midi data into the input buffer
471 //ALOGV("MidiFile::render - rendering audio");
472 int num_output = 0;
473 EAS_PCM* p = mAudioBuffer;
474 for (int i = 0; i < NUM_BUFFERS; i++) {
475 result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count);
476 if (result != EAS_SUCCESS) {
477 ALOGE("EAS_Render returned %ld", result);
478 }
479 p += count * pLibConfig->numChannels;
480 num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM);
481 }
482
483 // update playback state and position
484 // ALOGV("MidiFile::render - updating state");
485 EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
486 EAS_State(mEasData, mEasHandle, &mState);
487 mMutex.unlock();
488
489 // create audio output track if necessary
490 if (!mAudioSink->ready()) {
491 ALOGV("MidiFile::render - create output track");
492 if (createOutputTrack() != NO_ERROR)
493 goto threadExit;
494 }
495
496 // Write data to the audio hardware
497 // ALOGV("MidiFile::render - writing to audio output");
498 if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) {
499 ALOGE("Error in writing:%d",temp);
500 return temp;
501 }
502
503 // start audio output if necessary
504 if (!audioStarted) {
505 //ALOGV("MidiFile::render - starting audio");
506 mAudioSink->start();
507 audioStarted = true;
508 }
509
510 // still playing?
511 if ((mState == EAS_STATE_STOPPED) || (mState == EAS_STATE_ERROR) ||
512 (mState == EAS_STATE_PAUSED))
513 {
514 switch(mState) {
515 case EAS_STATE_STOPPED:
516 {
517 ALOGV("MidiFile::render - stopped");
518 sendEvent(MEDIA_PLAYBACK_COMPLETE);
519 break;
520 }
521 case EAS_STATE_ERROR:
522 {
523 ALOGE("MidiFile::render - error");
524 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN);
525 break;
526 }
527 case EAS_STATE_PAUSED:
528 ALOGV("MidiFile::render - paused");
529 break;
530 default:
531 break;
532 }
533 mAudioSink->stop();
534 audioStarted = false;
535 mRender = false;
536 }
537 }
538
539 threadExit:
540 mAudioSink.clear();
541 if (mAudioBuffer) {
542 delete [] mAudioBuffer;
543 mAudioBuffer = NULL;
544 }
545 mMutex.lock();
546 mTid = -1;
547 mCondition.signal();
548 mMutex.unlock();
549 return result;
550 }
551
552 } // end namespace android
553