/* * 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; } } }