xref: /aosp_15_r20/frameworks/wilhelm/src/itf/IOutputMixExt.cpp (revision bebae9c0e76121f8312ccb50385c080b3a0b023c)
1*bebae9c0SAndroid Build Coastguard Worker /*
2*bebae9c0SAndroid Build Coastguard Worker  * Copyright (C) 2010 The Android Open Source Project
3*bebae9c0SAndroid Build Coastguard Worker  *
4*bebae9c0SAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*bebae9c0SAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*bebae9c0SAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*bebae9c0SAndroid Build Coastguard Worker  *
8*bebae9c0SAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*bebae9c0SAndroid Build Coastguard Worker  *
10*bebae9c0SAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*bebae9c0SAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*bebae9c0SAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*bebae9c0SAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*bebae9c0SAndroid Build Coastguard Worker  * limitations under the License.
15*bebae9c0SAndroid Build Coastguard Worker  */
16*bebae9c0SAndroid Build Coastguard Worker 
17*bebae9c0SAndroid Build Coastguard Worker /* OutputMixExt implementation */
18*bebae9c0SAndroid Build Coastguard Worker 
19*bebae9c0SAndroid Build Coastguard Worker #include "sles_allinclusive.h"
20*bebae9c0SAndroid Build Coastguard Worker #include <math.h>
21*bebae9c0SAndroid Build Coastguard Worker 
22*bebae9c0SAndroid Build Coastguard Worker 
23*bebae9c0SAndroid Build Coastguard Worker // OutputMixExt is used by SDL, but is not specific to or dependent on SDL
24*bebae9c0SAndroid Build Coastguard Worker 
25*bebae9c0SAndroid Build Coastguard Worker 
26*bebae9c0SAndroid Build Coastguard Worker // stereo is a frame consisting of a pair of 16-bit PCM samples
27*bebae9c0SAndroid Build Coastguard Worker 
28*bebae9c0SAndroid Build Coastguard Worker typedef struct {
29*bebae9c0SAndroid Build Coastguard Worker     short left;
30*bebae9c0SAndroid Build Coastguard Worker     short right;
31*bebae9c0SAndroid Build Coastguard Worker } stereo;
32*bebae9c0SAndroid Build Coastguard Worker 
33*bebae9c0SAndroid Build Coastguard Worker 
34*bebae9c0SAndroid Build Coastguard Worker /** \brief Summary of the gain, as an optimization for the mixer */
35*bebae9c0SAndroid Build Coastguard Worker 
36*bebae9c0SAndroid Build Coastguard Worker typedef enum {
37*bebae9c0SAndroid Build Coastguard Worker     GAIN_MUTE  = 0,  // mValue == 0.0f within epsilon
38*bebae9c0SAndroid Build Coastguard Worker     GAIN_UNITY = 1,  // mValue == 1.0f within epsilon
39*bebae9c0SAndroid Build Coastguard Worker     GAIN_OTHER = 2   // 0.0f < mValue < 1.0f
40*bebae9c0SAndroid Build Coastguard Worker } Summary;
41*bebae9c0SAndroid Build Coastguard Worker 
42*bebae9c0SAndroid Build Coastguard Worker 
43*bebae9c0SAndroid Build Coastguard Worker /** \brief Check whether a track has any data for us to read */
44*bebae9c0SAndroid Build Coastguard Worker 
track_check(Track * track)45*bebae9c0SAndroid Build Coastguard Worker static SLboolean track_check(Track *track)
46*bebae9c0SAndroid Build Coastguard Worker {
47*bebae9c0SAndroid Build Coastguard Worker     assert(NULL != track);
48*bebae9c0SAndroid Build Coastguard Worker     SLboolean trackHasData = SL_BOOLEAN_FALSE;
49*bebae9c0SAndroid Build Coastguard Worker 
50*bebae9c0SAndroid Build Coastguard Worker     CAudioPlayer *audioPlayer = track->mAudioPlayer;
51*bebae9c0SAndroid Build Coastguard Worker     if (NULL != audioPlayer) {
52*bebae9c0SAndroid Build Coastguard Worker 
53*bebae9c0SAndroid Build Coastguard Worker         // track is initialized
54*bebae9c0SAndroid Build Coastguard Worker 
55*bebae9c0SAndroid Build Coastguard Worker         // FIXME This lock could block and result in stuttering;
56*bebae9c0SAndroid Build Coastguard Worker         // a trylock with retry or lockless solution would be ideal
57*bebae9c0SAndroid Build Coastguard Worker         object_lock_exclusive(&audioPlayer->mObject);
58*bebae9c0SAndroid Build Coastguard Worker         assert(audioPlayer->mTrack == track);
59*bebae9c0SAndroid Build Coastguard Worker 
60*bebae9c0SAndroid Build Coastguard Worker         SLuint32 framesMixed = track->mFramesMixed;
61*bebae9c0SAndroid Build Coastguard Worker         if (0 != framesMixed) {
62*bebae9c0SAndroid Build Coastguard Worker             track->mFramesMixed = 0;
63*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mPlay.mFramesSinceLastSeek += framesMixed;
64*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mPlay.mFramesSincePositionUpdate += framesMixed;
65*bebae9c0SAndroid Build Coastguard Worker         }
66*bebae9c0SAndroid Build Coastguard Worker 
67*bebae9c0SAndroid Build Coastguard Worker         SLboolean doBroadcast = SL_BOOLEAN_FALSE;
68*bebae9c0SAndroid Build Coastguard Worker         const BufferHeader *oldFront;
69*bebae9c0SAndroid Build Coastguard Worker 
70*bebae9c0SAndroid Build Coastguard Worker         if (audioPlayer->mBufferQueue.mClearRequested) {
71*bebae9c0SAndroid Build Coastguard Worker             // application thread(s) that call BufferQueue::Clear while mixer is active
72*bebae9c0SAndroid Build Coastguard Worker             // will block synchronously until mixer acknowledges the Clear request
73*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mBufferQueue.mFront = &audioPlayer->mBufferQueue.mArray[0];
74*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mBufferQueue.mRear = &audioPlayer->mBufferQueue.mArray[0];
75*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mBufferQueue.mState.count = 0;
76*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mBufferQueue.mState.playIndex = 0;
77*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mBufferQueue.mClearRequested = SL_BOOLEAN_FALSE;
78*bebae9c0SAndroid Build Coastguard Worker             track->mReader = NULL;
79*bebae9c0SAndroid Build Coastguard Worker             track->mAvail = 0;
80*bebae9c0SAndroid Build Coastguard Worker             doBroadcast = SL_BOOLEAN_TRUE;
81*bebae9c0SAndroid Build Coastguard Worker         }
82*bebae9c0SAndroid Build Coastguard Worker 
83*bebae9c0SAndroid Build Coastguard Worker         if (audioPlayer->mDestroyRequested) {
84*bebae9c0SAndroid Build Coastguard Worker             // an application thread that calls Object::Destroy while mixer is active will block
85*bebae9c0SAndroid Build Coastguard Worker             // synchronously in the PreDestroy hook until mixer acknowledges the Destroy request
86*bebae9c0SAndroid Build Coastguard Worker             COutputMix *outputMix = CAudioPlayer_GetOutputMix(audioPlayer);
87*bebae9c0SAndroid Build Coastguard Worker             unsigned i = track - outputMix->mOutputMixExt.mTracks;
88*bebae9c0SAndroid Build Coastguard Worker             assert( /* 0 <= i && */ i < MAX_TRACK);
89*bebae9c0SAndroid Build Coastguard Worker             unsigned mask = 1 << i;
90*bebae9c0SAndroid Build Coastguard Worker             track->mAudioPlayer = NULL;
91*bebae9c0SAndroid Build Coastguard Worker             assert(outputMix->mOutputMixExt.mActiveMask & mask);
92*bebae9c0SAndroid Build Coastguard Worker             outputMix->mOutputMixExt.mActiveMask &= ~mask;
93*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mTrack = NULL;
94*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mDestroyRequested = SL_BOOLEAN_FALSE;
95*bebae9c0SAndroid Build Coastguard Worker             doBroadcast = SL_BOOLEAN_TRUE;
96*bebae9c0SAndroid Build Coastguard Worker             goto broadcast;
97*bebae9c0SAndroid Build Coastguard Worker         }
98*bebae9c0SAndroid Build Coastguard Worker 
99*bebae9c0SAndroid Build Coastguard Worker         switch (audioPlayer->mPlay.mState) {
100*bebae9c0SAndroid Build Coastguard Worker 
101*bebae9c0SAndroid Build Coastguard Worker         case SL_PLAYSTATE_PLAYING:  // continue playing current track data
102*bebae9c0SAndroid Build Coastguard Worker             if (0 < track->mAvail) {
103*bebae9c0SAndroid Build Coastguard Worker                 trackHasData = SL_BOOLEAN_TRUE;
104*bebae9c0SAndroid Build Coastguard Worker                 break;
105*bebae9c0SAndroid Build Coastguard Worker             }
106*bebae9c0SAndroid Build Coastguard Worker 
107*bebae9c0SAndroid Build Coastguard Worker             // try to get another buffer from queue
108*bebae9c0SAndroid Build Coastguard Worker             oldFront = audioPlayer->mBufferQueue.mFront;
109*bebae9c0SAndroid Build Coastguard Worker             if (oldFront != audioPlayer->mBufferQueue.mRear) {
110*bebae9c0SAndroid Build Coastguard Worker                 assert(0 < audioPlayer->mBufferQueue.mState.count);
111*bebae9c0SAndroid Build Coastguard Worker                 track->mReader = oldFront->mBuffer;
112*bebae9c0SAndroid Build Coastguard Worker                 track->mAvail = oldFront->mSize;
113*bebae9c0SAndroid Build Coastguard Worker                 // note that the buffer stays on the queue while we are reading
114*bebae9c0SAndroid Build Coastguard Worker                 audioPlayer->mPlay.mState = SL_PLAYSTATE_PLAYING;
115*bebae9c0SAndroid Build Coastguard Worker                 trackHasData = SL_BOOLEAN_TRUE;
116*bebae9c0SAndroid Build Coastguard Worker             } else {
117*bebae9c0SAndroid Build Coastguard Worker                 // no buffers on queue, so playable but not playing
118*bebae9c0SAndroid Build Coastguard Worker                 // NTH should be able to call a desperation callback when completely starved,
119*bebae9c0SAndroid Build Coastguard Worker                 // or call less often than every buffer based on high/low water-marks
120*bebae9c0SAndroid Build Coastguard Worker             }
121*bebae9c0SAndroid Build Coastguard Worker 
122*bebae9c0SAndroid Build Coastguard Worker             // copy gains from audio player to track
123*bebae9c0SAndroid Build Coastguard Worker             track->mGains[0] = audioPlayer->mGains[0];
124*bebae9c0SAndroid Build Coastguard Worker             track->mGains[1] = audioPlayer->mGains[1];
125*bebae9c0SAndroid Build Coastguard Worker             break;
126*bebae9c0SAndroid Build Coastguard Worker 
127*bebae9c0SAndroid Build Coastguard Worker         case SL_PLAYSTATE_STOPPING: // application thread(s) called Play::SetPlayState(STOPPED)
128*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mPlay.mPosition = (SLmillisecond) 0;
129*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mPlay.mFramesSinceLastSeek = 0;
130*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mPlay.mFramesSincePositionUpdate = 0;
131*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mPlay.mLastSeekPosition = 0;
132*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mPlay.mState = SL_PLAYSTATE_STOPPED;
133*bebae9c0SAndroid Build Coastguard Worker             // stop cancels a pending seek
134*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mSeek.mPos = SL_TIME_UNKNOWN;
135*bebae9c0SAndroid Build Coastguard Worker             oldFront = audioPlayer->mBufferQueue.mFront;
136*bebae9c0SAndroid Build Coastguard Worker             if (oldFront != audioPlayer->mBufferQueue.mRear) {
137*bebae9c0SAndroid Build Coastguard Worker                 assert(0 < audioPlayer->mBufferQueue.mState.count);
138*bebae9c0SAndroid Build Coastguard Worker                 track->mReader = oldFront->mBuffer;
139*bebae9c0SAndroid Build Coastguard Worker                 track->mAvail = oldFront->mSize;
140*bebae9c0SAndroid Build Coastguard Worker             }
141*bebae9c0SAndroid Build Coastguard Worker             doBroadcast = SL_BOOLEAN_TRUE;
142*bebae9c0SAndroid Build Coastguard Worker             break;
143*bebae9c0SAndroid Build Coastguard Worker 
144*bebae9c0SAndroid Build Coastguard Worker         case SL_PLAYSTATE_STOPPED:  // idle
145*bebae9c0SAndroid Build Coastguard Worker         case SL_PLAYSTATE_PAUSED:   // idle
146*bebae9c0SAndroid Build Coastguard Worker             break;
147*bebae9c0SAndroid Build Coastguard Worker 
148*bebae9c0SAndroid Build Coastguard Worker         default:
149*bebae9c0SAndroid Build Coastguard Worker             assert(SL_BOOLEAN_FALSE);
150*bebae9c0SAndroid Build Coastguard Worker             break;
151*bebae9c0SAndroid Build Coastguard Worker         }
152*bebae9c0SAndroid Build Coastguard Worker 
153*bebae9c0SAndroid Build Coastguard Worker broadcast:
154*bebae9c0SAndroid Build Coastguard Worker         if (doBroadcast) {
155*bebae9c0SAndroid Build Coastguard Worker             object_cond_broadcast(&audioPlayer->mObject);
156*bebae9c0SAndroid Build Coastguard Worker         }
157*bebae9c0SAndroid Build Coastguard Worker 
158*bebae9c0SAndroid Build Coastguard Worker         object_unlock_exclusive(&audioPlayer->mObject);
159*bebae9c0SAndroid Build Coastguard Worker 
160*bebae9c0SAndroid Build Coastguard Worker     }
161*bebae9c0SAndroid Build Coastguard Worker 
162*bebae9c0SAndroid Build Coastguard Worker     return trackHasData;
163*bebae9c0SAndroid Build Coastguard Worker 
164*bebae9c0SAndroid Build Coastguard Worker }
165*bebae9c0SAndroid Build Coastguard Worker 
166*bebae9c0SAndroid Build Coastguard Worker 
167*bebae9c0SAndroid Build Coastguard Worker /** \brief This is the track mixer: fill the specified 16-bit stereo PCM buffer */
168*bebae9c0SAndroid Build Coastguard Worker 
IOutputMixExt_FillBuffer(SLOutputMixExtItf self,void * pBuffer,SLuint32 size)169*bebae9c0SAndroid Build Coastguard Worker void IOutputMixExt_FillBuffer(SLOutputMixExtItf self, void *pBuffer, SLuint32 size)
170*bebae9c0SAndroid Build Coastguard Worker {
171*bebae9c0SAndroid Build Coastguard Worker     SL_ENTER_INTERFACE_VOID
172*bebae9c0SAndroid Build Coastguard Worker 
173*bebae9c0SAndroid Build Coastguard Worker     // Force to be a multiple of a frame, assumes stereo 16-bit PCM
174*bebae9c0SAndroid Build Coastguard Worker     size &= ~3;
175*bebae9c0SAndroid Build Coastguard Worker     SLboolean mixBufferHasData = SL_BOOLEAN_FALSE;
176*bebae9c0SAndroid Build Coastguard Worker     IOutputMixExt *thiz = (IOutputMixExt *) self;
177*bebae9c0SAndroid Build Coastguard Worker     IObject *thisObject = thiz->mThis;
178*bebae9c0SAndroid Build Coastguard Worker     // This lock should never block, except when the application destroys the output mix object
179*bebae9c0SAndroid Build Coastguard Worker     object_lock_exclusive(thisObject);
180*bebae9c0SAndroid Build Coastguard Worker     unsigned activeMask;
181*bebae9c0SAndroid Build Coastguard Worker     // If the output mix is marked for destruction, then acknowledge the request
182*bebae9c0SAndroid Build Coastguard Worker     if (thiz->mDestroyRequested) {
183*bebae9c0SAndroid Build Coastguard Worker         IEngine *thisEngine = &thisObject->mEngine->mEngine;
184*bebae9c0SAndroid Build Coastguard Worker         interface_lock_exclusive(thisEngine);
185*bebae9c0SAndroid Build Coastguard Worker         assert(&thisEngine->mOutputMix->mObject == thisObject);
186*bebae9c0SAndroid Build Coastguard Worker         thisEngine->mOutputMix = NULL;
187*bebae9c0SAndroid Build Coastguard Worker         // Note we don't attempt to connect another output mix, even if there is one
188*bebae9c0SAndroid Build Coastguard Worker         interface_unlock_exclusive(thisEngine);
189*bebae9c0SAndroid Build Coastguard Worker         // Acknowledge the destroy request, and notify the pre-destroy hook
190*bebae9c0SAndroid Build Coastguard Worker         thiz->mDestroyRequested = SL_BOOLEAN_FALSE;
191*bebae9c0SAndroid Build Coastguard Worker         object_cond_broadcast(thisObject);
192*bebae9c0SAndroid Build Coastguard Worker         activeMask = 0;
193*bebae9c0SAndroid Build Coastguard Worker     } else {
194*bebae9c0SAndroid Build Coastguard Worker         activeMask = thiz->mActiveMask;
195*bebae9c0SAndroid Build Coastguard Worker     }
196*bebae9c0SAndroid Build Coastguard Worker     while (activeMask) {
197*bebae9c0SAndroid Build Coastguard Worker         unsigned i = ctz(activeMask);
198*bebae9c0SAndroid Build Coastguard Worker         assert(MAX_TRACK > i);
199*bebae9c0SAndroid Build Coastguard Worker         activeMask &= ~(1 << i);
200*bebae9c0SAndroid Build Coastguard Worker         Track *track = &thiz->mTracks[i];
201*bebae9c0SAndroid Build Coastguard Worker 
202*bebae9c0SAndroid Build Coastguard Worker         // track is allocated
203*bebae9c0SAndroid Build Coastguard Worker 
204*bebae9c0SAndroid Build Coastguard Worker         if (!track_check(track)) {
205*bebae9c0SAndroid Build Coastguard Worker             continue;
206*bebae9c0SAndroid Build Coastguard Worker         }
207*bebae9c0SAndroid Build Coastguard Worker 
208*bebae9c0SAndroid Build Coastguard Worker         // track is playing
209*bebae9c0SAndroid Build Coastguard Worker         void *dstWriter = pBuffer;
210*bebae9c0SAndroid Build Coastguard Worker         unsigned desired = size;
211*bebae9c0SAndroid Build Coastguard Worker         SLboolean trackContributedToMix = SL_BOOLEAN_FALSE;
212*bebae9c0SAndroid Build Coastguard Worker         float gains[STEREO_CHANNELS];
213*bebae9c0SAndroid Build Coastguard Worker         Summary summaries[STEREO_CHANNELS];
214*bebae9c0SAndroid Build Coastguard Worker         unsigned channel;
215*bebae9c0SAndroid Build Coastguard Worker         for (channel = 0; channel < STEREO_CHANNELS; ++channel) {
216*bebae9c0SAndroid Build Coastguard Worker             float gain = track->mGains[channel];
217*bebae9c0SAndroid Build Coastguard Worker             gains[channel] = gain;
218*bebae9c0SAndroid Build Coastguard Worker             Summary summary;
219*bebae9c0SAndroid Build Coastguard Worker             if (gain <= 0.001) {
220*bebae9c0SAndroid Build Coastguard Worker                 summary = GAIN_MUTE;
221*bebae9c0SAndroid Build Coastguard Worker             } else if (gain >= 0.999) {
222*bebae9c0SAndroid Build Coastguard Worker                 summary = GAIN_UNITY;
223*bebae9c0SAndroid Build Coastguard Worker             } else {
224*bebae9c0SAndroid Build Coastguard Worker                 summary = GAIN_OTHER;
225*bebae9c0SAndroid Build Coastguard Worker             }
226*bebae9c0SAndroid Build Coastguard Worker             summaries[channel] = summary;
227*bebae9c0SAndroid Build Coastguard Worker         }
228*bebae9c0SAndroid Build Coastguard Worker         while (desired > 0) {
229*bebae9c0SAndroid Build Coastguard Worker             unsigned actual = desired;
230*bebae9c0SAndroid Build Coastguard Worker             if (track->mAvail < actual) {
231*bebae9c0SAndroid Build Coastguard Worker                 actual = track->mAvail;
232*bebae9c0SAndroid Build Coastguard Worker             }
233*bebae9c0SAndroid Build Coastguard Worker             // force actual to be a frame multiple
234*bebae9c0SAndroid Build Coastguard Worker             if (actual > 0) {
235*bebae9c0SAndroid Build Coastguard Worker                 assert(NULL != track->mReader);
236*bebae9c0SAndroid Build Coastguard Worker                 stereo *mixBuffer = (stereo *) dstWriter;
237*bebae9c0SAndroid Build Coastguard Worker                 const stereo *source = (const stereo *) track->mReader;
238*bebae9c0SAndroid Build Coastguard Worker                 unsigned j;
239*bebae9c0SAndroid Build Coastguard Worker                 if (GAIN_MUTE != summaries[0] || GAIN_MUTE != summaries[1]) {
240*bebae9c0SAndroid Build Coastguard Worker                     if (mixBufferHasData) {
241*bebae9c0SAndroid Build Coastguard Worker                         // apply gain during add
242*bebae9c0SAndroid Build Coastguard Worker                         if (GAIN_UNITY != summaries[0] || GAIN_UNITY != summaries[1]) {
243*bebae9c0SAndroid Build Coastguard Worker                             for (j = 0; j < actual; j += sizeof(stereo), ++mixBuffer, ++source) {
244*bebae9c0SAndroid Build Coastguard Worker                                 mixBuffer->left += (short) (source->left * track->mGains[0]);
245*bebae9c0SAndroid Build Coastguard Worker                                 mixBuffer->right += (short) (source->right * track->mGains[1]);
246*bebae9c0SAndroid Build Coastguard Worker                             }
247*bebae9c0SAndroid Build Coastguard Worker                         // no gain adjustment needed, so do a simple add
248*bebae9c0SAndroid Build Coastguard Worker                         } else {
249*bebae9c0SAndroid Build Coastguard Worker                             for (j = 0; j < actual; j += sizeof(stereo), ++mixBuffer, ++source) {
250*bebae9c0SAndroid Build Coastguard Worker                                 mixBuffer->left += source->left;
251*bebae9c0SAndroid Build Coastguard Worker                                 mixBuffer->right += source->right;
252*bebae9c0SAndroid Build Coastguard Worker                             }
253*bebae9c0SAndroid Build Coastguard Worker                         }
254*bebae9c0SAndroid Build Coastguard Worker                     } else {
255*bebae9c0SAndroid Build Coastguard Worker                         // apply gain during copy
256*bebae9c0SAndroid Build Coastguard Worker                         if (GAIN_UNITY != summaries[0] || GAIN_UNITY != summaries[1]) {
257*bebae9c0SAndroid Build Coastguard Worker                             for (j = 0; j < actual; j += sizeof(stereo), ++mixBuffer, ++source) {
258*bebae9c0SAndroid Build Coastguard Worker                                 mixBuffer->left = (short) (source->left * track->mGains[0]);
259*bebae9c0SAndroid Build Coastguard Worker                                 mixBuffer->right = (short) (source->right * track->mGains[1]);
260*bebae9c0SAndroid Build Coastguard Worker                             }
261*bebae9c0SAndroid Build Coastguard Worker                         // no gain adjustment needed, so do a simple copy
262*bebae9c0SAndroid Build Coastguard Worker                         } else {
263*bebae9c0SAndroid Build Coastguard Worker                             memcpy(dstWriter, track->mReader, actual);
264*bebae9c0SAndroid Build Coastguard Worker                         }
265*bebae9c0SAndroid Build Coastguard Worker                     }
266*bebae9c0SAndroid Build Coastguard Worker                     trackContributedToMix = SL_BOOLEAN_TRUE;
267*bebae9c0SAndroid Build Coastguard Worker                 }
268*bebae9c0SAndroid Build Coastguard Worker                 dstWriter = (char *) dstWriter + actual;
269*bebae9c0SAndroid Build Coastguard Worker                 desired -= actual;
270*bebae9c0SAndroid Build Coastguard Worker                 track->mReader = (char *) track->mReader + actual;
271*bebae9c0SAndroid Build Coastguard Worker                 track->mAvail -= actual;
272*bebae9c0SAndroid Build Coastguard Worker                 if (track->mAvail == 0) {
273*bebae9c0SAndroid Build Coastguard Worker                     IBufferQueue *bufferQueue = &track->mAudioPlayer->mBufferQueue;
274*bebae9c0SAndroid Build Coastguard Worker                     interface_lock_exclusive(bufferQueue);
275*bebae9c0SAndroid Build Coastguard Worker                     const BufferHeader *oldFront, *newFront, *rear;
276*bebae9c0SAndroid Build Coastguard Worker                     oldFront = bufferQueue->mFront;
277*bebae9c0SAndroid Build Coastguard Worker                     rear = bufferQueue->mRear;
278*bebae9c0SAndroid Build Coastguard Worker                     // a buffer stays on queue while playing, so it better still be there
279*bebae9c0SAndroid Build Coastguard Worker                     assert(oldFront != rear);
280*bebae9c0SAndroid Build Coastguard Worker                     newFront = oldFront;
281*bebae9c0SAndroid Build Coastguard Worker                     if (++newFront == &bufferQueue->mArray[bufferQueue->mNumBuffers + 1]) {
282*bebae9c0SAndroid Build Coastguard Worker                         newFront = bufferQueue->mArray;
283*bebae9c0SAndroid Build Coastguard Worker                     }
284*bebae9c0SAndroid Build Coastguard Worker                     bufferQueue->mFront = (BufferHeader *) newFront;
285*bebae9c0SAndroid Build Coastguard Worker                     assert(0 < bufferQueue->mState.count);
286*bebae9c0SAndroid Build Coastguard Worker                     --bufferQueue->mState.count;
287*bebae9c0SAndroid Build Coastguard Worker                     if (newFront != rear) {
288*bebae9c0SAndroid Build Coastguard Worker                         // we don't acknowledge application requests between buffers
289*bebae9c0SAndroid Build Coastguard Worker                         // within the same mixer frame
290*bebae9c0SAndroid Build Coastguard Worker                         assert(0 < bufferQueue->mState.count);
291*bebae9c0SAndroid Build Coastguard Worker                         track->mReader = newFront->mBuffer;
292*bebae9c0SAndroid Build Coastguard Worker                         track->mAvail = newFront->mSize;
293*bebae9c0SAndroid Build Coastguard Worker                     }
294*bebae9c0SAndroid Build Coastguard Worker                     // else we would set play state to playable but not playing during next mixer
295*bebae9c0SAndroid Build Coastguard Worker                     // frame if the queue is still empty at that time
296*bebae9c0SAndroid Build Coastguard Worker                     ++bufferQueue->mState.playIndex;
297*bebae9c0SAndroid Build Coastguard Worker                     slBufferQueueCallback callback = bufferQueue->mCallback;
298*bebae9c0SAndroid Build Coastguard Worker                     void *context = bufferQueue->mContext;
299*bebae9c0SAndroid Build Coastguard Worker                     interface_unlock_exclusive(bufferQueue);
300*bebae9c0SAndroid Build Coastguard Worker                     // The callback function is called on each buffer completion
301*bebae9c0SAndroid Build Coastguard Worker                     if (NULL != callback) {
302*bebae9c0SAndroid Build Coastguard Worker                         (*callback)((SLBufferQueueItf) bufferQueue, context);
303*bebae9c0SAndroid Build Coastguard Worker                         // Maybe it enqueued another buffer, or maybe it didn't.
304*bebae9c0SAndroid Build Coastguard Worker                         // We will find out later during the next mixer frame.
305*bebae9c0SAndroid Build Coastguard Worker                     }
306*bebae9c0SAndroid Build Coastguard Worker                 }
307*bebae9c0SAndroid Build Coastguard Worker                 // no lock, but safe because noone else updates this field
308*bebae9c0SAndroid Build Coastguard Worker                 track->mFramesMixed += actual >> 2;    // sizeof(short) * STEREO_CHANNELS
309*bebae9c0SAndroid Build Coastguard Worker                 continue;
310*bebae9c0SAndroid Build Coastguard Worker             }
311*bebae9c0SAndroid Build Coastguard Worker             // we need more data: desired > 0 but actual == 0
312*bebae9c0SAndroid Build Coastguard Worker             if (track_check(track)) {
313*bebae9c0SAndroid Build Coastguard Worker                 continue;
314*bebae9c0SAndroid Build Coastguard Worker             }
315*bebae9c0SAndroid Build Coastguard Worker             // underflow: clear out rest of partial buffer (NTH synthesize comfort noise)
316*bebae9c0SAndroid Build Coastguard Worker             if (!mixBufferHasData && trackContributedToMix) {
317*bebae9c0SAndroid Build Coastguard Worker                 memset(dstWriter, 0, actual);
318*bebae9c0SAndroid Build Coastguard Worker             }
319*bebae9c0SAndroid Build Coastguard Worker             break;
320*bebae9c0SAndroid Build Coastguard Worker         }
321*bebae9c0SAndroid Build Coastguard Worker         if (trackContributedToMix) {
322*bebae9c0SAndroid Build Coastguard Worker             mixBufferHasData = SL_BOOLEAN_TRUE;
323*bebae9c0SAndroid Build Coastguard Worker         }
324*bebae9c0SAndroid Build Coastguard Worker     }
325*bebae9c0SAndroid Build Coastguard Worker     object_unlock_exclusive(thisObject);
326*bebae9c0SAndroid Build Coastguard Worker     // No active tracks, so output silence
327*bebae9c0SAndroid Build Coastguard Worker     if (!mixBufferHasData) {
328*bebae9c0SAndroid Build Coastguard Worker         memset(pBuffer, 0, size);
329*bebae9c0SAndroid Build Coastguard Worker     }
330*bebae9c0SAndroid Build Coastguard Worker 
331*bebae9c0SAndroid Build Coastguard Worker     SL_LEAVE_INTERFACE_VOID
332*bebae9c0SAndroid Build Coastguard Worker }
333*bebae9c0SAndroid Build Coastguard Worker 
334*bebae9c0SAndroid Build Coastguard Worker 
335*bebae9c0SAndroid Build Coastguard Worker static const struct SLOutputMixExtItf_ IOutputMixExt_Itf = {
336*bebae9c0SAndroid Build Coastguard Worker     IOutputMixExt_FillBuffer
337*bebae9c0SAndroid Build Coastguard Worker };
338*bebae9c0SAndroid Build Coastguard Worker 
IOutputMixExt_init(void * self)339*bebae9c0SAndroid Build Coastguard Worker void IOutputMixExt_init(void *self)
340*bebae9c0SAndroid Build Coastguard Worker {
341*bebae9c0SAndroid Build Coastguard Worker     IOutputMixExt *thiz = (IOutputMixExt *) self;
342*bebae9c0SAndroid Build Coastguard Worker     thiz->mItf = &IOutputMixExt_Itf;
343*bebae9c0SAndroid Build Coastguard Worker     thiz->mActiveMask = 0;
344*bebae9c0SAndroid Build Coastguard Worker     Track *track = &thiz->mTracks[0];
345*bebae9c0SAndroid Build Coastguard Worker     unsigned i;
346*bebae9c0SAndroid Build Coastguard Worker     for (i = 0; i < MAX_TRACK; ++i, ++track) {
347*bebae9c0SAndroid Build Coastguard Worker         track->mAudioPlayer = NULL;
348*bebae9c0SAndroid Build Coastguard Worker     }
349*bebae9c0SAndroid Build Coastguard Worker     thiz->mDestroyRequested = SL_BOOLEAN_FALSE;
350*bebae9c0SAndroid Build Coastguard Worker }
351*bebae9c0SAndroid Build Coastguard Worker 
352*bebae9c0SAndroid Build Coastguard Worker 
353*bebae9c0SAndroid Build Coastguard Worker /** \brief Called by Engine::CreateAudioPlayer to allocate a track */
354*bebae9c0SAndroid Build Coastguard Worker 
IOutputMixExt_checkAudioPlayerSourceSink(CAudioPlayer * thiz)355*bebae9c0SAndroid Build Coastguard Worker SLresult IOutputMixExt_checkAudioPlayerSourceSink(CAudioPlayer *thiz)
356*bebae9c0SAndroid Build Coastguard Worker {
357*bebae9c0SAndroid Build Coastguard Worker     thiz->mTrack = NULL;
358*bebae9c0SAndroid Build Coastguard Worker 
359*bebae9c0SAndroid Build Coastguard Worker     // check the source for compatibility
360*bebae9c0SAndroid Build Coastguard Worker     switch (thiz->mDataSource.mLocator.mLocatorType) {
361*bebae9c0SAndroid Build Coastguard Worker     case SL_DATALOCATOR_BUFFERQUEUE:
362*bebae9c0SAndroid Build Coastguard Worker #ifdef ANDROID
363*bebae9c0SAndroid Build Coastguard Worker     case SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE:
364*bebae9c0SAndroid Build Coastguard Worker #endif
365*bebae9c0SAndroid Build Coastguard Worker         switch (thiz->mDataSource.mFormat.mFormatType) {
366*bebae9c0SAndroid Build Coastguard Worker         case SL_DATAFORMAT_PCM:
367*bebae9c0SAndroid Build Coastguard Worker #ifdef USE_SDL
368*bebae9c0SAndroid Build Coastguard Worker             // SDL is hard-coded to 44.1 kHz, and there is no sample rate converter
369*bebae9c0SAndroid Build Coastguard Worker             if (SL_SAMPLINGRATE_44_1 != thiz->mDataSource.mFormat.mPCM.samplesPerSec)
370*bebae9c0SAndroid Build Coastguard Worker                 return SL_RESULT_CONTENT_UNSUPPORTED;
371*bebae9c0SAndroid Build Coastguard Worker #endif
372*bebae9c0SAndroid Build Coastguard Worker             break;
373*bebae9c0SAndroid Build Coastguard Worker         default:
374*bebae9c0SAndroid Build Coastguard Worker             break;
375*bebae9c0SAndroid Build Coastguard Worker         }
376*bebae9c0SAndroid Build Coastguard Worker         break;
377*bebae9c0SAndroid Build Coastguard Worker     default:
378*bebae9c0SAndroid Build Coastguard Worker         break;
379*bebae9c0SAndroid Build Coastguard Worker     }
380*bebae9c0SAndroid Build Coastguard Worker 
381*bebae9c0SAndroid Build Coastguard Worker     // check the sink for compatibility
382*bebae9c0SAndroid Build Coastguard Worker     const SLDataSink *pAudioSnk = &thiz->mDataSink.u.mSink;
383*bebae9c0SAndroid Build Coastguard Worker     Track *track = NULL;
384*bebae9c0SAndroid Build Coastguard Worker     switch (*(SLuint32 *)pAudioSnk->pLocator) {
385*bebae9c0SAndroid Build Coastguard Worker     case SL_DATALOCATOR_OUTPUTMIX:
386*bebae9c0SAndroid Build Coastguard Worker         {
387*bebae9c0SAndroid Build Coastguard Worker         // pAudioSnk->pFormat is ignored
388*bebae9c0SAndroid Build Coastguard Worker         IOutputMixExt *omExt = &((COutputMix *) ((SLDataLocator_OutputMix *)
389*bebae9c0SAndroid Build Coastguard Worker             pAudioSnk->pLocator)->outputMix)->mOutputMixExt;
390*bebae9c0SAndroid Build Coastguard Worker         // allocate an entry within OutputMix for this track
391*bebae9c0SAndroid Build Coastguard Worker         interface_lock_exclusive(omExt);
392*bebae9c0SAndroid Build Coastguard Worker         unsigned availMask = ~omExt->mActiveMask;
393*bebae9c0SAndroid Build Coastguard Worker         if (!availMask) {
394*bebae9c0SAndroid Build Coastguard Worker             interface_unlock_exclusive(omExt);
395*bebae9c0SAndroid Build Coastguard Worker             // All track slots full in output mix
396*bebae9c0SAndroid Build Coastguard Worker             return SL_RESULT_MEMORY_FAILURE;
397*bebae9c0SAndroid Build Coastguard Worker         }
398*bebae9c0SAndroid Build Coastguard Worker         unsigned i = ctz(availMask);
399*bebae9c0SAndroid Build Coastguard Worker         assert(MAX_TRACK > i);
400*bebae9c0SAndroid Build Coastguard Worker         omExt->mActiveMask |= 1 << i;
401*bebae9c0SAndroid Build Coastguard Worker         track = &omExt->mTracks[i];
402*bebae9c0SAndroid Build Coastguard Worker         track->mAudioPlayer = NULL;    // only field that is accessed before full initialization
403*bebae9c0SAndroid Build Coastguard Worker         interface_unlock_exclusive(omExt);
404*bebae9c0SAndroid Build Coastguard Worker         thiz->mTrack = track;
405*bebae9c0SAndroid Build Coastguard Worker         thiz->mGains[0] = 1.0f;
406*bebae9c0SAndroid Build Coastguard Worker         thiz->mGains[1] = 1.0f;
407*bebae9c0SAndroid Build Coastguard Worker         thiz->mDestroyRequested = SL_BOOLEAN_FALSE;
408*bebae9c0SAndroid Build Coastguard Worker         }
409*bebae9c0SAndroid Build Coastguard Worker         break;
410*bebae9c0SAndroid Build Coastguard Worker     default:
411*bebae9c0SAndroid Build Coastguard Worker         return SL_RESULT_CONTENT_UNSUPPORTED;
412*bebae9c0SAndroid Build Coastguard Worker     }
413*bebae9c0SAndroid Build Coastguard Worker 
414*bebae9c0SAndroid Build Coastguard Worker     assert(NULL != track);
415*bebae9c0SAndroid Build Coastguard Worker     track->mBufferQueue = &thiz->mBufferQueue;
416*bebae9c0SAndroid Build Coastguard Worker     track->mAudioPlayer = thiz;
417*bebae9c0SAndroid Build Coastguard Worker     track->mReader = NULL;
418*bebae9c0SAndroid Build Coastguard Worker     track->mAvail = 0;
419*bebae9c0SAndroid Build Coastguard Worker     track->mGains[0] = 1.0f;
420*bebae9c0SAndroid Build Coastguard Worker     track->mGains[1] = 1.0f;
421*bebae9c0SAndroid Build Coastguard Worker     track->mFramesMixed = 0;
422*bebae9c0SAndroid Build Coastguard Worker     return SL_RESULT_SUCCESS;
423*bebae9c0SAndroid Build Coastguard Worker }
424*bebae9c0SAndroid Build Coastguard Worker 
425*bebae9c0SAndroid Build Coastguard Worker 
426*bebae9c0SAndroid Build Coastguard Worker /** \brief Called when a gain-related field (mute, solo, volume, stereo position, etc.) updated */
427*bebae9c0SAndroid Build Coastguard Worker 
audioPlayerGainUpdate(CAudioPlayer * audioPlayer)428*bebae9c0SAndroid Build Coastguard Worker void audioPlayerGainUpdate(CAudioPlayer *audioPlayer)
429*bebae9c0SAndroid Build Coastguard Worker {
430*bebae9c0SAndroid Build Coastguard Worker     SLboolean mute = audioPlayer->mVolume.mMute;
431*bebae9c0SAndroid Build Coastguard Worker     SLuint8 muteMask = audioPlayer->mMuteMask;
432*bebae9c0SAndroid Build Coastguard Worker     SLuint8 soloMask = audioPlayer->mSoloMask;
433*bebae9c0SAndroid Build Coastguard Worker     SLmillibel level = audioPlayer->mVolume.mLevel;
434*bebae9c0SAndroid Build Coastguard Worker     SLboolean enableStereoPosition = audioPlayer->mVolume.mEnableStereoPosition;
435*bebae9c0SAndroid Build Coastguard Worker     SLpermille stereoPosition = audioPlayer->mVolume.mStereoPosition;
436*bebae9c0SAndroid Build Coastguard Worker 
437*bebae9c0SAndroid Build Coastguard Worker     if (soloMask) {
438*bebae9c0SAndroid Build Coastguard Worker         muteMask |= ~soloMask;
439*bebae9c0SAndroid Build Coastguard Worker     }
440*bebae9c0SAndroid Build Coastguard Worker     if (mute || !(~muteMask & 3)) {
441*bebae9c0SAndroid Build Coastguard Worker         audioPlayer->mGains[0] = 0.0f;
442*bebae9c0SAndroid Build Coastguard Worker         audioPlayer->mGains[1] = 0.0f;
443*bebae9c0SAndroid Build Coastguard Worker     } else {
444*bebae9c0SAndroid Build Coastguard Worker         float playerGain = powf(10.0f, level / 2000.0f);
445*bebae9c0SAndroid Build Coastguard Worker         unsigned channel;
446*bebae9c0SAndroid Build Coastguard Worker         for (channel = 0; channel < STEREO_CHANNELS; ++channel) {
447*bebae9c0SAndroid Build Coastguard Worker             float gain;
448*bebae9c0SAndroid Build Coastguard Worker             if (muteMask & (1 << channel)) {
449*bebae9c0SAndroid Build Coastguard Worker                 gain = 0.0f;
450*bebae9c0SAndroid Build Coastguard Worker             } else {
451*bebae9c0SAndroid Build Coastguard Worker                 gain = playerGain;
452*bebae9c0SAndroid Build Coastguard Worker                 if (enableStereoPosition) {
453*bebae9c0SAndroid Build Coastguard Worker                     switch (channel) {
454*bebae9c0SAndroid Build Coastguard Worker                     case 0:
455*bebae9c0SAndroid Build Coastguard Worker                         if (stereoPosition > 0) {
456*bebae9c0SAndroid Build Coastguard Worker                             gain *= (1000 - stereoPosition) / 1000.0f;
457*bebae9c0SAndroid Build Coastguard Worker                         }
458*bebae9c0SAndroid Build Coastguard Worker                         break;
459*bebae9c0SAndroid Build Coastguard Worker                     case 1:
460*bebae9c0SAndroid Build Coastguard Worker                         if (stereoPosition < 0) {
461*bebae9c0SAndroid Build Coastguard Worker                             gain *= (1000 + stereoPosition) / 1000.0f;
462*bebae9c0SAndroid Build Coastguard Worker                         }
463*bebae9c0SAndroid Build Coastguard Worker                         break;
464*bebae9c0SAndroid Build Coastguard Worker                     default:
465*bebae9c0SAndroid Build Coastguard Worker                         assert(SL_BOOLEAN_FALSE);
466*bebae9c0SAndroid Build Coastguard Worker                         break;
467*bebae9c0SAndroid Build Coastguard Worker                     }
468*bebae9c0SAndroid Build Coastguard Worker                 }
469*bebae9c0SAndroid Build Coastguard Worker             }
470*bebae9c0SAndroid Build Coastguard Worker             audioPlayer->mGains[channel] = gain;
471*bebae9c0SAndroid Build Coastguard Worker         }
472*bebae9c0SAndroid Build Coastguard Worker     }
473*bebae9c0SAndroid Build Coastguard Worker }
474