Dave Sparks c082769b67 MIDI render thread was nice 0, should be nice -16.
Seems like a recent change sets the thread priority to 0. Previously it
inherited priority from the parent thread. This change sets the MIDI
render thread priority to the default for audio threads.
Reference bug 1800905
2009-04-29 12:59:33 -07:00

559 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)
{
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);
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