MidiFile.cpp revision 5f3194c9efeadc200c89d9e2b00a19f1afeac55d
125b3c049e70834cf33790a28643ab058b507b35cBen Cheng/* MidiFile.cpp 225b3c049e70834cf33790a28643ab058b507b35cBen Cheng** 325b3c049e70834cf33790a28643ab058b507b35cBen Cheng** Copyright 2007, The Android Open Source Project 425b3c049e70834cf33790a28643ab058b507b35cBen Cheng** 525b3c049e70834cf33790a28643ab058b507b35cBen Cheng** Licensed under the Apache License, Version 2.0 (the "License"); 625b3c049e70834cf33790a28643ab058b507b35cBen Cheng** you may not use this file except in compliance with the License. 725b3c049e70834cf33790a28643ab058b507b35cBen Cheng** You may obtain a copy of the License at 825b3c049e70834cf33790a28643ab058b507b35cBen Cheng** 925b3c049e70834cf33790a28643ab058b507b35cBen Cheng** http://www.apache.org/licenses/LICENSE-2.0 1025b3c049e70834cf33790a28643ab058b507b35cBen Cheng** 1125b3c049e70834cf33790a28643ab058b507b35cBen Cheng** Unless required by applicable law or agreed to in writing, software 1225b3c049e70834cf33790a28643ab058b507b35cBen Cheng** distributed under the License is distributed on an "AS IS" BASIS, 1325b3c049e70834cf33790a28643ab058b507b35cBen Cheng** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1425b3c049e70834cf33790a28643ab058b507b35cBen Cheng** See the License for the specific language governing permissions and 1525b3c049e70834cf33790a28643ab058b507b35cBen Cheng** limitations under the License. 1625b3c049e70834cf33790a28643ab058b507b35cBen Cheng*/ 1725b3c049e70834cf33790a28643ab058b507b35cBen Cheng 1825b3c049e70834cf33790a28643ab058b507b35cBen Cheng//#define LOG_NDEBUG 0 1925b3c049e70834cf33790a28643ab058b507b35cBen Cheng#define LOG_TAG "MidiFile" 2025b3c049e70834cf33790a28643ab058b507b35cBen Cheng#include "utils/Log.h" 2125b3c049e70834cf33790a28643ab058b507b35cBen Cheng 2225b3c049e70834cf33790a28643ab058b507b35cBen Cheng#include <stdio.h> 2325b3c049e70834cf33790a28643ab058b507b35cBen Cheng#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 40namespace android { 41 42// ---------------------------------------------------------------------------- 43 44// The midi engine buffers are a bit small (128 frames), so we batch them up 45static const int NUM_BUFFERS = 4; 46 47// TODO: Determine appropriate return codes 48static status_t ERROR_NOT_OPEN = -1; 49static status_t ERROR_OPEN_FAILED = -2; 50static status_t ERROR_EAS_FAILURE = -3; 51static status_t ERROR_ALLOCATE_FAILED = -4; 52 53static const S_EAS_LIB_CONFIG* pLibConfig = NULL; 54 55MidiFile::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 101Failed: 102 return; 103} 104 105status_t MidiFile::initCheck() 106{ 107 if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE; 108 return NO_ERROR; 109} 110 111MidiFile::~MidiFile() { 112 ALOGV("MidiFile destructor"); 113 release(); 114} 115 116status_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 149status_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 177status_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 193status_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 207status_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 235status_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 254status_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 276status_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 292bool MidiFile::isPlaying() 293{ 294 ALOGV("MidiFile::isPlaying, mState=%d", int(mState)); 295 if (!mEasHandle || mPaused) return false; 296 return (mState == EAS_STATE_PLAY || (mState == EAS_STATE_READY && mRender)); 297} 298 299status_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 314status_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 358status_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 381status_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 389status_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 417status_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 431status_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 440int 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 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 561