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