Jean-Michel Trivi 4ed260feda Add channel mask in AudioSink
Add support for specifying a channel mask when opening an AudioSink.
  This parameter does not replace the channel count parameter in order
  to not have to duplicate the logic to derive a mask from the
  channel count everywhere an AudioSink is used without a known mask.

A mask of 0 (CHANNEL_MASK_USE_CHANNEL_ORDER) means a mask will
  be automatically derived from the number of channels.

Update existing AudioSink implementations to use the channel mask,
  and users of AudioSink to specify the mask if available, and
  CHANNEL_MASK_USE_CHANNEL_ORDER otherwise.

Change-Id: Ifa9bd259874816dbc25ead2b03ea52e873cff474
2012-03-02 17:26:49 -08:00

553 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 <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;
// 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;
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;
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");
// 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) != 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