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