MidiFile.cpp revision 5e07b5774c8b376776caa4f5b0a193767697e97e
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 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(AudioTrack::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); 92 createThreadEtc(renderThread, this, "midithread"); 93 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 118status_t MidiFile::setDataSource(const char* path) 119{ 120 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 559