You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
474 lines
18 KiB
474 lines
18 KiB
/*
|
|
* Copyright (C) 2010 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.
|
|
*/
|
|
|
|
/* OutputMixExt implementation */
|
|
|
|
#include "sles_allinclusive.h"
|
|
#include <math.h>
|
|
|
|
|
|
// OutputMixExt is used by SDL, but is not specific to or dependent on SDL
|
|
|
|
|
|
// stereo is a frame consisting of a pair of 16-bit PCM samples
|
|
|
|
typedef struct {
|
|
short left;
|
|
short right;
|
|
} stereo;
|
|
|
|
|
|
/** \brief Summary of the gain, as an optimization for the mixer */
|
|
|
|
typedef enum {
|
|
GAIN_MUTE = 0, // mValue == 0.0f within epsilon
|
|
GAIN_UNITY = 1, // mValue == 1.0f within epsilon
|
|
GAIN_OTHER = 2 // 0.0f < mValue < 1.0f
|
|
} Summary;
|
|
|
|
|
|
/** \brief Check whether a track has any data for us to read */
|
|
|
|
static SLboolean track_check(Track *track)
|
|
{
|
|
assert(NULL != track);
|
|
SLboolean trackHasData = SL_BOOLEAN_FALSE;
|
|
|
|
CAudioPlayer *audioPlayer = track->mAudioPlayer;
|
|
if (NULL != audioPlayer) {
|
|
|
|
// track is initialized
|
|
|
|
// FIXME This lock could block and result in stuttering;
|
|
// a trylock with retry or lockless solution would be ideal
|
|
object_lock_exclusive(&audioPlayer->mObject);
|
|
assert(audioPlayer->mTrack == track);
|
|
|
|
SLuint32 framesMixed = track->mFramesMixed;
|
|
if (0 != framesMixed) {
|
|
track->mFramesMixed = 0;
|
|
audioPlayer->mPlay.mFramesSinceLastSeek += framesMixed;
|
|
audioPlayer->mPlay.mFramesSincePositionUpdate += framesMixed;
|
|
}
|
|
|
|
SLboolean doBroadcast = SL_BOOLEAN_FALSE;
|
|
const BufferHeader *oldFront;
|
|
|
|
if (audioPlayer->mBufferQueue.mClearRequested) {
|
|
// application thread(s) that call BufferQueue::Clear while mixer is active
|
|
// will block synchronously until mixer acknowledges the Clear request
|
|
audioPlayer->mBufferQueue.mFront = &audioPlayer->mBufferQueue.mArray[0];
|
|
audioPlayer->mBufferQueue.mRear = &audioPlayer->mBufferQueue.mArray[0];
|
|
audioPlayer->mBufferQueue.mState.count = 0;
|
|
audioPlayer->mBufferQueue.mState.playIndex = 0;
|
|
audioPlayer->mBufferQueue.mClearRequested = SL_BOOLEAN_FALSE;
|
|
track->mReader = NULL;
|
|
track->mAvail = 0;
|
|
doBroadcast = SL_BOOLEAN_TRUE;
|
|
}
|
|
|
|
if (audioPlayer->mDestroyRequested) {
|
|
// an application thread that calls Object::Destroy while mixer is active will block
|
|
// synchronously in the PreDestroy hook until mixer acknowledges the Destroy request
|
|
COutputMix *outputMix = CAudioPlayer_GetOutputMix(audioPlayer);
|
|
unsigned i = track - outputMix->mOutputMixExt.mTracks;
|
|
assert( /* 0 <= i && */ i < MAX_TRACK);
|
|
unsigned mask = 1 << i;
|
|
track->mAudioPlayer = NULL;
|
|
assert(outputMix->mOutputMixExt.mActiveMask & mask);
|
|
outputMix->mOutputMixExt.mActiveMask &= ~mask;
|
|
audioPlayer->mTrack = NULL;
|
|
audioPlayer->mDestroyRequested = SL_BOOLEAN_FALSE;
|
|
doBroadcast = SL_BOOLEAN_TRUE;
|
|
goto broadcast;
|
|
}
|
|
|
|
switch (audioPlayer->mPlay.mState) {
|
|
|
|
case SL_PLAYSTATE_PLAYING: // continue playing current track data
|
|
if (0 < track->mAvail) {
|
|
trackHasData = SL_BOOLEAN_TRUE;
|
|
break;
|
|
}
|
|
|
|
// try to get another buffer from queue
|
|
oldFront = audioPlayer->mBufferQueue.mFront;
|
|
if (oldFront != audioPlayer->mBufferQueue.mRear) {
|
|
assert(0 < audioPlayer->mBufferQueue.mState.count);
|
|
track->mReader = oldFront->mBuffer;
|
|
track->mAvail = oldFront->mSize;
|
|
// note that the buffer stays on the queue while we are reading
|
|
audioPlayer->mPlay.mState = SL_PLAYSTATE_PLAYING;
|
|
trackHasData = SL_BOOLEAN_TRUE;
|
|
} else {
|
|
// no buffers on queue, so playable but not playing
|
|
// NTH should be able to call a desperation callback when completely starved,
|
|
// or call less often than every buffer based on high/low water-marks
|
|
}
|
|
|
|
// copy gains from audio player to track
|
|
track->mGains[0] = audioPlayer->mGains[0];
|
|
track->mGains[1] = audioPlayer->mGains[1];
|
|
break;
|
|
|
|
case SL_PLAYSTATE_STOPPING: // application thread(s) called Play::SetPlayState(STOPPED)
|
|
audioPlayer->mPlay.mPosition = (SLmillisecond) 0;
|
|
audioPlayer->mPlay.mFramesSinceLastSeek = 0;
|
|
audioPlayer->mPlay.mFramesSincePositionUpdate = 0;
|
|
audioPlayer->mPlay.mLastSeekPosition = 0;
|
|
audioPlayer->mPlay.mState = SL_PLAYSTATE_STOPPED;
|
|
// stop cancels a pending seek
|
|
audioPlayer->mSeek.mPos = SL_TIME_UNKNOWN;
|
|
oldFront = audioPlayer->mBufferQueue.mFront;
|
|
if (oldFront != audioPlayer->mBufferQueue.mRear) {
|
|
assert(0 < audioPlayer->mBufferQueue.mState.count);
|
|
track->mReader = oldFront->mBuffer;
|
|
track->mAvail = oldFront->mSize;
|
|
}
|
|
doBroadcast = SL_BOOLEAN_TRUE;
|
|
break;
|
|
|
|
case SL_PLAYSTATE_STOPPED: // idle
|
|
case SL_PLAYSTATE_PAUSED: // idle
|
|
break;
|
|
|
|
default:
|
|
assert(SL_BOOLEAN_FALSE);
|
|
break;
|
|
}
|
|
|
|
broadcast:
|
|
if (doBroadcast) {
|
|
object_cond_broadcast(&audioPlayer->mObject);
|
|
}
|
|
|
|
object_unlock_exclusive(&audioPlayer->mObject);
|
|
|
|
}
|
|
|
|
return trackHasData;
|
|
|
|
}
|
|
|
|
|
|
/** \brief This is the track mixer: fill the specified 16-bit stereo PCM buffer */
|
|
|
|
void IOutputMixExt_FillBuffer(SLOutputMixExtItf self, void *pBuffer, SLuint32 size)
|
|
{
|
|
SL_ENTER_INTERFACE_VOID
|
|
|
|
// Force to be a multiple of a frame, assumes stereo 16-bit PCM
|
|
size &= ~3;
|
|
SLboolean mixBufferHasData = SL_BOOLEAN_FALSE;
|
|
IOutputMixExt *thiz = (IOutputMixExt *) self;
|
|
IObject *thisObject = thiz->mThis;
|
|
// This lock should never block, except when the application destroys the output mix object
|
|
object_lock_exclusive(thisObject);
|
|
unsigned activeMask;
|
|
// If the output mix is marked for destruction, then acknowledge the request
|
|
if (thiz->mDestroyRequested) {
|
|
IEngine *thisEngine = &thisObject->mEngine->mEngine;
|
|
interface_lock_exclusive(thisEngine);
|
|
assert(&thisEngine->mOutputMix->mObject == thisObject);
|
|
thisEngine->mOutputMix = NULL;
|
|
// Note we don't attempt to connect another output mix, even if there is one
|
|
interface_unlock_exclusive(thisEngine);
|
|
// Acknowledge the destroy request, and notify the pre-destroy hook
|
|
thiz->mDestroyRequested = SL_BOOLEAN_FALSE;
|
|
object_cond_broadcast(thisObject);
|
|
activeMask = 0;
|
|
} else {
|
|
activeMask = thiz->mActiveMask;
|
|
}
|
|
while (activeMask) {
|
|
unsigned i = ctz(activeMask);
|
|
assert(MAX_TRACK > i);
|
|
activeMask &= ~(1 << i);
|
|
Track *track = &thiz->mTracks[i];
|
|
|
|
// track is allocated
|
|
|
|
if (!track_check(track)) {
|
|
continue;
|
|
}
|
|
|
|
// track is playing
|
|
void *dstWriter = pBuffer;
|
|
unsigned desired = size;
|
|
SLboolean trackContributedToMix = SL_BOOLEAN_FALSE;
|
|
float gains[STEREO_CHANNELS];
|
|
Summary summaries[STEREO_CHANNELS];
|
|
unsigned channel;
|
|
for (channel = 0; channel < STEREO_CHANNELS; ++channel) {
|
|
float gain = track->mGains[channel];
|
|
gains[channel] = gain;
|
|
Summary summary;
|
|
if (gain <= 0.001) {
|
|
summary = GAIN_MUTE;
|
|
} else if (gain >= 0.999) {
|
|
summary = GAIN_UNITY;
|
|
} else {
|
|
summary = GAIN_OTHER;
|
|
}
|
|
summaries[channel] = summary;
|
|
}
|
|
while (desired > 0) {
|
|
unsigned actual = desired;
|
|
if (track->mAvail < actual) {
|
|
actual = track->mAvail;
|
|
}
|
|
// force actual to be a frame multiple
|
|
if (actual > 0) {
|
|
assert(NULL != track->mReader);
|
|
stereo *mixBuffer = (stereo *) dstWriter;
|
|
const stereo *source = (const stereo *) track->mReader;
|
|
unsigned j;
|
|
if (GAIN_MUTE != summaries[0] || GAIN_MUTE != summaries[1]) {
|
|
if (mixBufferHasData) {
|
|
// apply gain during add
|
|
if (GAIN_UNITY != summaries[0] || GAIN_UNITY != summaries[1]) {
|
|
for (j = 0; j < actual; j += sizeof(stereo), ++mixBuffer, ++source) {
|
|
mixBuffer->left += (short) (source->left * track->mGains[0]);
|
|
mixBuffer->right += (short) (source->right * track->mGains[1]);
|
|
}
|
|
// no gain adjustment needed, so do a simple add
|
|
} else {
|
|
for (j = 0; j < actual; j += sizeof(stereo), ++mixBuffer, ++source) {
|
|
mixBuffer->left += source->left;
|
|
mixBuffer->right += source->right;
|
|
}
|
|
}
|
|
} else {
|
|
// apply gain during copy
|
|
if (GAIN_UNITY != summaries[0] || GAIN_UNITY != summaries[1]) {
|
|
for (j = 0; j < actual; j += sizeof(stereo), ++mixBuffer, ++source) {
|
|
mixBuffer->left = (short) (source->left * track->mGains[0]);
|
|
mixBuffer->right = (short) (source->right * track->mGains[1]);
|
|
}
|
|
// no gain adjustment needed, so do a simple copy
|
|
} else {
|
|
memcpy(dstWriter, track->mReader, actual);
|
|
}
|
|
}
|
|
trackContributedToMix = SL_BOOLEAN_TRUE;
|
|
}
|
|
dstWriter = (char *) dstWriter + actual;
|
|
desired -= actual;
|
|
track->mReader = (char *) track->mReader + actual;
|
|
track->mAvail -= actual;
|
|
if (track->mAvail == 0) {
|
|
IBufferQueue *bufferQueue = &track->mAudioPlayer->mBufferQueue;
|
|
interface_lock_exclusive(bufferQueue);
|
|
const BufferHeader *oldFront, *newFront, *rear;
|
|
oldFront = bufferQueue->mFront;
|
|
rear = bufferQueue->mRear;
|
|
// a buffer stays on queue while playing, so it better still be there
|
|
assert(oldFront != rear);
|
|
newFront = oldFront;
|
|
if (++newFront == &bufferQueue->mArray[bufferQueue->mNumBuffers + 1]) {
|
|
newFront = bufferQueue->mArray;
|
|
}
|
|
bufferQueue->mFront = (BufferHeader *) newFront;
|
|
assert(0 < bufferQueue->mState.count);
|
|
--bufferQueue->mState.count;
|
|
if (newFront != rear) {
|
|
// we don't acknowledge application requests between buffers
|
|
// within the same mixer frame
|
|
assert(0 < bufferQueue->mState.count);
|
|
track->mReader = newFront->mBuffer;
|
|
track->mAvail = newFront->mSize;
|
|
}
|
|
// else we would set play state to playable but not playing during next mixer
|
|
// frame if the queue is still empty at that time
|
|
++bufferQueue->mState.playIndex;
|
|
slBufferQueueCallback callback = bufferQueue->mCallback;
|
|
void *context = bufferQueue->mContext;
|
|
interface_unlock_exclusive(bufferQueue);
|
|
// The callback function is called on each buffer completion
|
|
if (NULL != callback) {
|
|
(*callback)((SLBufferQueueItf) bufferQueue, context);
|
|
// Maybe it enqueued another buffer, or maybe it didn't.
|
|
// We will find out later during the next mixer frame.
|
|
}
|
|
}
|
|
// no lock, but safe because noone else updates this field
|
|
track->mFramesMixed += actual >> 2; // sizeof(short) * STEREO_CHANNELS
|
|
continue;
|
|
}
|
|
// we need more data: desired > 0 but actual == 0
|
|
if (track_check(track)) {
|
|
continue;
|
|
}
|
|
// underflow: clear out rest of partial buffer (NTH synthesize comfort noise)
|
|
if (!mixBufferHasData && trackContributedToMix) {
|
|
memset(dstWriter, 0, actual);
|
|
}
|
|
break;
|
|
}
|
|
if (trackContributedToMix) {
|
|
mixBufferHasData = SL_BOOLEAN_TRUE;
|
|
}
|
|
}
|
|
object_unlock_exclusive(thisObject);
|
|
// No active tracks, so output silence
|
|
if (!mixBufferHasData) {
|
|
memset(pBuffer, 0, size);
|
|
}
|
|
|
|
SL_LEAVE_INTERFACE_VOID
|
|
}
|
|
|
|
|
|
static const struct SLOutputMixExtItf_ IOutputMixExt_Itf = {
|
|
IOutputMixExt_FillBuffer
|
|
};
|
|
|
|
void IOutputMixExt_init(void *self)
|
|
{
|
|
IOutputMixExt *thiz = (IOutputMixExt *) self;
|
|
thiz->mItf = &IOutputMixExt_Itf;
|
|
thiz->mActiveMask = 0;
|
|
Track *track = &thiz->mTracks[0];
|
|
unsigned i;
|
|
for (i = 0; i < MAX_TRACK; ++i, ++track) {
|
|
track->mAudioPlayer = NULL;
|
|
}
|
|
thiz->mDestroyRequested = SL_BOOLEAN_FALSE;
|
|
}
|
|
|
|
|
|
/** \brief Called by Engine::CreateAudioPlayer to allocate a track */
|
|
|
|
SLresult IOutputMixExt_checkAudioPlayerSourceSink(CAudioPlayer *thiz)
|
|
{
|
|
thiz->mTrack = NULL;
|
|
|
|
// check the source for compatibility
|
|
switch (thiz->mDataSource.mLocator.mLocatorType) {
|
|
case SL_DATALOCATOR_BUFFERQUEUE:
|
|
#ifdef ANDROID
|
|
case SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE:
|
|
#endif
|
|
switch (thiz->mDataSource.mFormat.mFormatType) {
|
|
case SL_DATAFORMAT_PCM:
|
|
#ifdef USE_SDL
|
|
// SDL is hard-coded to 44.1 kHz, and there is no sample rate converter
|
|
if (SL_SAMPLINGRATE_44_1 != thiz->mDataSource.mFormat.mPCM.samplesPerSec)
|
|
return SL_RESULT_CONTENT_UNSUPPORTED;
|
|
#endif
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// check the sink for compatibility
|
|
const SLDataSink *pAudioSnk = &thiz->mDataSink.u.mSink;
|
|
Track *track = NULL;
|
|
switch (*(SLuint32 *)pAudioSnk->pLocator) {
|
|
case SL_DATALOCATOR_OUTPUTMIX:
|
|
{
|
|
// pAudioSnk->pFormat is ignored
|
|
IOutputMixExt *omExt = &((COutputMix *) ((SLDataLocator_OutputMix *)
|
|
pAudioSnk->pLocator)->outputMix)->mOutputMixExt;
|
|
// allocate an entry within OutputMix for this track
|
|
interface_lock_exclusive(omExt);
|
|
unsigned availMask = ~omExt->mActiveMask;
|
|
if (!availMask) {
|
|
interface_unlock_exclusive(omExt);
|
|
// All track slots full in output mix
|
|
return SL_RESULT_MEMORY_FAILURE;
|
|
}
|
|
unsigned i = ctz(availMask);
|
|
assert(MAX_TRACK > i);
|
|
omExt->mActiveMask |= 1 << i;
|
|
track = &omExt->mTracks[i];
|
|
track->mAudioPlayer = NULL; // only field that is accessed before full initialization
|
|
interface_unlock_exclusive(omExt);
|
|
thiz->mTrack = track;
|
|
thiz->mGains[0] = 1.0f;
|
|
thiz->mGains[1] = 1.0f;
|
|
thiz->mDestroyRequested = SL_BOOLEAN_FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
return SL_RESULT_CONTENT_UNSUPPORTED;
|
|
}
|
|
|
|
assert(NULL != track);
|
|
track->mBufferQueue = &thiz->mBufferQueue;
|
|
track->mAudioPlayer = thiz;
|
|
track->mReader = NULL;
|
|
track->mAvail = 0;
|
|
track->mGains[0] = 1.0f;
|
|
track->mGains[1] = 1.0f;
|
|
track->mFramesMixed = 0;
|
|
return SL_RESULT_SUCCESS;
|
|
}
|
|
|
|
|
|
/** \brief Called when a gain-related field (mute, solo, volume, stereo position, etc.) updated */
|
|
|
|
void audioPlayerGainUpdate(CAudioPlayer *audioPlayer)
|
|
{
|
|
SLboolean mute = audioPlayer->mVolume.mMute;
|
|
SLuint8 muteMask = audioPlayer->mMuteMask;
|
|
SLuint8 soloMask = audioPlayer->mSoloMask;
|
|
SLmillibel level = audioPlayer->mVolume.mLevel;
|
|
SLboolean enableStereoPosition = audioPlayer->mVolume.mEnableStereoPosition;
|
|
SLpermille stereoPosition = audioPlayer->mVolume.mStereoPosition;
|
|
|
|
if (soloMask) {
|
|
muteMask |= ~soloMask;
|
|
}
|
|
if (mute || !(~muteMask & 3)) {
|
|
audioPlayer->mGains[0] = 0.0f;
|
|
audioPlayer->mGains[1] = 0.0f;
|
|
} else {
|
|
float playerGain = powf(10.0f, level / 2000.0f);
|
|
unsigned channel;
|
|
for (channel = 0; channel < STEREO_CHANNELS; ++channel) {
|
|
float gain;
|
|
if (muteMask & (1 << channel)) {
|
|
gain = 0.0f;
|
|
} else {
|
|
gain = playerGain;
|
|
if (enableStereoPosition) {
|
|
switch (channel) {
|
|
case 0:
|
|
if (stereoPosition > 0) {
|
|
gain *= (1000 - stereoPosition) / 1000.0f;
|
|
}
|
|
break;
|
|
case 1:
|
|
if (stereoPosition < 0) {
|
|
gain *= (1000 + stereoPosition) / 1000.0f;
|
|
}
|
|
break;
|
|
default:
|
|
assert(SL_BOOLEAN_FALSE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
audioPlayer->mGains[channel] = gain;
|
|
}
|
|
}
|
|
}
|