MidiFile.cpp revision cbaffcffee6418d678806e63097c19fe26d48fe0
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 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 char* path, const KeyedVector<String8, String8> *) { 118 ALOGV("MidiFile::setDataSource url=%s", path); 119 Mutex::Autolock lock(mMutex); 120 121 // file still open? 122 if (mEasHandle) { 123 reset_nosync(); 124 } 125 126 // open file and set paused state 127 mFileLocator.path = strdup(path); 128 mFileLocator.fd = -1; 129 mFileLocator.offset = 0; 130 mFileLocator.length = 0; 131 EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle); 132 if (result == EAS_SUCCESS) { 133 updateState(); 134 } 135 136 if (result != EAS_SUCCESS) { 137 ALOGE("EAS_OpenFile failed: [%d]", (int)result); 138 mState = EAS_STATE_ERROR; 139 return ERROR_OPEN_FAILED; 140 } 141 142 mState = EAS_STATE_OPEN; 143 mPlayTime = 0; 144 return NO_ERROR; 145} 146 147status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length) 148{ 149 ALOGV("MidiFile::setDataSource fd=%d", fd); 150 Mutex::Autolock lock(mMutex); 151 152 // file still open? 153 if (mEasHandle) { 154 reset_nosync(); 155 } 156 157 // open file and set paused state 158 mFileLocator.fd = dup(fd); 159 mFileLocator.offset = offset; 160 mFileLocator.length = length; 161 EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle); 162 updateState(); 163 164 if (result != EAS_SUCCESS) { 165 ALOGE("EAS_OpenFile failed: [%d]", (int)result); 166 mState = EAS_STATE_ERROR; 167 return ERROR_OPEN_FAILED; 168 } 169 170 mState = EAS_STATE_OPEN; 171 mPlayTime = 0; 172 return NO_ERROR; 173} 174 175status_t MidiFile::prepare() 176{ 177 ALOGV("MidiFile::prepare"); 178 Mutex::Autolock lock(mMutex); 179 if (!mEasHandle) { 180 return ERROR_NOT_OPEN; 181 } 182 EAS_RESULT result; 183 if ((result = EAS_Prepare(mEasData, mEasHandle)) != EAS_SUCCESS) { 184 ALOGE("EAS_Prepare failed: [%ld]", result); 185 return ERROR_EAS_FAILURE; 186 } 187 updateState(); 188 return NO_ERROR; 189} 190 191status_t MidiFile::prepareAsync() 192{ 193 ALOGV("MidiFile::prepareAsync"); 194 status_t ret = prepare(); 195 196 // don't hold lock during callback 197 if (ret == NO_ERROR) { 198 sendEvent(MEDIA_PREPARED); 199 } else { 200 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, ret); 201 } 202 return ret; 203} 204 205status_t MidiFile::start() 206{ 207 ALOGV("MidiFile::start"); 208 Mutex::Autolock lock(mMutex); 209 if (!mEasHandle) { 210 return ERROR_NOT_OPEN; 211 } 212 213 // resuming after pause? 214 if (mPaused) { 215 if (EAS_Resume(mEasData, mEasHandle) != EAS_SUCCESS) { 216 return ERROR_EAS_FAILURE; 217 } 218 mPaused = false; 219 updateState(); 220 } 221 222 mRender = true; 223 if (mState == EAS_STATE_PLAY) { 224 sendEvent(MEDIA_STARTED); 225 } 226 227 // wake up render thread 228 ALOGV(" wakeup render thread"); 229 mCondition.signal(); 230 return NO_ERROR; 231} 232 233status_t MidiFile::stop() 234{ 235 ALOGV("MidiFile::stop"); 236 Mutex::Autolock lock(mMutex); 237 if (!mEasHandle) { 238 return ERROR_NOT_OPEN; 239 } 240 if (!mPaused && (mState != EAS_STATE_STOPPED)) { 241 EAS_RESULT result = EAS_Pause(mEasData, mEasHandle); 242 if (result != EAS_SUCCESS) { 243 ALOGE("EAS_Pause returned error %ld", result); 244 return ERROR_EAS_FAILURE; 245 } 246 } 247 mPaused = false; 248 sendEvent(MEDIA_STOPPED); 249 return NO_ERROR; 250} 251 252status_t MidiFile::seekTo(int position) 253{ 254 ALOGV("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 ALOGE("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 ALOGV("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 sendEvent(MEDIA_PAUSED); 287 return NO_ERROR; 288} 289 290bool MidiFile::isPlaying() 291{ 292 ALOGV("MidiFile::isPlaying, mState=%d", int(mState)); 293 if (!mEasHandle || mPaused) return false; 294 return (mState == EAS_STATE_PLAY); 295} 296 297status_t MidiFile::getCurrentPosition(int* position) 298{ 299 ALOGV("MidiFile::getCurrentPosition"); 300 if (!mEasHandle) { 301 ALOGE("getCurrentPosition(): file not open"); 302 return ERROR_NOT_OPEN; 303 } 304 if (mPlayTime < 0) { 305 ALOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime); 306 return ERROR_EAS_FAILURE; 307 } 308 *position = mPlayTime; 309 return NO_ERROR; 310} 311 312status_t MidiFile::getDuration(int* duration) 313{ 314 315 ALOGV("MidiFile::getDuration"); 316 { 317 Mutex::Autolock lock(mMutex); 318 if (!mEasHandle) return ERROR_NOT_OPEN; 319 *duration = mDuration; 320 } 321 322 // if no duration cached, get the duration 323 // don't need a lock here because we spin up a new engine 324 if (*duration < 0) { 325 EAS_I32 temp; 326 EAS_DATA_HANDLE easData = NULL; 327 EAS_HANDLE easHandle = NULL; 328 EAS_RESULT result = EAS_Init(&easData); 329 if (result == EAS_SUCCESS) { 330 result = EAS_OpenFile(easData, &mFileLocator, &easHandle); 331 } 332 if (result == EAS_SUCCESS) { 333 result = EAS_Prepare(easData, easHandle); 334 } 335 if (result == EAS_SUCCESS) { 336 result = EAS_ParseMetaData(easData, easHandle, &temp); 337 } 338 if (easHandle) { 339 EAS_CloseFile(easData, easHandle); 340 } 341 if (easData) { 342 EAS_Shutdown(easData); 343 } 344 345 if (result != EAS_SUCCESS) { 346 return ERROR_EAS_FAILURE; 347 } 348 349 // cache successful result 350 mDuration = *duration = int(temp); 351 } 352 353 return NO_ERROR; 354} 355 356status_t MidiFile::release() 357{ 358 ALOGV("MidiFile::release"); 359 Mutex::Autolock l(mMutex); 360 reset_nosync(); 361 362 // wait for render thread to exit 363 mExit = true; 364 mCondition.signal(); 365 366 // wait for thread to exit 367 if (mAudioBuffer) { 368 mCondition.wait(mMutex); 369 } 370 371 // release resources 372 if (mEasData) { 373 EAS_Shutdown(mEasData); 374 mEasData = NULL; 375 } 376 return NO_ERROR; 377} 378 379status_t MidiFile::reset() 380{ 381 ALOGV("MidiFile::reset"); 382 Mutex::Autolock lock(mMutex); 383 return reset_nosync(); 384} 385 386// call only with mutex held 387status_t MidiFile::reset_nosync() 388{ 389 ALOGV("MidiFile::reset_nosync"); 390 sendEvent(MEDIA_STOPPED); 391 // close file 392 if (mEasHandle) { 393 EAS_CloseFile(mEasData, mEasHandle); 394 mEasHandle = NULL; 395 } 396 if (mFileLocator.path) { 397 free((void*)mFileLocator.path); 398 mFileLocator.path = NULL; 399 } 400 if (mFileLocator.fd >= 0) { 401 close(mFileLocator.fd); 402 } 403 mFileLocator.fd = -1; 404 mFileLocator.offset = 0; 405 mFileLocator.length = 0; 406 407 mPlayTime = -1; 408 mDuration = -1; 409 mLoop = false; 410 mPaused = false; 411 mRender = false; 412 return NO_ERROR; 413} 414 415status_t MidiFile::setLooping(int loop) 416{ 417 ALOGV("MidiFile::setLooping"); 418 Mutex::Autolock lock(mMutex); 419 if (!mEasHandle) { 420 return ERROR_NOT_OPEN; 421 } 422 loop = loop ? -1 : 0; 423 if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) { 424 return ERROR_EAS_FAILURE; 425 } 426 return NO_ERROR; 427} 428 429status_t MidiFile::createOutputTrack() { 430 if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels, 431 CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 2 /*bufferCount*/) != NO_ERROR) { 432 ALOGE("mAudioSink open failed"); 433 return ERROR_OPEN_FAILED; 434 } 435 return NO_ERROR; 436} 437 438int MidiFile::render() { 439 EAS_RESULT result = EAS_FAILURE; 440 EAS_I32 count; 441 int temp; 442 bool audioStarted = false; 443 444 ALOGV("MidiFile::render"); 445 446 // allocate render buffer 447 mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS]; 448 if (!mAudioBuffer) { 449 ALOGE("mAudioBuffer allocate failed"); 450 goto threadExit; 451 } 452 453 // signal main thread that we started 454 { 455 Mutex::Autolock l(mMutex); 456 mTid = gettid(); 457 ALOGV("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 ALOGV("MidiFile::render - signal wait"); 468 mCondition.wait(mMutex); 469 ALOGV("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 //ALOGV("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 ALOGE("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 // ALOGV("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 ALOGV("MidiFile::render - create output track"); 498 if (createOutputTrack() != NO_ERROR) 499 goto threadExit; 500 } 501 502 // Write data to the audio hardware 503 // ALOGV("MidiFile::render - writing to audio output"); 504 if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) { 505 ALOGE("Error in writing:%d",temp); 506 return temp; 507 } 508 509 // start audio output if necessary 510 if (!audioStarted) { 511 //ALOGV("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 ALOGV("MidiFile::render - stopped"); 524 sendEvent(MEDIA_PLAYBACK_COMPLETE); 525 break; 526 } 527 case EAS_STATE_ERROR: 528 { 529 ALOGE("MidiFile::render - error"); 530 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN); 531 break; 532 } 533 case EAS_STATE_PAUSED: 534 ALOGV("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 559