/* 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)
{
LOGV("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)) {
LOGE("EAS library/header mismatch");
goto Failed;
}
// initialize EAS library
if (EAS_Init(&mEasData) != EAS_SUCCESS) {
LOGE("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);
createThreadEtc(renderThread, this, "midithread", ANDROID_PRIORITY_AUDIO);
mCondition.wait(mMutex);
LOGV("thread started");
}
// indicate success
if (mTid > 0) {
LOGV(" 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() {
LOGV("MidiFile destructor");
release();
}
status_t MidiFile::setDataSource(
const char* path, const KeyedVector<String8, String8> *) {
LOGV("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) {
LOGE("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)
{
LOGV("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) {
LOGE("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()
{
LOGV("MidiFile::prepare");
Mutex::Autolock lock(mMutex);
if (!mEasHandle) {
return ERROR_NOT_OPEN;
}
EAS_RESULT result;
if ((result = EAS_Prepare(mEasData, mEasHandle)) != EAS_SUCCESS) {
LOGE("EAS_Prepare failed: [%ld]", result);
return ERROR_EAS_FAILURE;
}
updateState();
return NO_ERROR;
}
status_t MidiFile::prepareAsync()
{
LOGV("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()
{
LOGV("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;
// wake up render thread
LOGV(" wakeup render thread");
mCondition.signal();
return NO_ERROR;
}
status_t MidiFile::stop()
{
LOGV("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) {
LOGE("EAS_Pause returned error %ld", result);
return ERROR_EAS_FAILURE;
}
}
mPaused = false;
return NO_ERROR;
}
status_t MidiFile::seekTo(int position)
{
LOGV("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)
{
LOGE("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()
{
LOGV("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;
return NO_ERROR;
}
bool MidiFile::isPlaying()
{
LOGV("MidiFile::isPlaying, mState=%d", int(mState));
if (!mEasHandle || mPaused) return false;
return (mState == EAS_STATE_PLAY);
}
status_t MidiFile::getCurrentPosition(int* position)
{
LOGV("MidiFile::getCurrentPosition");
if (!mEasHandle) {
LOGE("getCurrentPosition(): file not open");
return ERROR_NOT_OPEN;
}
if (mPlayTime < 0) {
LOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime);
return ERROR_EAS_FAILURE;
}
*position = mPlayTime;
return NO_ERROR;
}
status_t MidiFile::getDuration(int* duration)
{
LOGV("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()
{
LOGV("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()
{
LOGV("MidiFile::reset");
Mutex::Autolock lock(mMutex);
return reset_nosync();
}
// call only with mutex held
status_t MidiFile::reset_nosync()
{
LOGV("MidiFile::reset_nosync");
// 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)
{
LOGV("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, AUDIO_FORMAT_PCM_16_BIT, 2) != NO_ERROR) {
LOGE("mAudioSink open failed");
return ERROR_OPEN_FAILED;
}
return NO_ERROR;
}
int MidiFile::renderThread(void* p) {
return ((MidiFile*)p)->render();
}
int MidiFile::render() {
EAS_RESULT result = EAS_FAILURE;
EAS_I32 count;
int temp;
bool audioStarted = false;
LOGV("MidiFile::render");
// allocate render buffer
mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS];
if (!mAudioBuffer) {
LOGE("mAudioBuffer allocate failed");
goto threadExit;
}
// signal main thread that we started
{
Mutex::Autolock l(mMutex);
mTid = gettid();
LOGV("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)
{
LOGV("MidiFile::render - signal wait");
mCondition.wait(mMutex);
LOGV("MidiFile::render - signal rx'd");
}
if (mExit) {
mMutex.unlock();
break;
}
// render midi data into the input buffer
//LOGV("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) {
LOGE("EAS_Render returned %ld", result);
}
p += count * pLibConfig->numChannels;
num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM);
}
// update playback state and position
// LOGV("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()) {
LOGV("MidiFile::render - create output track");
if (createOutputTrack() != NO_ERROR)
goto threadExit;
}
// Write data to the audio hardware
// LOGV("MidiFile::render - writing to audio output");
if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) {
LOGE("Error in writing:%d",temp);
return temp;
}
// start audio output if necessary
if (!audioStarted) {
//LOGV("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:
{
LOGV("MidiFile::render - stopped");
sendEvent(MEDIA_PLAYBACK_COMPLETE);
break;
}
case EAS_STATE_ERROR:
{
LOGE("MidiFile::render - error");
sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN);
break;
}
case EAS_STATE_PAUSED:
LOGV("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