Home | History | Annotate | Download | only in android
      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 */
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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 
    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