blob: b5b1e354d1bd83ae3abdda918a110a2897e97a55 [file] [log] [blame]
/*
* 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"
#ifdef USE_OUTPUTMIXEXT
// Used by SDL but not specific to or dependent on SDL
static void IOutputMixExt_FillBuffer(SLOutputMixExtItf self, void *pBuffer, SLuint32 size)
{
// Force to be a multiple of a frame, assumes stereo 16-bit PCM
size &= ~3;
IOutputMixExt *thisExt = (IOutputMixExt *) self;
IOutputMix *this = &((COutputMix *) thisExt->mThis)->mOutputMix;
unsigned activeMask = this->mActiveMask;
struct Track *track = this->mTracks;
unsigned i;
SLboolean mixBufferHasData = SL_BOOLEAN_FALSE;
// FIXME O(32) loop even when few tracks are active.
// To avoid loop, use activeMask to check for active track(s)
// and decide whether we actually need to copy or mix.
for (i = 0; 0 != activeMask; ++i, ++track, activeMask >>= 1) {
assert(i < 32);
if (!(activeMask & 1))
continue;
// track is allocated
IPlay *play = track->mPlay;
if (NULL == play)
continue;
// track is initialized
if (SL_PLAYSTATE_PLAYING != play->mState)
continue;
// track is playing
void *dstWriter = pBuffer;
unsigned desired = size;
SLboolean trackContributedToMix = SL_BOOLEAN_FALSE;
IBufferQueue *bufferQueue = track->mBufferQueue;
while (desired > 0) {
const struct BufferHeader *oldFront, *newFront, *rear;
unsigned actual = desired;
if (track->mAvail < actual)
actual = track->mAvail;
// force actual to be a frame multiple
if (actual > 0) {
// FIXME check for either mute or volume 0
// in which case skip the input buffer processing
assert(NULL != track->mReader);
// FIXME && gain == 1.0
if (mixBufferHasData) {
stereo *mixBuffer = (stereo *) dstWriter;
const stereo *source = (const stereo *) track->mReader;
unsigned j;
for (j = 0; j < actual; j += sizeof(stereo), ++mixBuffer,
++source) {
// apply gain here
mixBuffer->left += source->left;
mixBuffer->right += source->right;
}
} else {
// apply gain during copy
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) {
if (NULL != bufferQueue) {
interface_lock_exclusive(bufferQueue);
oldFront = bufferQueue->mFront;
rear = bufferQueue->mRear;
assert(oldFront != rear);
newFront = oldFront;
if (++newFront == &bufferQueue->mArray[bufferQueue->mNumBuffers])
newFront = bufferQueue->mArray;
bufferQueue->mFront = (struct BufferHeader *) newFront;
assert(0 < bufferQueue->mState.count);
--bufferQueue->mState.count;
// FIXME here or in Enqueue?
++bufferQueue->mState.playIndex;
interface_unlock_exclusive(bufferQueue);
// FIXME a good time to do an early warning
// callback depending on buffer count
}
}
continue;
}
// actual == 0
if (NULL != bufferQueue) {
interface_lock_shared(bufferQueue);
oldFront = bufferQueue->mFront;
rear = bufferQueue->mRear;
if (oldFront != rear) {
got_one:
assert(0 < bufferQueue->mState.count);
track->mReader = oldFront->mBuffer;
track->mAvail = oldFront->mSize;
interface_unlock_shared(bufferQueue);
continue;
}
// FIXME should be able to configure when to
// kick off the callback e.g. high/low water-marks etc.
// need data but none available, attempt a desperate callback
slBufferQueueCallback callback = bufferQueue->mCallback;
void *context = bufferQueue->mContext;
interface_unlock_shared(bufferQueue);
if (NULL != callback) {
(*callback)((SLBufferQueueItf) bufferQueue, context);
// if lucky, the callback enqueued a buffer
interface_lock_shared(bufferQueue);
if (rear != bufferQueue->mRear)
goto got_one;
interface_unlock_shared(bufferQueue);
// unlucky, queue still empty, the callback failed
}
// here on underflow due to no callback, or failed callback
// FIXME underflow, send silence (or previous buffer?)
// we did a callback to try to kick start again but failed
// should log this
}
// no buffer queue or underflow, clear out rest of partial buffer
if (!mixBufferHasData && trackContributedToMix)
memset(dstWriter, 0, actual);
break;
}
if (trackContributedToMix)
mixBufferHasData = SL_BOOLEAN_TRUE;
}
// No active tracks, so output silence
if (!mixBufferHasData)
memset(pBuffer, 0, size);
}
static const struct SLOutputMixExtItf_ IOutputMixExt_Itf = {
IOutputMixExt_FillBuffer
};
void IOutputMixExt_init(void *self)
{
IOutputMixExt *this = (IOutputMixExt *) self;
this->mItf = &IOutputMixExt_Itf;
}
SLresult IOutputMixExt_checkAudioPlayerSourceSink(CAudioPlayer *this)
{
//const SLDataSource *pAudioSrc = &this->mDataSource.u.mSource;
const SLDataSink *pAudioSnk = &this->mDataSink.u.mSink;
struct Track *track = NULL;
switch (*(SLuint32 *)pAudioSnk->pLocator) {
case SL_DATALOCATOR_OUTPUTMIX:
{
// pAudioSnk->pFormat is ignored
IOutputMix *om = &((COutputMix *) ((SLDataLocator_OutputMix *) pAudioSnk->pLocator)->outputMix)->mOutputMix;
// allocate an entry within OutputMix for this track
// FIXME O(n)
unsigned i;
for (i = 0, track = &om->mTracks[0]; i < 32; ++i, ++track) {
if (om->mActiveMask & (1 << i))
continue;
om->mActiveMask |= 1 << i;
break;
}
if (32 <= i) {
// FIXME Need a better error code for all slots full in output mix
return SL_RESULT_MEMORY_FAILURE;
}
}
break;
default:
return SL_RESULT_CONTENT_UNSUPPORTED;
}
track->mBufferQueue = &this->mBufferQueue;
track->mPlay = &this->mPlay;
// next 2 fields must be initialized explicitly (not part of this)
track->mReader = NULL;
track->mAvail = 0;
return SL_RESULT_SUCCESS;
}
#endif // USE_OUTPUTMIXEXT