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
559 lines
14 KiB
C++
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
|