• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 **
3 ** Copyright 2008, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 **     http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17 
18 //#define LOG_NDEBUG 0
19 #define LOG_TAG "AudioOutput"
20 #include <utils/Log.h>
21 
22 #include "android_audio_output.h"
23 
24 #include <sys/prctl.h>
25 #include <sys/resource.h>
26 #include <utils/threads.h>
27 #include <media/AudioTrack.h>
28 
29 using namespace android;
30 
31 // TODO: dynamic buffer count based on sample rate and # channels
32 static const int kNumOutputBuffers = 4;
33 
34 // maximum allowed clock drift before correction
35 static const int32 kMaxClockDriftInMsecs = 25;    // should be tight enough for reasonable sync
36 static const int32 kMaxClockCorrection = 100;     // maximum clock correction per update
37 
38 /*
39 / Packet Video Audio MIO component
40 /
41 / This implementation routes audio to AudioFlinger. Audio buffers are
42 / enqueued in a message queue to a separate audio output thread. Once
43 / the buffers have been successfully written, they are returned through
44 / another message queue to the MIO and from there back to the engine.
45 / This separation is necessary because most of the PV API is not
46 / thread-safe.
47 */
AndroidAudioOutput()48 OSCL_EXPORT_REF AndroidAudioOutput::AndroidAudioOutput() :
49     AndroidAudioMIO("AndroidAudioOutput"),
50     iExitAudioThread(false),
51     iReturnBuffers(false),
52     iActiveTiming(NULL)
53 {
54     LOGV("constructor");
55     iClockTimeOfWriting_ns = 0;
56     iInputFrameSizeInBytes = 0;
57 
58     // semaphore used to communicate between this  mio and the audio output thread
59     iAudioThreadSem = new OsclSemaphore();
60     iAudioThreadSem->Create(0);
61     iAudioThreadTermSem = new OsclSemaphore();
62     iAudioThreadTermSem->Create(0);
63     iAudioThreadReturnSem = new OsclSemaphore();
64     iAudioThreadReturnSem->Create(0);
65     iAudioThreadCreatedSem = new OsclSemaphore();
66     iAudioThreadCreatedSem->Create(0);
67 
68     // locks to access the queues by this mio and by the audio output thread
69     iOSSRequestQueueLock.Create();
70     iOSSRequestQueue.reserve(iWriteResponseQueue.capacity());
71 
72     // create active timing object
73     OsclMemAllocator alloc;
74     OsclAny*ptr=alloc.allocate(sizeof(AndroidAudioMIOActiveTimingSupport));
75     if (ptr) {
76         iActiveTiming=new(ptr)AndroidAudioMIOActiveTimingSupport(kMaxClockDriftInMsecs, kMaxClockCorrection);
77         iActiveTiming->setThreadSemaphore(iAudioThreadSem);
78     }
79 }
80 
~AndroidAudioOutput()81 OSCL_EXPORT_REF AndroidAudioOutput::~AndroidAudioOutput()
82 {
83     LOGV("destructor");
84 
85     // make sure output thread has exited
86     RequestAndWaitForThreadExit();
87 
88     // cleanup active timing object
89     if (iActiveTiming) {
90         iActiveTiming->~AndroidAudioMIOActiveTimingSupport();
91         OsclMemAllocator alloc;
92         alloc.deallocate(iActiveTiming);
93     }
94 
95     // clean up some thread interface objects
96     iAudioThreadSem->Close();
97     delete iAudioThreadSem;
98     iAudioThreadTermSem->Close();
99     delete iAudioThreadTermSem;
100     iAudioThreadReturnSem->Close();
101     delete iAudioThreadReturnSem;
102     iAudioThreadCreatedSem->Close();
103     delete iAudioThreadCreatedSem;
104 
105     iOSSRequestQueueLock.Close();
106 }
107 
QueryInterface(const PVUuid & aUuid,PVInterface * & aInterfacePtr,const OsclAny * aContext)108 PVMFCommandId AndroidAudioOutput::QueryInterface(const PVUuid& aUuid, PVInterface*& aInterfacePtr, const OsclAny* aContext)
109 {
110     LOGV("QueryInterface in");
111     // check for active timing extension
112     if (iActiveTiming && (aUuid == PvmiClockExtensionInterfaceUuid)) {
113         PvmiClockExtensionInterface* myInterface = OSCL_STATIC_CAST(PvmiClockExtensionInterface*,iActiveTiming);
114         aInterfacePtr = OSCL_STATIC_CAST(PVInterface*, myInterface);
115         return QueueCmdResponse(PVMFSuccess, aContext);
116     }
117 
118     // pass to base class
119     else return AndroidAudioMIO::QueryInterface(aUuid, aInterfacePtr, aContext);
120 }
121 
QueryUUID(const PvmfMimeString & aMimeType,Oscl_Vector<PVUuid,OsclMemAllocator> & aUuids,bool aExactUuidsOnly,const OsclAny * aContext)122 PVMFCommandId AndroidAudioOutput::QueryUUID(const PvmfMimeString& aMimeType,
123                                         Oscl_Vector<PVUuid, OsclMemAllocator>& aUuids,
124                                         bool aExactUuidsOnly, const OsclAny* aContext)
125 {
126     LOGV("QueryUUID in");
127     int32 err;
128     OSCL_TRY(err,
129             aUuids.push_back(PVMI_CAPABILITY_AND_CONFIG_PVUUID);
130             if (iActiveTiming) {
131             PVUuid uuid;
132             iActiveTiming->queryUuid(uuid);
133             aUuids.push_back(uuid);
134             }
135             );
136     return QueueCmdResponse(err == OsclErrNone ? PVMFSuccess : PVMFFailure, aContext);
137 }
138 
Stop(const OsclAny * aContext)139 PVMFCommandId AndroidAudioOutput::Stop(const OsclAny* aContext)
140 {
141     LOGV("AndroidAudioOutput Stop (%p)", aContext);
142     // return all buffer by writecomplete
143     returnAllBuffers();
144     return AndroidAudioMIO::Stop(aContext);
145 }
146 
Reset(const OsclAny * aContext)147 PVMFCommandId AndroidAudioOutput::Reset(const OsclAny* aContext)
148 {
149     LOGV("AndroidAudioOutput Reset (%p)", aContext);
150     // return all buffer by writecomplete
151     returnAllBuffers();
152     // request output thread to exit
153     RequestAndWaitForThreadExit();
154     return AndroidAudioMIO::Reset(aContext);
155 }
156 
cancelCommand(PVMFCommandId command_id)157 void AndroidAudioOutput::cancelCommand(PVMFCommandId command_id)
158 {
159     LOGV("cancelCommand (%u) RequestQ size %d", command_id,iOSSRequestQueue.size());
160     iOSSRequestQueueLock.Lock();
161     for (uint32 i = 0; i < iOSSRequestQueue.size(); i++) {
162         if (iOSSRequestQueue[i].iCmdId == command_id) {
163             iDataQueued -= iOSSRequestQueue[i].iDataLen;
164             if (iPeer)
165                 iPeer->writeComplete(PVMFSuccess, iOSSRequestQueue[i].iCmdId, (OsclAny*)iOSSRequestQueue[i].iContext);
166             iOSSRequestQueue.erase(&iOSSRequestQueue[i]);
167             break;
168         }
169     }
170     iOSSRequestQueueLock.Unlock();
171     LOGV("cancelCommand data queued = %u", iDataQueued);
172 
173     ProcessWriteResponseQueue();
174 }
175 
returnAllBuffers()176 void AndroidAudioOutput::returnAllBuffers()
177 {
178     LOGV("returnAllBuffers RequestQ size %d",iOSSRequestQueue.size());
179     iOSSRequestQueueLock.Lock();
180     while (iOSSRequestQueue.size()) {
181         iDataQueued -= iOSSRequestQueue[0].iDataLen;
182         if (iPeer)
183             iPeer->writeComplete(PVMFSuccess, iOSSRequestQueue[0].iCmdId, (OsclAny*)iOSSRequestQueue[0].iContext);
184         iOSSRequestQueue.erase(&iOSSRequestQueue[0]);
185     }
186     iOSSRequestQueueLock.Unlock();
187     LOGV("returnAllBuffers data queued = %u", iDataQueued);
188     if (iAudioThreadSem && iAudioThreadCreatedAndMIOConfigured) {
189         LOGV("signal thread to return buffers");
190         iReturnBuffers = true;
191         iAudioThreadSem->Signal();
192         while (iAudioThreadReturnSem->Wait() != OsclProcStatus::SUCCESS_ERROR)
193             ;
194         LOGV("return buffers signal completed");
195     }
196 }
197 
198 
DiscardData(PVMFTimestamp aTimestamp,const OsclAny * aContext)199 PVMFCommandId AndroidAudioOutput::DiscardData(PVMFTimestamp aTimestamp, const OsclAny* aContext)
200 {
201     LOGV("DiscardData timestamp(%u) RequestQ size %d", aTimestamp,iOSSRequestQueue.size());
202 
203     if(iActiveTiming){
204         LOGV("Force clock update");
205         iActiveTiming->ForceClockUpdate();
206     }
207 
208     bool sched = false;
209     PVMFCommandId audcmdid;
210     const OsclAny* context;
211     PVMFTimestamp timestamp;
212 
213     // the OSSRequest queue should be drained
214     // all the buffers in them should be returned to the engine
215     // writeComplete cannot be called from here
216     // thus the best way is to queue the buffers onto the write response queue
217     // and then call RunIfNotReady
218     iOSSRequestQueueLock.Lock();
219     for (int32 i = (iOSSRequestQueue.size() - 1); i >= 0; i--) {
220         if (iOSSRequestQueue[i].iTimestamp < aTimestamp) {
221             audcmdid = iOSSRequestQueue[i].iCmdId;
222             context = iOSSRequestQueue[i].iContext;
223             timestamp = iOSSRequestQueue[i].iTimestamp;
224             iDataQueued -= iOSSRequestQueue[i].iDataLen;
225             LOGV("discard buffer (%d) context(%p) timestamp(%u) Datalen(%d)", audcmdid,context, timestamp,iOSSRequestQueue[i].iDataLen);
226             iOSSRequestQueue.erase(&iOSSRequestQueue[i]);
227             sched = true;
228 
229             WriteResponse resp(PVMFSuccess, audcmdid, context, timestamp);
230             iWriteResponseQueueLock.Lock();
231             iWriteResponseQueue.push_back(resp);
232             iWriteResponseQueueLock.Unlock();
233         }
234     }
235     LOGV("DiscardData data queued = %u, setting flush pending", iDataQueued);
236     iFlushPending=true;
237 
238     iOSSRequestQueueLock.Unlock();
239 
240     if (sched)
241         RunIfNotReady();
242 
243     return AndroidAudioMIO::DiscardData(aTimestamp, aContext);
244 }
245 
RequestAndWaitForThreadExit()246 void AndroidAudioOutput::RequestAndWaitForThreadExit()
247 {
248     LOGV("RequestAndWaitForThreadExit In");
249     if (iAudioThreadSem && iAudioThreadCreatedAndMIOConfigured) {
250         LOGV("signal thread for exit");
251         iExitAudioThread = true;
252         iAudioThreadSem->Signal();
253         while (iAudioThreadTermSem->Wait() != OsclProcStatus::SUCCESS_ERROR)
254             ;
255         LOGV("thread term signal received");
256         iAudioThreadCreatedAndMIOConfigured = false;
257     }
258 }
259 
setParametersSync(PvmiMIOSession aSession,PvmiKvp * aParameters,int num_elements,PvmiKvp * & aRet_kvp)260 void AndroidAudioOutput::setParametersSync(PvmiMIOSession aSession, PvmiKvp* aParameters,
261                                         int num_elements, PvmiKvp * & aRet_kvp)
262 {
263     LOGV("AndroidAudioOutput setParametersSync In");
264     AndroidAudioMIO::setParametersSync(aSession, aParameters, num_elements, aRet_kvp);
265 
266     // initialize thread when we have enough information
267     if (iAudioSamplingRateValid && iAudioNumChannelsValid && iAudioFormat != PVMF_MIME_FORMAT_UNKNOWN) {
268         LOGV("start audio thread");
269         OsclThread AudioOutput_Thread;
270         iExitAudioThread = false;
271         iReturnBuffers = false;
272         OsclProcStatus::eOsclProcError ret = AudioOutput_Thread.Create((TOsclThreadFuncPtr)start_audout_thread_func,
273                                                     0, (TOsclThreadFuncArg)this, Start_on_creation);
274 
275         //Don't signal the MIO node that the configuration is complete until the driver latency has been set
276         while (iAudioThreadCreatedSem->Wait() != OsclProcStatus::SUCCESS_ERROR)
277            ;
278 
279         if(OsclProcStatus::SUCCESS_ERROR == ret){
280             iAudioThreadCreatedAndMIOConfigured = true;
281             if(iObserver){
282                 LOGV("event PVMFMIOConfigurationComplete to peer");
283                 iObserver->ReportInfoEvent(PVMFMIOConfigurationComplete);
284             }
285         }
286         else{
287             iAudioThreadCreatedAndMIOConfigured = false;
288             if(iObserver){
289                 LOGE("event PVMFErrResourceConfiguration to peer");
290                 iObserver->ReportErrorEvent(PVMFErrResourceConfiguration);
291             }
292         }
293     }
294     LOGV("AndroidAudioOutput setParametersSync out");
295 }
296 
Run()297 void AndroidAudioOutput::Run()
298 {
299     // if running, update clock
300     if ((iState == STATE_MIO_STARTED) && iInputFrameSizeInBytes) {
301         uint32 msecsQueued = iDataQueued / iInputFrameSizeInBytes * iActiveTiming->msecsPerFrame();
302         LOGV("%u msecs of data queued, %u bytes of data queued", msecsQueued,iDataQueued);
303         iActiveTiming->UpdateClock();
304     }
305     AndroidAudioMIO::Run();
306 }
307 
writeAudioBuffer(uint8 * aData,uint32 aDataLen,PVMFCommandId cmdId,OsclAny * aContext,PVMFTimestamp aTimestamp)308 void AndroidAudioOutput::writeAudioBuffer(uint8* aData, uint32 aDataLen, PVMFCommandId cmdId, OsclAny* aContext, PVMFTimestamp aTimestamp)
309 {
310     // queue up buffer and signal audio thread to process it
311     LOGV("writeAudioBuffer :: DataLen(%d), cmdId(%d), Context(%p), Timestamp (%d)",aDataLen, cmdId, aContext, aTimestamp);
312     OSSRequest req(aData, aDataLen, cmdId, aContext, aTimestamp);
313     iOSSRequestQueueLock.Lock();
314     iOSSRequestQueue.push_back(req);
315     iDataQueued += aDataLen;
316 
317     // wake up the audio output thread to process this buffer only if clock has started running
318     if (iActiveTiming->clockState() == PVMFMediaClock::RUNNING) {
319         LOGV("clock is ticking signal thread for data");
320         iAudioThreadSem->Signal();
321     }
322     iOSSRequestQueueLock.Unlock();
323 }
324 
325 //------------------------------------------------------------------------
326 // audio thread
327 //
328 
329 #undef LOG_TAG
330 #define LOG_TAG "audiothread"
331 
332 // this is the audio output thread
333 // used to send data to the linux audio output device
334 // communicates with the audio MIO via a semaphore, a request queue and a response queue
start_audout_thread_func(TOsclThreadFuncArg arg)335 /*static*/ int AndroidAudioOutput::start_audout_thread_func(TOsclThreadFuncArg arg)
336 {
337     LOGV("start_audout_thread_func in");
338     AndroidAudioOutput *obj = (AndroidAudioOutput *)arg;
339     prctl(PR_SET_NAME, (unsigned long) "audio out", 0, 0, 0);
340     int err = obj->audout_thread_func();
341     LOGV("start_audout_thread_func out return code %d",err);
342     return err;
343 }
344 
audout_thread_func()345 int AndroidAudioOutput::audout_thread_func()
346 {
347     enum { IDLE, STOPPED, STARTED, PAUSED } state = IDLE;
348     int64_t lastClock = 0;
349 
350     // LOGD("audout_thread_func");
351 
352 #if defined(HAVE_SCHED_SETSCHEDULER) && defined(HAVE_GETTID)
353     setpriority(PRIO_PROCESS, gettid(), ANDROID_PRIORITY_AUDIO);
354 #endif
355 
356     if (iAudioNumChannelsValid == false || iAudioSamplingRateValid == false || iAudioFormat == PVMF_MIME_FORMAT_UNKNOWN) {
357         LOGE("channel count or sample rate is invalid");
358         return -1;
359     }
360 
361     LOGV("Creating AudioTrack object: rate=%d, channels=%d, buffers=%d", iAudioSamplingRate, iAudioNumChannels, kNumOutputBuffers);
362     status_t ret = mAudioSink->open(iAudioSamplingRate, iAudioNumChannels, ((iAudioFormat==PVMF_MIME_PCM8)?AudioSystem::PCM_8_BIT:AudioSystem::PCM_16_BIT), kNumOutputBuffers);
363     iAudioSamplingRateValid = false; // purpose of these flags is over here, reset these for next validation recording.
364     iAudioNumChannelsValid  = false;
365     iAudioFormat = PVMF_MIME_FORMAT_UNKNOWN;
366     if (ret != 0) {
367         iAudioThreadCreatedAndMIOConfigured = false;
368         LOGE("Error creating AudioTrack");
369         return -1;
370     }
371 
372     // calculate timing data
373     int outputFrameSizeInBytes = mAudioSink->frameSize();
374     float msecsPerFrame = mAudioSink->msecsPerFrame();
375     uint32 latency = mAudioSink->latency();
376     LOGV("driver latency(%u),outputFrameSizeInBytes(%d),msecsPerFrame(%f),frame count(%d)", latency,outputFrameSizeInBytes,msecsPerFrame,mAudioSink->frameCount());
377 
378     // initialize active timing
379     iActiveTiming->setFrameRate(msecsPerFrame);
380     iActiveTiming->setDriverLatency(latency);
381 
382     iAudioThreadCreatedSem->Signal();
383     // this must be set after iActiveTiming->setFrameRate to prevent race
384     // condition in Run()
385     iInputFrameSizeInBytes = outputFrameSizeInBytes;
386 
387     // buffer management
388     uint32 bytesAvailInBuffer = 0;
389     uint32 bytesToWrite;
390     uint32 bytesWritten;
391     uint8* data = 0;
392     uint32 len = 0;
393     PVMFCommandId cmdid = 0;
394     const OsclAny* context = 0;
395     PVMFTimestamp timestamp = 0;
396 
397     // wait for signal from MIO thread
398     LOGV("wait for signal");
399     iAudioThreadSem->Wait();
400     LOGV("ready to work");
401 
402     while (1)
403     {
404         // if paused, stop the output track
405         switch (iActiveTiming->clockState()) {
406         case PVMFMediaClock::RUNNING:
407             // start output
408             if (state != STARTED) {
409                 if (iFlushPending) {
410                     LOGV("flush");
411                     mAudioSink->flush();
412                     iFlushPending = false;
413                     bytesAvailInBuffer = 0;
414                     iClockTimeOfWriting_ns = 0;
415                     // discard partial buffer and send response to MIO
416                     if (data && len) {
417                         LOGV("discard partial buffer and send response to MIO");
418                         sendResponse(cmdid, context, timestamp);
419                         data = 0;
420                         len = 0;
421                     }
422                 }
423                 if (iDataQueued || len) {
424                     LOGV("start");
425                     mAudioSink->start();
426                     state = STARTED;
427                 } else {
428                     LOGV("clock running and no data queued - don't start track");
429                 }
430             }
431             else{
432                 LOGV("audio sink already in started state");
433             }
434             break;
435         case PVMFMediaClock::STOPPED:
436              LOGV("clock has been stopped...");
437         case PVMFMediaClock::PAUSED:
438             if (state == STARTED) {
439                 LOGV("pause");
440                 mAudioSink->pause();
441             }
442             state = PAUSED;
443             if(!iExitAudioThread && !iReturnBuffers) {
444                 LOGV("wait");
445                 iAudioThreadSem->Wait();
446                 LOGV("awake");
447             }
448             break;
449         default:
450             break;
451         }
452         // if out of data, check the request queue
453         if (len == 0) {
454             //LOGV("no playable data, Request Q size %d",iOSSRequestQueue.size());
455             iOSSRequestQueueLock.Lock();
456             bool empty = iOSSRequestQueue.empty();
457             if (!empty) {
458                 data = iOSSRequestQueue[0].iData;
459                 len = iOSSRequestQueue[0].iDataLen;
460                 cmdid = iOSSRequestQueue[0].iCmdId;
461                 context = iOSSRequestQueue[0].iContext;
462                 timestamp = iOSSRequestQueue[0].iTimestamp;
463                 iDataQueued -= len;
464                 iOSSRequestQueue.erase(&iOSSRequestQueue[0]);
465                 LOGV("receive buffer (%d), timestamp = %u data queued = %u", cmdid, timestamp,iDataQueued);
466             }
467             iOSSRequestQueueLock.Unlock();
468 
469             // if queue is empty, wait for more work
470             // FIXME: Why do end up here so many times when stopping?
471             if (empty && !iExitAudioThread && !iReturnBuffers) {
472                 LOGV("queue is empty, wait for more work");
473                 iAudioThreadSem->Wait();
474             }
475 
476             // empty buffer means "End-Of-Stream" - send response to MIO
477             else if (len == 0) {
478                 LOGV("EOS");
479                 state = STOPPED;
480                 mAudioSink->stop();
481                 if(!iExitAudioThread){
482                     nsecs_t interval_nanosec = 0; // Interval between last writetime and EOS processing time in nanosec
483                     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
484                     LOGV("now = %lld ,iClockTimeOfWriting_ns = %lld",now, iClockTimeOfWriting_ns);
485                     if(now >= iClockTimeOfWriting_ns){
486                         interval_nanosec = now - iClockTimeOfWriting_ns;
487                     }
488                     else{ //when timevalue wraps
489                          interval_nanosec = 0;
490                     }
491                     LOGV(" I am early,going for sleep for latency = %u millsec, interval_nanosec = %lld",latency, interval_nanosec);
492                     struct timespec requested_time_delay, remaining;
493                     requested_time_delay.tv_sec = latency/1000;
494                     nsecs_t latency_nanosec = (latency%1000)*1000*1000;
495                     if(interval_nanosec < latency_nanosec){
496                         requested_time_delay.tv_nsec = latency_nanosec - interval_nanosec;
497                         nanosleep (&requested_time_delay, &remaining);
498                         LOGV(" Wow, what a great siesta....send response to engine");
499                     }
500                     else{// interval is greater than latency so no need of sleep
501                         LOGV(" No time to sleep :( send response to engine anyways");
502                     }
503                     iClockTimeOfWriting_ns = 0;
504                     sendResponse(cmdid, context, timestamp);
505                 }
506             }
507         }
508 
509         if (iReturnBuffers) {
510             LOGV("Return buffers from the audio thread");
511             if (len) sendResponse(cmdid, context, timestamp);
512             iReturnBuffers=false;
513             data = 0;
514             len = 0;
515             iAudioThreadReturnSem->Signal();
516         }
517 
518         // check for exit signal
519         if (iExitAudioThread) {
520             LOGV("exit received");
521             if (len) sendResponse(cmdid, context, timestamp);
522             break;
523         }
524 
525         // data to output?
526         if (len && (state == STARTED) && !iExitAudioThread) {
527 
528             // always align to AudioFlinger buffer boundary
529             if (bytesAvailInBuffer == 0)
530                 bytesAvailInBuffer = mAudioSink->bufferSize();
531 
532                 bytesToWrite = bytesAvailInBuffer > len ? len : bytesAvailInBuffer;
533                 //LOGV("16 bit :: cmdid = %d, len = %u, bytesAvailInBuffer = %u, bytesToWrite = %u", cmdid, len, bytesAvailInBuffer, bytesToWrite);
534                 bytesWritten = mAudioSink->write(data, bytesToWrite);
535                 if (bytesWritten != bytesToWrite) {
536                     LOGE("Error writing audio data");
537                     iAudioThreadSem->Wait();
538                 }
539                 data += bytesWritten;
540                 len -= bytesWritten;
541                 iClockTimeOfWriting_ns = systemTime(SYSTEM_TIME_MONOTONIC);
542 
543 
544             // count bytes sent
545             bytesAvailInBuffer -= bytesWritten;
546 
547             // update frame count for latency calculation
548             iActiveTiming->incFrameCount(bytesWritten / outputFrameSizeInBytes);
549             //LOGV("outputFrameSizeInBytes = %u,bytesWritten = %u,bytesAvailInBuffer = %u", outputFrameSizeInBytes,bytesWritten,bytesAvailInBuffer);
550             // if done with buffer - send response to MIO
551             if (data && !len) {
552                 LOGV("done with the data cmdid %d, context %p, timestamp %d ",cmdid, context, timestamp);
553                 sendResponse(cmdid, context, timestamp);
554                 data = 0;
555             }
556         }
557     } // while loop
558 
559     LOGV("stop and delete track");
560     mAudioSink->stop();
561     iClockTimeOfWriting_ns = 0;
562 
563     // LOGD("audout_thread_func exit");
564     iAudioThreadTermSem->Signal();
565 
566     return 0;
567 }
568 
569