/* MidiFile.cpp ** ** Copyright 2007, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "MidiFile" #include "utils/Log.h" #include <stdio.h> #include <assert.h> #include <limits.h> #include <unistd.h> #include <fcntl.h> #include <sched.h> #include <utils/threads.h> #include <libsonivox/eas_reverb.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <system/audio.h> #include "MidiFile.h" // ---------------------------------------------------------------------------- namespace android { // ---------------------------------------------------------------------------- // The midi engine buffers are a bit small (128 frames), so we batch them up static const int NUM_BUFFERS = 4; // TODO: Determine appropriate return codes static status_t ERROR_NOT_OPEN = -1; static status_t ERROR_OPEN_FAILED = -2; static status_t ERROR_EAS_FAILURE = -3; static status_t ERROR_ALLOCATE_FAILED = -4; static const S_EAS_LIB_CONFIG* pLibConfig = NULL; MidiFile::MidiFile() : mEasData(NULL), mEasHandle(NULL), mAudioBuffer(NULL), mPlayTime(-1), mDuration(-1), mState(EAS_STATE_ERROR), mStreamType(AUDIO_STREAM_MUSIC), mLoop(false), mExit(false), mPaused(false), mRender(false), mTid(-1) { ALOGV("constructor"); mFileLocator.path = NULL; mFileLocator.fd = -1; mFileLocator.offset = 0; mFileLocator.length = 0; // get the library configuration and do sanity check if (pLibConfig == NULL) pLibConfig = EAS_Config(); if ((pLibConfig == NULL) || (LIB_VERSION != pLibConfig->libVersion)) { ALOGE("EAS library/header mismatch"); goto Failed; } // initialize EAS library if (EAS_Init(&mEasData) != EAS_SUCCESS) { ALOGE("EAS_Init failed"); goto Failed; } // select reverb preset and enable EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER); EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE); // create playback thread { Mutex::Autolock l(mMutex); mThread = new MidiFileThread(this); mThread->run("midithread", ANDROID_PRIORITY_AUDIO); mCondition.wait(mMutex); ALOGV("thread started"); } // indicate success if (mTid > 0) { ALOGV(" render thread(%d) started", mTid); mState = EAS_STATE_READY; } Failed: return; } status_t MidiFile::initCheck() { if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE; return NO_ERROR; } MidiFile::~MidiFile() { ALOGV("MidiFile destructor"); release(); } status_t MidiFile::setDataSource( const char* path, const KeyedVector<String8, String8> *) { ALOGV("MidiFile::setDataSource url=%s", path); Mutex::Autolock lock(mMutex); // file still open? if (mEasHandle) { reset_nosync(); } // open file and set paused state mFileLocator.path = strdup(path); mFileLocator.fd = -1; mFileLocator.offset = 0; mFileLocator.length = 0; EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle); if (result == EAS_SUCCESS) { updateState(); } if (result != EAS_SUCCESS) { ALOGE("EAS_OpenFile failed: [%d]", (int)result); mState = EAS_STATE_ERROR; return ERROR_OPEN_FAILED; } mState = EAS_STATE_OPEN; mPlayTime = 0; return NO_ERROR; } status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length) { ALOGV("MidiFile::setDataSource fd=%d", fd); Mutex::Autolock lock(mMutex); // file still open? if (mEasHandle) { reset_nosync(); } // open file and set paused state mFileLocator.fd = dup(fd); mFileLocator.offset = offset; mFileLocator.length = length; EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle); updateState(); if (result != EAS_SUCCESS) { ALOGE("EAS_OpenFile failed: [%d]", (int)result); mState = EAS_STATE_ERROR; return ERROR_OPEN_FAILED; } mState = EAS_STATE_OPEN; mPlayTime = 0; return NO_ERROR; } status_t MidiFile::prepare() { ALOGV("MidiFile::prepare"); Mutex::Autolock lock(mMutex); if (!mEasHandle) { return ERROR_NOT_OPEN; } EAS_RESULT result; if ((result = EAS_Prepare(mEasData, mEasHandle)) != EAS_SUCCESS) { ALOGE("EAS_Prepare failed: [%ld]", result); return ERROR_EAS_FAILURE; } updateState(); return NO_ERROR; } status_t MidiFile::prepareAsync() { ALOGV("MidiFile::prepareAsync"); status_t ret = prepare(); // don't hold lock during callback if (ret == NO_ERROR) { sendEvent(MEDIA_PREPARED); } else { sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, ret); } return ret; } status_t MidiFile::start() { ALOGV("MidiFile::start"); Mutex::Autolock lock(mMutex); if (!mEasHandle) { return ERROR_NOT_OPEN; } // resuming after pause? if (mPaused) { if (EAS_Resume(mEasData, mEasHandle) != EAS_SUCCESS) { return ERROR_EAS_FAILURE; } mPaused = false; updateState(); } mRender = true; if (mState == EAS_STATE_PLAY) { sendEvent(MEDIA_STARTED); } // wake up render thread ALOGV(" wakeup render thread"); mCondition.signal(); return NO_ERROR; } status_t MidiFile::stop() { ALOGV("MidiFile::stop"); Mutex::Autolock lock(mMutex); if (!mEasHandle) { return ERROR_NOT_OPEN; } if (!mPaused && (mState != EAS_STATE_STOPPED)) { EAS_RESULT result = EAS_Pause(mEasData, mEasHandle); if (result != EAS_SUCCESS) { ALOGE("EAS_Pause returned error %ld", result); return ERROR_EAS_FAILURE; } } mPaused = false; sendEvent(MEDIA_STOPPED); return NO_ERROR; } status_t MidiFile::seekTo(int position) { ALOGV("MidiFile::seekTo %d", position); // hold lock during EAS calls { Mutex::Autolock lock(mMutex); if (!mEasHandle) { return ERROR_NOT_OPEN; } EAS_RESULT result; if ((result = EAS_Locate(mEasData, mEasHandle, position, false)) != EAS_SUCCESS) { ALOGE("EAS_Locate returned %ld", result); return ERROR_EAS_FAILURE; } EAS_GetLocation(mEasData, mEasHandle, &mPlayTime); } sendEvent(MEDIA_SEEK_COMPLETE); return NO_ERROR; } status_t MidiFile::pause() { ALOGV("MidiFile::pause"); Mutex::Autolock lock(mMutex); if (!mEasHandle) { return ERROR_NOT_OPEN; } if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR; if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) { return ERROR_EAS_FAILURE; } mPaused = true; sendEvent(MEDIA_PAUSED); return NO_ERROR; } bool MidiFile::isPlaying() { ALOGV("MidiFile::isPlaying, mState=%d", int(mState)); if (!mEasHandle || mPaused) return false; return (mState == EAS_STATE_PLAY); } status_t MidiFile::getCurrentPosition(int* position) { ALOGV("MidiFile::getCurrentPosition"); if (!mEasHandle) { ALOGE("getCurrentPosition(): file not open"); return ERROR_NOT_OPEN; } if (mPlayTime < 0) { ALOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime); return ERROR_EAS_FAILURE; } *position = mPlayTime; return NO_ERROR; } status_t MidiFile::getDuration(int* duration) { ALOGV("MidiFile::getDuration"); { Mutex::Autolock lock(mMutex); if (!mEasHandle) return ERROR_NOT_OPEN; *duration = mDuration; } // if no duration cached, get the duration // don't need a lock here because we spin up a new engine if (*duration < 0) { EAS_I32 temp; EAS_DATA_HANDLE easData = NULL; EAS_HANDLE easHandle = NULL; EAS_RESULT result = EAS_Init(&easData); if (result == EAS_SUCCESS) { result = EAS_OpenFile(easData, &mFileLocator, &easHandle); } if (result == EAS_SUCCESS) { result = EAS_Prepare(easData, easHandle); } if (result == EAS_SUCCESS) { result = EAS_ParseMetaData(easData, easHandle, &temp); } if (easHandle) { EAS_CloseFile(easData, easHandle); } if (easData) { EAS_Shutdown(easData); } if (result != EAS_SUCCESS) { return ERROR_EAS_FAILURE; } // cache successful result mDuration = *duration = int(temp); } return NO_ERROR; } status_t MidiFile::release() { ALOGV("MidiFile::release"); Mutex::Autolock l(mMutex); reset_nosync(); // wait for render thread to exit mExit = true; mCondition.signal(); // wait for thread to exit if (mAudioBuffer) { mCondition.wait(mMutex); } // release resources if (mEasData) { EAS_Shutdown(mEasData); mEasData = NULL; } return NO_ERROR; } status_t MidiFile::reset() { ALOGV("MidiFile::reset"); Mutex::Autolock lock(mMutex); return reset_nosync(); } // call only with mutex held status_t MidiFile::reset_nosync() { ALOGV("MidiFile::reset_nosync"); sendEvent(MEDIA_STOPPED); // close file if (mEasHandle) { EAS_CloseFile(mEasData, mEasHandle); mEasHandle = NULL; } if (mFileLocator.path) { free((void*)mFileLocator.path); mFileLocator.path = NULL; } if (mFileLocator.fd >= 0) { close(mFileLocator.fd); } mFileLocator.fd = -1; mFileLocator.offset = 0; mFileLocator.length = 0; mPlayTime = -1; mDuration = -1; mLoop = false; mPaused = false; mRender = false; return NO_ERROR; } status_t MidiFile::setLooping(int loop) { ALOGV("MidiFile::setLooping"); Mutex::Autolock lock(mMutex); if (!mEasHandle) { return ERROR_NOT_OPEN; } loop = loop ? -1 : 0; if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) { return ERROR_EAS_FAILURE; } return NO_ERROR; } status_t MidiFile::createOutputTrack() { if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels, CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 2 /*bufferCount*/) != NO_ERROR) { ALOGE("mAudioSink open failed"); return ERROR_OPEN_FAILED; } return NO_ERROR; } int MidiFile::render() { EAS_RESULT result = EAS_FAILURE; EAS_I32 count; int temp; bool audioStarted = false; ALOGV("MidiFile::render"); // allocate render buffer mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS]; if (!mAudioBuffer) { ALOGE("mAudioBuffer allocate failed"); goto threadExit; } // signal main thread that we started { Mutex::Autolock l(mMutex); mTid = gettid(); ALOGV("render thread(%d) signal", mTid); mCondition.signal(); } while (1) { mMutex.lock(); // nothing to render, wait for client thread to wake us up while (!mRender && !mExit) { ALOGV("MidiFile::render - signal wait"); mCondition.wait(mMutex); ALOGV("MidiFile::render - signal rx'd"); } if (mExit) { mMutex.unlock(); break; } // render midi data into the input buffer //ALOGV("MidiFile::render - rendering audio"); int num_output = 0; EAS_PCM* p = mAudioBuffer; for (int i = 0; i < NUM_BUFFERS; i++) { result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count); if (result != EAS_SUCCESS) { ALOGE("EAS_Render returned %ld", result); } p += count * pLibConfig->numChannels; num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM); } // update playback state and position // ALOGV("MidiFile::render - updating state"); EAS_GetLocation(mEasData, mEasHandle, &mPlayTime); EAS_State(mEasData, mEasHandle, &mState); mMutex.unlock(); // create audio output track if necessary if (!mAudioSink->ready()) { ALOGV("MidiFile::render - create output track"); if (createOutputTrack() != NO_ERROR) goto threadExit; } // Write data to the audio hardware // ALOGV("MidiFile::render - writing to audio output"); if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) { ALOGE("Error in writing:%d",temp); return temp; } // start audio output if necessary if (!audioStarted) { //ALOGV("MidiFile::render - starting audio"); mAudioSink->start(); audioStarted = true; } // still playing? if ((mState == EAS_STATE_STOPPED) || (mState == EAS_STATE_ERROR) || (mState == EAS_STATE_PAUSED)) { switch(mState) { case EAS_STATE_STOPPED: { ALOGV("MidiFile::render - stopped"); sendEvent(MEDIA_PLAYBACK_COMPLETE); break; } case EAS_STATE_ERROR: { ALOGE("MidiFile::render - error"); sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN); break; } case EAS_STATE_PAUSED: ALOGV("MidiFile::render - paused"); break; default: break; } mAudioSink->stop(); audioStarted = false; mRender = false; } } threadExit: mAudioSink.clear(); if (mAudioBuffer) { delete [] mAudioBuffer; mAudioBuffer = NULL; } mMutex.lock(); mTid = -1; mCondition.signal(); mMutex.unlock(); return result; } } // end namespace android