565 lines
14 KiB
C++
565 lines
14 KiB
C++
|
/* 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 "MidiFile.h"
|
||
|
|
||
|
#ifdef HAVE_GETTID
|
||
|
static pid_t myTid() { return gettid(); }
|
||
|
#else
|
||
|
static pid_t myTid() { return getpid(); }
|
||
|
#endif
|
||
|
|
||
|
// ----------------------------------------------------------------------------
|
||
|
|
||
|
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(AudioSystem::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, AudioSystem::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 = myTid();
|
||
|
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);
|
||
|
if((mState != EAS_STATE_STOPPED) && (mPlayTime >= mDuration))
|
||
|
mState = EAS_STATE_STOPPED;
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
status_t MidiFile::setParameters(const String8& params) {
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
} // end namespace android
|