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