blob: 37a3db3cfde0544e32bfcdff743f5325973ea224 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/* 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
Dima Zavin24fc2fb2011-04-19 22:30:36 -070033#include <hardware/audio.h>
34
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035#include "MidiFile.h"
36
37#ifdef HAVE_GETTID
38static pid_t myTid() { return gettid(); }
39#else
40static pid_t myTid() { return getpid(); }
41#endif
42
43// ----------------------------------------------------------------------------
44
45namespace android {
46
47// ----------------------------------------------------------------------------
48
49// The midi engine buffers are a bit small (128 frames), so we batch them up
50static const int NUM_BUFFERS = 4;
51
52// TODO: Determine appropriate return codes
53static status_t ERROR_NOT_OPEN = -1;
54static status_t ERROR_OPEN_FAILED = -2;
55static status_t ERROR_EAS_FAILURE = -3;
56static status_t ERROR_ALLOCATE_FAILED = -4;
57
58static const S_EAS_LIB_CONFIG* pLibConfig = NULL;
59
60MidiFile::MidiFile() :
61 mEasData(NULL), mEasHandle(NULL), mAudioBuffer(NULL),
62 mPlayTime(-1), mDuration(-1), mState(EAS_STATE_ERROR),
Dima Zavin24fc2fb2011-04-19 22:30:36 -070063 mStreamType(AUDIO_STREAM_MUSIC), mLoop(false), mExit(false),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 mPaused(false), mRender(false), mTid(-1)
65{
66 LOGV("constructor");
67
68 mFileLocator.path = NULL;
69 mFileLocator.fd = -1;
70 mFileLocator.offset = 0;
71 mFileLocator.length = 0;
72
73 // get the library configuration and do sanity check
74 if (pLibConfig == NULL)
75 pLibConfig = EAS_Config();
76 if ((pLibConfig == NULL) || (LIB_VERSION != pLibConfig->libVersion)) {
77 LOGE("EAS library/header mismatch");
78 goto Failed;
79 }
80
81 // initialize EAS library
82 if (EAS_Init(&mEasData) != EAS_SUCCESS) {
83 LOGE("EAS_Init failed");
84 goto Failed;
85 }
86
87 // select reverb preset and enable
88 EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER);
89 EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE);
90
91 // create playback thread
92 {
93 Mutex::Autolock l(mMutex);
Dave Sparksc0827692009-04-29 12:59:33 -070094 createThreadEtc(renderThread, this, "midithread", ANDROID_PRIORITY_AUDIO);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095 mCondition.wait(mMutex);
96 LOGV("thread started");
97 }
98
99 // indicate success
100 if (mTid > 0) {
101 LOGV(" render thread(%d) started", mTid);
102 mState = EAS_STATE_READY;
103 }
104
105Failed:
106 return;
107}
108
109status_t MidiFile::initCheck()
110{
111 if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE;
112 return NO_ERROR;
113}
114
115MidiFile::~MidiFile() {
116 LOGV("MidiFile destructor");
117 release();
118}
119
Andreas Huber25643002010-01-28 11:19:57 -0800120status_t MidiFile::setDataSource(
121 const char* path, const KeyedVector<String8, String8> *) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 LOGV("MidiFile::setDataSource url=%s", path);
123 Mutex::Autolock lock(mMutex);
124
125 // file still open?
126 if (mEasHandle) {
127 reset_nosync();
128 }
129
130 // open file and set paused state
131 mFileLocator.path = strdup(path);
132 mFileLocator.fd = -1;
133 mFileLocator.offset = 0;
134 mFileLocator.length = 0;
135 EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
136 if (result == EAS_SUCCESS) {
137 updateState();
138 }
139
140 if (result != EAS_SUCCESS) {
141 LOGE("EAS_OpenFile failed: [%d]", (int)result);
142 mState = EAS_STATE_ERROR;
143 return ERROR_OPEN_FAILED;
144 }
145
146 mState = EAS_STATE_OPEN;
147 mPlayTime = 0;
148 return NO_ERROR;
149}
150
151status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length)
152{
153 LOGV("MidiFile::setDataSource fd=%d", fd);
154 Mutex::Autolock lock(mMutex);
155
156 // file still open?
157 if (mEasHandle) {
158 reset_nosync();
159 }
160
161 // open file and set paused state
162 mFileLocator.fd = dup(fd);
163 mFileLocator.offset = offset;
164 mFileLocator.length = length;
165 EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
166 updateState();
167
168 if (result != EAS_SUCCESS) {
169 LOGE("EAS_OpenFile failed: [%d]", (int)result);
170 mState = EAS_STATE_ERROR;
171 return ERROR_OPEN_FAILED;
172 }
173
174 mState = EAS_STATE_OPEN;
175 mPlayTime = 0;
176 return NO_ERROR;
177}
178
179status_t MidiFile::prepare()
180{
181 LOGV("MidiFile::prepare");
182 Mutex::Autolock lock(mMutex);
183 if (!mEasHandle) {
184 return ERROR_NOT_OPEN;
185 }
186 EAS_RESULT result;
187 if ((result = EAS_Prepare(mEasData, mEasHandle)) != EAS_SUCCESS) {
188 LOGE("EAS_Prepare failed: [%ld]", result);
189 return ERROR_EAS_FAILURE;
190 }
191 updateState();
192 return NO_ERROR;
193}
194
195status_t MidiFile::prepareAsync()
196{
197 LOGV("MidiFile::prepareAsync");
198 status_t ret = prepare();
199
200 // don't hold lock during callback
201 if (ret == NO_ERROR) {
202 sendEvent(MEDIA_PREPARED);
203 } else {
204 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, ret);
205 }
206 return ret;
207}
208
209status_t MidiFile::start()
210{
211 LOGV("MidiFile::start");
212 Mutex::Autolock lock(mMutex);
213 if (!mEasHandle) {
214 return ERROR_NOT_OPEN;
215 }
216
217 // resuming after pause?
218 if (mPaused) {
219 if (EAS_Resume(mEasData, mEasHandle) != EAS_SUCCESS) {
220 return ERROR_EAS_FAILURE;
221 }
222 mPaused = false;
223 updateState();
224 }
225
226 mRender = true;
227
228 // wake up render thread
229 LOGV(" wakeup render thread");
230 mCondition.signal();
231 return NO_ERROR;
232}
233
234status_t MidiFile::stop()
235{
236 LOGV("MidiFile::stop");
237 Mutex::Autolock lock(mMutex);
238 if (!mEasHandle) {
239 return ERROR_NOT_OPEN;
240 }
241 if (!mPaused && (mState != EAS_STATE_STOPPED)) {
242 EAS_RESULT result = EAS_Pause(mEasData, mEasHandle);
243 if (result != EAS_SUCCESS) {
244 LOGE("EAS_Pause returned error %ld", result);
245 return ERROR_EAS_FAILURE;
246 }
247 }
248 mPaused = false;
249 return NO_ERROR;
250}
251
252status_t MidiFile::seekTo(int position)
253{
254 LOGV("MidiFile::seekTo %d", position);
255 // hold lock during EAS calls
256 {
257 Mutex::Autolock lock(mMutex);
258 if (!mEasHandle) {
259 return ERROR_NOT_OPEN;
260 }
261 EAS_RESULT result;
262 if ((result = EAS_Locate(mEasData, mEasHandle, position, false))
263 != EAS_SUCCESS)
264 {
265 LOGE("EAS_Locate returned %ld", result);
266 return ERROR_EAS_FAILURE;
267 }
268 EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
269 }
270 sendEvent(MEDIA_SEEK_COMPLETE);
271 return NO_ERROR;
272}
273
274status_t MidiFile::pause()
275{
276 LOGV("MidiFile::pause");
277 Mutex::Autolock lock(mMutex);
278 if (!mEasHandle) {
279 return ERROR_NOT_OPEN;
280 }
281 if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR;
282 if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) {
283 return ERROR_EAS_FAILURE;
284 }
285 mPaused = true;
286 return NO_ERROR;
287}
288
289bool MidiFile::isPlaying()
290{
291 LOGV("MidiFile::isPlaying, mState=%d", int(mState));
292 if (!mEasHandle || mPaused) return false;
293 return (mState == EAS_STATE_PLAY);
294}
295
296status_t MidiFile::getCurrentPosition(int* position)
297{
298 LOGV("MidiFile::getCurrentPosition");
299 if (!mEasHandle) {
300 LOGE("getCurrentPosition(): file not open");
301 return ERROR_NOT_OPEN;
302 }
303 if (mPlayTime < 0) {
304 LOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime);
305 return ERROR_EAS_FAILURE;
306 }
307 *position = mPlayTime;
308 return NO_ERROR;
309}
310
311status_t MidiFile::getDuration(int* duration)
312{
313
314 LOGV("MidiFile::getDuration");
315 {
316 Mutex::Autolock lock(mMutex);
317 if (!mEasHandle) return ERROR_NOT_OPEN;
318 *duration = mDuration;
319 }
320
321 // if no duration cached, get the duration
322 // don't need a lock here because we spin up a new engine
323 if (*duration < 0) {
324 EAS_I32 temp;
325 EAS_DATA_HANDLE easData = NULL;
326 EAS_HANDLE easHandle = NULL;
327 EAS_RESULT result = EAS_Init(&easData);
328 if (result == EAS_SUCCESS) {
329 result = EAS_OpenFile(easData, &mFileLocator, &easHandle);
330 }
331 if (result == EAS_SUCCESS) {
332 result = EAS_Prepare(easData, easHandle);
333 }
334 if (result == EAS_SUCCESS) {
335 result = EAS_ParseMetaData(easData, easHandle, &temp);
336 }
337 if (easHandle) {
338 EAS_CloseFile(easData, easHandle);
339 }
340 if (easData) {
341 EAS_Shutdown(easData);
342 }
343
344 if (result != EAS_SUCCESS) {
345 return ERROR_EAS_FAILURE;
346 }
347
348 // cache successful result
349 mDuration = *duration = int(temp);
350 }
351
352 return NO_ERROR;
353}
354
355status_t MidiFile::release()
356{
357 LOGV("MidiFile::release");
358 Mutex::Autolock l(mMutex);
359 reset_nosync();
360
361 // wait for render thread to exit
362 mExit = true;
363 mCondition.signal();
364
365 // wait for thread to exit
366 if (mAudioBuffer) {
367 mCondition.wait(mMutex);
368 }
369
370 // release resources
371 if (mEasData) {
372 EAS_Shutdown(mEasData);
373 mEasData = NULL;
374 }
375 return NO_ERROR;
376}
377
378status_t MidiFile::reset()
379{
380 LOGV("MidiFile::reset");
381 Mutex::Autolock lock(mMutex);
382 return reset_nosync();
383}
384
385// call only with mutex held
386status_t MidiFile::reset_nosync()
387{
388 LOGV("MidiFile::reset_nosync");
389 // close file
390 if (mEasHandle) {
391 EAS_CloseFile(mEasData, mEasHandle);
392 mEasHandle = NULL;
393 }
394 if (mFileLocator.path) {
395 free((void*)mFileLocator.path);
396 mFileLocator.path = NULL;
397 }
398 if (mFileLocator.fd >= 0) {
399 close(mFileLocator.fd);
400 }
401 mFileLocator.fd = -1;
402 mFileLocator.offset = 0;
403 mFileLocator.length = 0;
404
405 mPlayTime = -1;
406 mDuration = -1;
407 mLoop = false;
408 mPaused = false;
409 mRender = false;
410 return NO_ERROR;
411}
412
413status_t MidiFile::setLooping(int loop)
414{
415 LOGV("MidiFile::setLooping");
416 Mutex::Autolock lock(mMutex);
417 if (!mEasHandle) {
418 return ERROR_NOT_OPEN;
419 }
420 loop = loop ? -1 : 0;
421 if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) {
422 return ERROR_EAS_FAILURE;
423 }
424 return NO_ERROR;
425}
426
427status_t MidiFile::createOutputTrack() {
Dima Zavin24fc2fb2011-04-19 22:30:36 -0700428 if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels, AUDIO_FORMAT_PCM_16_BIT, 2) != NO_ERROR) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800429 LOGE("mAudioSink open failed");
430 return ERROR_OPEN_FAILED;
431 }
432 return NO_ERROR;
433}
434
435int MidiFile::renderThread(void* p) {
436
437 return ((MidiFile*)p)->render();
438}
439
440int MidiFile::render() {
441 EAS_RESULT result = EAS_FAILURE;
442 EAS_I32 count;
443 int temp;
444 bool audioStarted = false;
445
446 LOGV("MidiFile::render");
447
448 // allocate render buffer
449 mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS];
450 if (!mAudioBuffer) {
451 LOGE("mAudioBuffer allocate failed");
452 goto threadExit;
453 }
454
455 // signal main thread that we started
456 {
457 Mutex::Autolock l(mMutex);
458 mTid = myTid();
459 LOGV("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 LOGV("MidiFile::render - signal wait");
470 mCondition.wait(mMutex);
471 LOGV("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 //LOGV("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 LOGE("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 // LOGV("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 LOGV("MidiFile::render - create output track");
500 if (createOutputTrack() != NO_ERROR)
501 goto threadExit;
502 }
503
504 // Write data to the audio hardware
505 // LOGV("MidiFile::render - writing to audio output");
506 if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) {
507 LOGE("Error in writing:%d",temp);
508 return temp;
509 }
510
511 // start audio output if necessary
512 if (!audioStarted) {
513 //LOGV("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 LOGV("MidiFile::render - stopped");
526 sendEvent(MEDIA_PLAYBACK_COMPLETE);
527 break;
528 }
529 case EAS_STATE_ERROR:
530 {
531 LOGE("MidiFile::render - error");
532 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN);
533 break;
534 }
535 case EAS_STATE_PAUSED:
536 LOGV("MidiFile::render - paused");
537 break;
538 default:
539 break;
540 }
541 mAudioSink->stop();
542 audioStarted = false;
543 mRender = false;
544 }
545 }
546
547threadExit:
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