blob: 1b0b05f92b4e3f5e49ee506574d540a904a0f8db [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
33#include "MidiFile.h"
34
35#ifdef HAVE_GETTID
36static pid_t myTid() { return gettid(); }
37#else
38static pid_t myTid() { return getpid(); }
39#endif
40
41// ----------------------------------------------------------------------------
42
43namespace android {
44
45// ----------------------------------------------------------------------------
46
47// The midi engine buffers are a bit small (128 frames), so we batch them up
48static const int NUM_BUFFERS = 4;
49
50// TODO: Determine appropriate return codes
51static status_t ERROR_NOT_OPEN = -1;
52static status_t ERROR_OPEN_FAILED = -2;
53static status_t ERROR_EAS_FAILURE = -3;
54static status_t ERROR_ALLOCATE_FAILED = -4;
55
56static const S_EAS_LIB_CONFIG* pLibConfig = NULL;
57
58MidiFile::MidiFile() :
59 mEasData(NULL), mEasHandle(NULL), mAudioBuffer(NULL),
60 mPlayTime(-1), mDuration(-1), mState(EAS_STATE_ERROR),
61 mStreamType(AudioSystem::MUSIC), mLoop(false), mExit(false),
62 mPaused(false), mRender(false), mTid(-1)
63{
64 LOGV("constructor");
65
66 mFileLocator.path = NULL;
67 mFileLocator.fd = -1;
68 mFileLocator.offset = 0;
69 mFileLocator.length = 0;
70
71 // get the library configuration and do sanity check
72 if (pLibConfig == NULL)
73 pLibConfig = EAS_Config();
74 if ((pLibConfig == NULL) || (LIB_VERSION != pLibConfig->libVersion)) {
75 LOGE("EAS library/header mismatch");
76 goto Failed;
77 }
78
79 // initialize EAS library
80 if (EAS_Init(&mEasData) != EAS_SUCCESS) {
81 LOGE("EAS_Init failed");
82 goto Failed;
83 }
84
85 // select reverb preset and enable
86 EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER);
87 EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE);
88
89 // create playback thread
90 {
91 Mutex::Autolock l(mMutex);
Dave Sparksc0827692009-04-29 12:59:33 -070092 createThreadEtc(renderThread, this, "midithread", ANDROID_PRIORITY_AUDIO);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093 mCondition.wait(mMutex);
94 LOGV("thread started");
95 }
96
97 // indicate success
98 if (mTid > 0) {
99 LOGV(" render thread(%d) started", mTid);
100 mState = EAS_STATE_READY;
101 }
102
103Failed:
104 return;
105}
106
107status_t MidiFile::initCheck()
108{
109 if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE;
110 return NO_ERROR;
111}
112
113MidiFile::~MidiFile() {
114 LOGV("MidiFile destructor");
115 release();
116}
117
Andreas Huber25643002010-01-28 11:19:57 -0800118status_t MidiFile::setDataSource(
119 const char* path, const KeyedVector<String8, String8> *) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 LOGV("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 LOGE("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
149status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length)
150{
151 LOGV("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 LOGE("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
177status_t MidiFile::prepare()
178{
179 LOGV("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 LOGE("EAS_Prepare failed: [%ld]", result);
187 return ERROR_EAS_FAILURE;
188 }
189 updateState();
190 return NO_ERROR;
191}
192
193status_t MidiFile::prepareAsync()
194{
195 LOGV("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
207status_t MidiFile::start()
208{
209 LOGV("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
226 // wake up render thread
227 LOGV(" wakeup render thread");
228 mCondition.signal();
229 return NO_ERROR;
230}
231
232status_t MidiFile::stop()
233{
234 LOGV("MidiFile::stop");
235 Mutex::Autolock lock(mMutex);
236 if (!mEasHandle) {
237 return ERROR_NOT_OPEN;
238 }
239 if (!mPaused && (mState != EAS_STATE_STOPPED)) {
240 EAS_RESULT result = EAS_Pause(mEasData, mEasHandle);
241 if (result != EAS_SUCCESS) {
242 LOGE("EAS_Pause returned error %ld", result);
243 return ERROR_EAS_FAILURE;
244 }
245 }
246 mPaused = false;
247 return NO_ERROR;
248}
249
250status_t MidiFile::seekTo(int position)
251{
252 LOGV("MidiFile::seekTo %d", position);
253 // hold lock during EAS calls
254 {
255 Mutex::Autolock lock(mMutex);
256 if (!mEasHandle) {
257 return ERROR_NOT_OPEN;
258 }
259 EAS_RESULT result;
260 if ((result = EAS_Locate(mEasData, mEasHandle, position, false))
261 != EAS_SUCCESS)
262 {
263 LOGE("EAS_Locate returned %ld", result);
264 return ERROR_EAS_FAILURE;
265 }
266 EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
267 }
268 sendEvent(MEDIA_SEEK_COMPLETE);
269 return NO_ERROR;
270}
271
272status_t MidiFile::pause()
273{
274 LOGV("MidiFile::pause");
275 Mutex::Autolock lock(mMutex);
276 if (!mEasHandle) {
277 return ERROR_NOT_OPEN;
278 }
279 if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR;
280 if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) {
281 return ERROR_EAS_FAILURE;
282 }
283 mPaused = true;
284 return NO_ERROR;
285}
286
287bool MidiFile::isPlaying()
288{
289 LOGV("MidiFile::isPlaying, mState=%d", int(mState));
290 if (!mEasHandle || mPaused) return false;
291 return (mState == EAS_STATE_PLAY);
292}
293
294status_t MidiFile::getCurrentPosition(int* position)
295{
296 LOGV("MidiFile::getCurrentPosition");
297 if (!mEasHandle) {
298 LOGE("getCurrentPosition(): file not open");
299 return ERROR_NOT_OPEN;
300 }
301 if (mPlayTime < 0) {
302 LOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime);
303 return ERROR_EAS_FAILURE;
304 }
305 *position = mPlayTime;
306 return NO_ERROR;
307}
308
309status_t MidiFile::getDuration(int* duration)
310{
311
312 LOGV("MidiFile::getDuration");
313 {
314 Mutex::Autolock lock(mMutex);
315 if (!mEasHandle) return ERROR_NOT_OPEN;
316 *duration = mDuration;
317 }
318
319 // if no duration cached, get the duration
320 // don't need a lock here because we spin up a new engine
321 if (*duration < 0) {
322 EAS_I32 temp;
323 EAS_DATA_HANDLE easData = NULL;
324 EAS_HANDLE easHandle = NULL;
325 EAS_RESULT result = EAS_Init(&easData);
326 if (result == EAS_SUCCESS) {
327 result = EAS_OpenFile(easData, &mFileLocator, &easHandle);
328 }
329 if (result == EAS_SUCCESS) {
330 result = EAS_Prepare(easData, easHandle);
331 }
332 if (result == EAS_SUCCESS) {
333 result = EAS_ParseMetaData(easData, easHandle, &temp);
334 }
335 if (easHandle) {
336 EAS_CloseFile(easData, easHandle);
337 }
338 if (easData) {
339 EAS_Shutdown(easData);
340 }
341
342 if (result != EAS_SUCCESS) {
343 return ERROR_EAS_FAILURE;
344 }
345
346 // cache successful result
347 mDuration = *duration = int(temp);
348 }
349
350 return NO_ERROR;
351}
352
353status_t MidiFile::release()
354{
355 LOGV("MidiFile::release");
356 Mutex::Autolock l(mMutex);
357 reset_nosync();
358
359 // wait for render thread to exit
360 mExit = true;
361 mCondition.signal();
362
363 // wait for thread to exit
364 if (mAudioBuffer) {
365 mCondition.wait(mMutex);
366 }
367
368 // release resources
369 if (mEasData) {
370 EAS_Shutdown(mEasData);
371 mEasData = NULL;
372 }
373 return NO_ERROR;
374}
375
376status_t MidiFile::reset()
377{
378 LOGV("MidiFile::reset");
379 Mutex::Autolock lock(mMutex);
380 return reset_nosync();
381}
382
383// call only with mutex held
384status_t MidiFile::reset_nosync()
385{
386 LOGV("MidiFile::reset_nosync");
387 // close file
388 if (mEasHandle) {
389 EAS_CloseFile(mEasData, mEasHandle);
390 mEasHandle = NULL;
391 }
392 if (mFileLocator.path) {
393 free((void*)mFileLocator.path);
394 mFileLocator.path = NULL;
395 }
396 if (mFileLocator.fd >= 0) {
397 close(mFileLocator.fd);
398 }
399 mFileLocator.fd = -1;
400 mFileLocator.offset = 0;
401 mFileLocator.length = 0;
402
403 mPlayTime = -1;
404 mDuration = -1;
405 mLoop = false;
406 mPaused = false;
407 mRender = false;
408 return NO_ERROR;
409}
410
411status_t MidiFile::setLooping(int loop)
412{
413 LOGV("MidiFile::setLooping");
414 Mutex::Autolock lock(mMutex);
415 if (!mEasHandle) {
416 return ERROR_NOT_OPEN;
417 }
418 loop = loop ? -1 : 0;
419 if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) {
420 return ERROR_EAS_FAILURE;
421 }
422 return NO_ERROR;
423}
424
425status_t MidiFile::createOutputTrack() {
426 if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels, AudioSystem::PCM_16_BIT, 2) != NO_ERROR) {
427 LOGE("mAudioSink open failed");
428 return ERROR_OPEN_FAILED;
429 }
430 return NO_ERROR;
431}
432
433int MidiFile::renderThread(void* p) {
434
435 return ((MidiFile*)p)->render();
436}
437
438int MidiFile::render() {
439 EAS_RESULT result = EAS_FAILURE;
440 EAS_I32 count;
441 int temp;
442 bool audioStarted = false;
443
444 LOGV("MidiFile::render");
445
446 // allocate render buffer
447 mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS];
448 if (!mAudioBuffer) {
449 LOGE("mAudioBuffer allocate failed");
450 goto threadExit;
451 }
452
453 // signal main thread that we started
454 {
455 Mutex::Autolock l(mMutex);
456 mTid = myTid();
457 LOGV("render thread(%d) signal", mTid);
458 mCondition.signal();
459 }
460
461 while (1) {
462 mMutex.lock();
463
464 // nothing to render, wait for client thread to wake us up
465 while (!mRender && !mExit)
466 {
467 LOGV("MidiFile::render - signal wait");
468 mCondition.wait(mMutex);
469 LOGV("MidiFile::render - signal rx'd");
470 }
471 if (mExit) {
472 mMutex.unlock();
473 break;
474 }
475
476 // render midi data into the input buffer
477 //LOGV("MidiFile::render - rendering audio");
478 int num_output = 0;
479 EAS_PCM* p = mAudioBuffer;
480 for (int i = 0; i < NUM_BUFFERS; i++) {
481 result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count);
482 if (result != EAS_SUCCESS) {
483 LOGE("EAS_Render returned %ld", result);
484 }
485 p += count * pLibConfig->numChannels;
486 num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM);
487 }
488
489 // update playback state and position
490 // LOGV("MidiFile::render - updating state");
491 EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
492 EAS_State(mEasData, mEasHandle, &mState);
493 mMutex.unlock();
494
495 // create audio output track if necessary
496 if (!mAudioSink->ready()) {
497 LOGV("MidiFile::render - create output track");
498 if (createOutputTrack() != NO_ERROR)
499 goto threadExit;
500 }
501
502 // Write data to the audio hardware
503 // LOGV("MidiFile::render - writing to audio output");
504 if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) {
505 LOGE("Error in writing:%d",temp);
506 return temp;
507 }
508
509 // start audio output if necessary
510 if (!audioStarted) {
511 //LOGV("MidiFile::render - starting audio");
512 mAudioSink->start();
513 audioStarted = true;
514 }
515
516 // still playing?
517 if ((mState == EAS_STATE_STOPPED) || (mState == EAS_STATE_ERROR) ||
518 (mState == EAS_STATE_PAUSED))
519 {
520 switch(mState) {
521 case EAS_STATE_STOPPED:
522 {
523 LOGV("MidiFile::render - stopped");
524 sendEvent(MEDIA_PLAYBACK_COMPLETE);
525 break;
526 }
527 case EAS_STATE_ERROR:
528 {
529 LOGE("MidiFile::render - error");
530 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN);
531 break;
532 }
533 case EAS_STATE_PAUSED:
534 LOGV("MidiFile::render - paused");
535 break;
536 default:
537 break;
538 }
539 mAudioSink->stop();
540 audioStarted = false;
541 mRender = false;
542 }
543 }
544
545threadExit:
546 mAudioSink.clear();
547 if (mAudioBuffer) {
548 delete [] mAudioBuffer;
549 mAudioBuffer = NULL;
550 }
551 mMutex.lock();
552 mTid = -1;
553 mCondition.signal();
554 mMutex.unlock();
555 return result;
556}
557
558} // end namespace android