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