]> Gitweb @ Texas Instruments - Open Source Git Repositories - git.TI.com/gitweb - processor-sdk/performance-audio-sr.git/blobdiff - pasdk/test_dsp/framework/aspDecOpCircBuf_master.c
PASDK-218:Add rudimentary Dec Op CB status and alpha command interface
[processor-sdk/performance-audio-sr.git] / pasdk / test_dsp / framework / aspDecOpCircBuf_master.c
index 21264a6512e6b53c447214420dac690043773260..f2331ae9240009e7683777ffe1bd3f93bfeeae0c 100644 (file)
@@ -65,10 +65,12 @@ static Void cbReadAfMute(
 );
 #endif
 
+#if 0
 // Init last audio frame configuration info 
 static Void cbInitLastAfInfo(
     PAF_AudioFrame *pAfRd      // last audio frame stored in CB instance
 );
+#endif
 
 // Update last audio frame configuration info 
 static Void cbUpdateLastAfInfo(
@@ -77,7 +79,7 @@ static Void cbUpdateLastAfInfo(
 );
 
 // Generate mute AF on circular buffer read using the last AF configuration info 
-static Void cbReadMuteWithLastAfInfo (
+static Void cbReadMuteWithLastAfInfo(
     PAF_AST_DecOpCircBuf *pCb,    // decoder output circular buffer control
     PAF_AudioFrame *pAfRd         // audio frame into which to read
 );
@@ -121,6 +123,7 @@ Int cbInit(
     PAF_AST_DecOpCircBuf *pCb
 )
 {
+#if 0 // FL: unused
     PAF_AudioFrame *pAfCb;
     PAF_AudioData *pPcmBuf;
     UInt8 *pMetaBuf;
@@ -137,7 +140,7 @@ Int cbInit(
     pCb->strFrameLen = DEF_STR_FRAME_LEN;
     
     // initialize circular buffer maximum number of audio frames
-    pCb->maxNumAfCb = ASP_DECOP_CB_MAX_NUM_AF_THD;//ASP_DECOP_CB_MAX_NUM_AF_PCM;
+    pCb->maxNumAfCb = ASP_DECOP_CB_MAX_NUM_AF_THD; //ASP_DECOP_CB_MAX_NUM_AF_PCM;
     pCb->afWrtIdx = ASP_DECOP_CB_INIT_LAG_PCM;
     pCb->afRdIdx = 0;
     pCb->pcmRdIdx = 0;
@@ -209,6 +212,7 @@ Int cbInit(
     
     // reset stats
     pCb->readAfWriterInactiveCnt = 0;
+    pCb->readAfNdCnt = 0;
     pCb->wrtAfReaderInactiveCnt = 0;
     pCb->wrtAfZeroSampsCnt = 0;
     pCb->errUndCnt = 0;
@@ -242,11 +246,28 @@ Int cbInit(
         }
     }
     Cache_wait();
+#endif
+
+    // set source select
+    pCb->sourceSel = PAF_SOURCE_UNKNOWN;
+
+    #ifdef CB_RW_OP_CAP_PP // debug
+    // Get address in global variables
+    gCB_samples_op = pCb->cb_samples_op;
+    gCB_op_owner = pCb->cb_op_owner;
+    gCB_opCnt = &pCb->cb_opCnt;
+    gCB_afRdIdx = pCb->cb_afRdIdx;
+    gCB_afWrtIdx = pCb->cb_afWrtIdx;
+    gCB_numAfCb = pCb->cb_numAfCb;
+    #endif
+    
+    // Write back circular buffer configuration
+    Cache_wb(pCb, sizeof(PAF_AST_DecOpCircBuf), Cache_Type_ALLD, 0);
+    Cache_wait();
 
     return ASP_DECOP_CB_SOK;
 }
 
-
 #if 0 // FL: moved to ARM
 // debug
 //Int8 gCbInitSourceSelCnt=0;
@@ -467,6 +488,7 @@ Int cbInitSourceSel(
     
     // reset stats
     pCb->readAfWriterInactiveCnt = 0;
+    pCb->readAfNdCnt = 0;
     pCb->wrtAfReaderInactiveCnt = 0;
     pCb->wrtAfZeroSampsCnt = 0;
     pCb->errUndCnt = 0;
@@ -496,6 +518,42 @@ Int cbInitSourceSel(
 }
 #endif
 
+// Initialize circular buffer for Stream reads
+Int cbInitStreamRead(
+    PAF_AST_DecOpCircBufCtl *pCbCtl,    // decoder output circular buffer control
+    Int8 cbIdx                          // decoder output circular buffer index
+)
+{
+    IArg key;
+    GateMP_Handle gateHandle;
+    PAF_AST_DecOpCircBuf *pCb;
+    
+    // Get gate handle
+    gateHandle = pCbCtl->gateHandle;
+    // Enter gate
+    key = GateMP_enter(gateHandle);
+
+    // Get circular buffer base pointer
+    pCb = &((*pCbCtl->pXDecOpCb)[cbIdx]);
+    
+    // Invalidate circular buffer configuration
+    Cache_inv(pCb, sizeof(PAF_AST_DecOpCircBuf), Cache_Type_ALLD, 0);
+    Cache_wait();
+
+    // Set output frame length
+    pCb->strFrameLen = pCb->cbStatus.strFrameLen;
+
+    // Write back circular buffer configuration
+    Cache_wb(pCb, sizeof(PAF_AST_DecOpCircBuf), Cache_Type_ALLD, 0);
+    Cache_wait();
+
+    // Leave the gate
+    GateMP_leave(gateHandle, key);
+    
+    //return ret;
+    return ASP_DECOP_CB_SOK;
+}
+
 // Start reads from circular buffer
 Int cbReadStart(
     PAF_AST_DecOpCircBufCtl *pCbCtl,    // decoder output circular buffer control
@@ -582,13 +640,16 @@ Int cbReadAf(
 )
 {
     IArg key;
-    GateMP_Handle gateHandle;
-    PAF_AST_DecOpCircBuf *pCb;
-    PAF_AudioFrame *pAfCb;
-    PAF_ChannelMask_HD streamMask;
+    GateMP_Handle gateHandle;           // CB gate handle to arbitrate CB r/w access
+    PAF_AST_DecOpCircBuf *pCb;          // pointer to CB
+    PAF_AudioFrame *pAfCb;              // pointer to current CB AF
+    PAF_ChannelMask_HD streamMask;      // CB AF stream mask
+    Int16 numSampsRd;                   // number of samples to read from current CB AF
+    Int16 totNumSampsRd;                // total number of samples read from CB
+    Int16 pcmWrtIdx;                    // read audio frame PCM write index
+    Int8 prvMdWrtIdx;                   // read audio frame metadata write index
     Int8 i;
     Int16 j;
-    Int8 numMetadata = 0;
     
     // Get gate handle
     gateHandle = pCbCtl->gateHandle;
@@ -604,7 +665,9 @@ Int cbReadAf(
 
     //Log_info1("cbReadAf:afCb=0x%04x", (IArg)pCb->afCb); // debug
 
+    //
     // Check (writerActiveFlag,drainFlag)=(1,1)
+    //
     if ((pCb->writerActiveFlag == 1) && (pCb->drainFlag == 1))
     {
         //
@@ -612,8 +675,8 @@ Int cbReadAf(
         // writer is active AND draining circular buffer
         //
         
-        //Log_info2("cbReadAf: ERROR: writerActiveFlag=%d, drainFlag=%d", pCb->writerActiveFlag, pCb->drainFlag); // FL: debug
-        SW_BREAKPOINT; // FL: debug
+        //Log_info2("cbReadAf: ERROR: writerActiveFlag=%d, drainFlag=%d", pCb->writerActiveFlag, pCb->drainFlag); // debug
+        SW_BREAKPOINT; // debug
         
         // Leave the gate
         GateMP_leave(gateHandle, key);
@@ -621,7 +684,9 @@ Int cbReadAf(
         return ASP_DECOP_CB_READ_INVSTATE;
     }
 
+    //
     // Check (writerActiveFlag,drainFlag)=(0,0)
+    //
     //if (((pCb->writerActiveFlag == 0) && (pCb->drainFlag == 0)) || (pCb->afLagIdx < pCb->afInitialLag))
     if ((pCb->writerActiveFlag == 0) && (pCb->drainFlag == 0))
     {
@@ -632,22 +697,53 @@ Int cbReadAf(
         
         pCb->readAfWriterInactiveCnt++;
         
+        // Mute output if write inactive and not draining CB
         //cbReadAfMute(pAfRd, pCb->strFrameLen);
         cbReadMuteWithLastAfInfo(pCb, pAfRd);
         
         // Write back circular buffer configuration.
         Cache_wb(pCb, sizeof(PAF_AST_DecOpCircBuf), Cache_Type_ALLD, 0);
-        Cache_wait();    
+        Cache_wait();
 
         // Leave the gate
         GateMP_leave(gateHandle, key);
 
         return ASP_DECOP_CB_SOK;
     }
+
+    if ((pCb->writerActiveFlag == 0) && (pCb->drainFlag == 1))
+    {
+        //
+        // Writer inactive, but remaining frames in circular buffer.
+        // Update drain flag.
+        //
+        
+        if (pCb->numAfCb <= 0)
+        {
+            pCb->drainFlag = 0;
+
+            // Mute output if CB drained
+            cbReadMuteWithLastAfInfo(pCb, pAfRd);
+
+            // Write back circular buffer configuration.
+            Cache_wb(pCb, sizeof(PAF_AST_DecOpCircBuf), Cache_Type_ALLD, 0);
+            Cache_wait();    
+
+            // Leave the gate
+            GateMP_leave(gateHandle, key);
+
+            return ASP_DECOP_CB_SOK;
+        }
+    }
     
+    //
+    // Hold off read of PCM samples from CB until Nominal Delay satisfied
+    //
     //if ((pCb->primedFlag == 0) || ((pCb->primedFlag==1) && (pCb->deltaSamps > 0))
     if ((pCb->primedFlag == 0) || (pCb->deltaSamps > 0))
     {
+        pCb->readAfNdCnt++;
+        
         if (pCb->primedFlag == 1)
         {
             pCb->deltaSamps = pCb->deltaSamps - pCb->strFrameLen;
@@ -670,23 +766,27 @@ Int cbReadAf(
         return ASP_DECOP_CB_SOK;
     }
     
-    // (writerActiveFlag,drainFlag)= (0,0) and (1,1) checked above
-    // (writerActiveFlag,drainFlag)=(1,0) and (0,1) are left
+    //
+    // (writerActiveFlag,drainFlag)= (0,0) and (1,1) states checked above
+    // (writerActiveFlag,drainFlag)=(1,0) and (0,1) states are left
     // Checking (writerActiveFlag,drainFlag)=(1,0) state here
+    //
     if (pCb->writerActiveFlag == 1)
     {
         // check underflow
         if (pCb->numAfCb <= 0)
         {
             // 
-            // Increment underflow count.
-            // Mute output on underflow.
+            // Increment underflow count
             //
-            pCb->errUndCnt++;
+            pCb->errAfUndCnt++;
+
+            // Mute output on underflow
             //cbReadAfMute(pAfRd, pCb->strFrameLen);
             cbReadMuteWithLastAfInfo(pCb, pAfRd);
-            //SW_BREAKPOINT; // FL: debug
+            //SW_BREAKPOINT; // debug
             
+#if 0 // (***) FL: shows timing of CB underflow
             // debug
             {
                 static Uint8 toggleState = 0;
@@ -696,6 +796,7 @@ Int cbReadAf(
                     GPIOClearOutput(GPIO_PORT_0, GPIO_PIN_107);
                 toggleState = ~(toggleState);
             }
+#endif
 
             #ifdef CB_RW_OP_CAP_PP // debug
             if (pCb->cb_opCnt < CB_OP_COUNT_MAX)
@@ -721,147 +822,280 @@ Int cbReadAf(
             // Leave the gate
             GateMP_leave(gateHandle, key);
             
-            return ASP_DECOP_CB_READ_UNDERFLOW;
+            return ASP_DECOP_CB_AF_READ_UNDERFLOW;
         }
     }
     
+    //
     // Checking (writerActiveFlag,drainFlag)=(1,0) state above and here
     // Checking (writerActiveFlag,drainFlag)=(0,1) state here
+    //
     if ((pCb->writerActiveFlag == 1) || (pCb->drainFlag == 1))
     {
         //
         // Writer active or draining remaining frames in circular buffer.
-        // Get next output audio frame.
+        // Read next audio frame.
+        //
+
+        //
+        // Read Audio Frame from CB Audio Frames.
+        // Read PCM & associated metadata from CB AFs until Read AF is filled.
+        //
+        // If multiple CB AFs are read in creating Read AF, CB AF parameters 
+        // can *change* between read CB AFs. Since only one Read AF is produced 
+        // per CB read, this is a "data collision", i.e. can't have more than 
+        // one set of CB AF parameters in Read AF.
+        //
+        // Currently applying parameters from earliest read CB AF to Read Af.
+        // This can result in delay (or skip) w.r.t. application of additional 
+        // CB AF parameters not used for creating Read AF. This is reasonable 
+        // given there's no way to "interpolate" AF parameters, e.g. how to 
+        // interpolation change in in bit-stream metadata type, or CC stream?
         //
         
-        // get pointer to current audio frame in circular buffer
+        // Get pointer to current CB Audio Frame
         pAfCb = &pCb->afCb[pCb->afRdIdx];
 
-        // Invalidate audio frame
+        // Cache invalidate CB AF
         Cache_inv(pAfCb, sizeof(PAF_AudioFrame), Cache_Type_ALLD, 0);
-        Cache_inv(pAfCb->data.samsiz, pCb->maxAFChanNum*sizeof(PAF_AudioSize), Cache_Type_ALLD, 0);
-        for (i=0; i<pAfCb->numPrivateMetadata; i++) // only invalidate numPrivateMetadata
-        {
-            Cache_inv(pAfCb->pafPrivateMetadata[i].pMdBuf, pAfCb->pafPrivateMetadata[i].size, Cache_Type_ALLD, 0); // only update metadata package size
-        }
-        Cache_wait();
-
-        // compute stream mask
-        streamMask = pAfRd->fxns->channelMask(pAfRd, pAfCb->channelConfigurationStream);
-
-        // Invalidate channel pointers
-        Cache_inv(pAfCb->data.sample, pCb->maxAFChanNum*sizeof(PAF_AudioData *), Cache_Type_ALLD, 0);
         Cache_wait();
-
-        // Invalidate PCM data
-        for (i = 0; i < pCb->maxAFChanNum; i++)
-        {
-            if ((streamMask >> i) & 0x1)
-            {
-                Cache_inv(&pAfCb->data.sample[i][pCb->pcmRdIdx], pCb->strFrameLen*sizeof(PAF_AudioData), Cache_Type_ALLD, 0);
-            }
-        }
-        Cache_wait();        
         
-        // read audio frame information updated by decoder
+        // Read CB AF information updated by decoder
         pAfRd->sampleDecode = pAfCb->sampleDecode;
         PAF_PROCESS_COPY(pAfRd->sampleProcess, pAfCb->sampleProcess);
         pAfRd->sampleRate = pAfCb->sampleRate;
-        pAfRd->sampleCount = pCb->strFrameLen;
         pAfRd->channelConfigurationRequest = pAfCb->channelConfigurationRequest;
         pAfRd->channelConfigurationStream = pAfCb->channelConfigurationStream;
-        
-        // read metadata information updated by decoder
-        pAfRd->bsMetadata_type     = pAfCb->bsMetadata_type;        /* non zero if metadata is attached. */
+        // Read CB AF bit-stream metadata information updated by decoder
+        pAfRd->bsMetadata_type     = pAfCb->bsMetadata_type;        /* non zero if private metadata is attached. */
         pAfRd->pafBsMetadataUpdate = pAfCb->pafBsMetadataUpdate;    /* indicates whether bit-stream metadata update */
-        pAfRd->numPrivateMetadata  = pAfCb->numPrivateMetadata;     /* number of valid private metadata (0 or 1 if metadata filtering enabled) */
         pAfRd->bsMetadata_offset   = pAfCb->bsMetadata_offset;      /* offset into audio frame for change in bsMetadata_type field */
         
-        #ifdef CB_RW_OP_CAP_PP // debug
-        if (pCb->cb_opCnt < CB_OP_COUNT_MAX)
-        {
-            if ((pCb->cb_samples_op != NULL) && (pCb->cb_op_owner != NULL))
-            {
-                // log sample count
-                pCb->cb_samples_op[pCb->cb_opCnt] = pAfRd->sampleCount;
-                pCb->cb_op_owner[pCb->cb_opCnt] = CB_OP_R;
-                // log idxs
-                pCb->cb_afRdIdx[pCb->cb_opCnt] = pCb->afRdIdx;
-                pCb->cb_afWrtIdx[pCb->cb_opCnt] = pCb->afWrtIdx;
-                pCb->cb_numAfCb[pCb->cb_opCnt] = pCb->numAfCb; // numAfCb might not be pointing to this instance
-                pCb->cb_opCnt++;
-            }
-        }
-        #endif
-
-        //// update Last Cb info as per actual stream
-        //pCb->lastAf.sampleCount = pCb->strFrameLen; // FL: last AF sample count isn't used (see cbReadMuteWithLastAfInfo())
-        //pCb->lastAf.sampleRate = pAfCb->sampleRate; // FL: moved inside cbUpdateLastAfInfo() along with other params from memcpy below
-        // Update last audio frame configuration info
-        cbUpdateLastAfInfo(pCb, pAfRd);
+        // Compute stream mask for current CB AF.
+        // Mask indicates which channels are present in AF.
+        // Mask needed for cache invalidate and read of PCM samples in AF.
+        streamMask = pAfRd->fxns->channelMask(pAfRd, pAfCb->channelConfigurationStream);
 
-        // read PCM samples
+        // Cache invalidate CB AF samsiz (volume scaling exponent) array
+        Cache_inv(pAfCb->data.samsiz, pCb->maxAFChanNum*sizeof(PAF_AudioSize), Cache_Type_ALLD, 0);
+        Cache_wait();
+        
+        // Read CB AF samsiz array
         for (i = 0; i < pCb->maxAFChanNum; i++)
         {
             if ((streamMask >> i) & 0x1)
             {
-                for (j = 0; j < pCb->strFrameLen; j++)
-                {
-                    pAfRd->data.sample[i][j] = pAfCb->data.sample[i][pCb->pcmRdIdx+j];
-                }
-
                 pAfRd->data.samsiz[i] = pAfCb->data.samsiz[i];
             }
         }
+
+        // FL: brute force clear of Read AF PCM data
+        // Reset Read AF PCM data
+        //for (i = 0; i < pCb->maxAFChanNum; i++)
+        //{
+        //    if ((streamMask >> i) & 0x1)
+        //    {
+        //        memset(pAfRd->data.sample[i], 0, pCb->strFrameLen);
+        //    }
+        //}
         
+        // FL: This brute force approach shouldn't be necessary if
+        //     decoders properly set, and downstream components
+        //     properly use, number of private metadata in frame
+        // Reset Read AF metadata
         for (i = 0; i < PAF_MAX_NUM_PRIVATE_MD; i++)
         {
             pAfRd->pafPrivateMetadata[i].offset = 0;
             pAfRd->pafPrivateMetadata[i].size   = 0;
         }
         
-        // read metadata
-        for (i = 0; i < pAfCb->numPrivateMetadata; i++) // only read numPrivateMetadata
+        totNumSampsRd = 0;  // init total number of samples read from CB
+        pcmWrtIdx = 0;      // init Read AF PCM write index
+        prvMdWrtIdx = 0;    // init Read AF metadata write index
+        while ((totNumSampsRd < pCb->strFrameLen) && (pCb->numAfCb > 0))
         {
-            if ((pAfCb->pafPrivateMetadata[i].offset >= pCb->pcmRdIdx) 
-                 &&(pAfCb->pafPrivateMetadata[i].offset < (pCb->pcmRdIdx + pCb->strFrameLen))
-                 &&(pAfCb->pafPrivateMetadata[i].size))
+            // Compute how many PCM samples can be read from CB AF
+            //  Number of samples left in current CB AF: pAfCb->sampleCount - pCb->pcmRdIdx
+            //  Number of samples left to write to Read AF: pCb->strFrameLen - totNumSampsRd
+            numSampsRd = (pAfCb->sampleCount - pCb->pcmRdIdx) < (pCb->strFrameLen - totNumSampsRd) ? 
+                (pAfCb->sampleCount - pCb->pcmRdIdx) : (pCb->strFrameLen - totNumSampsRd);
+
+            // Cache invalidate CB AF PCM sample channel pointers
+            Cache_inv(pAfCb->data.sample, pCb->maxAFChanNum*sizeof(PAF_AudioData *), Cache_Type_ALLD, 0);
+            Cache_wait();
+
+            // Cache invalidate CB AF PCM samples
+            for (i = 0; i < pCb->maxAFChanNum; i++)
             {
-                // the offset is adjusted for segment [pCb->pcmRdIdx, (pCb->pcmRdIdx + pCb->pafFrameLen)]
-                pAfRd->pafPrivateMetadata[numMetadata].offset = pAfCb->pafPrivateMetadata[i].offset - pCb->pcmRdIdx;
-                pAfRd->pafPrivateMetadata[numMetadata].size   = pAfCb->pafPrivateMetadata[i].size;
-                memcpy(pAfRd->pafPrivateMetadata[numMetadata].pMdBuf, pAfCb->pafPrivateMetadata[i].pMdBuf, pAfCb->pafPrivateMetadata[i].size);
-                numMetadata++; //number of metadata associated with current 256 segment of audio samples
+                if ((streamMask >> i) & 0x1)
+                {
+                    Cache_inv(&pAfCb->data.sample[i][pCb->pcmRdIdx], numSampsRd*sizeof(PAF_AudioData), Cache_Type_ALLD, 0);
+                }
             }
-            else //reset un-used buf
+            Cache_wait();
+
+            // Read PCM samples from CB AF
+            for (i = 0; i < pCb->maxAFChanNum; i++)
             {
-                pAfRd->pafPrivateMetadata[i].offset = 0;
-                pAfRd->pafPrivateMetadata[i].size   = 0;
+                if ((streamMask >> i) & 0x1)
+                {
+                    for (j = 0; j < numSampsRd; j++)
+                    {
+                        pAfRd->data.sample[i][pcmWrtIdx+j] = pAfCb->data.sample[i][pCb->pcmRdIdx+j];
+                    }
+                }
             }
 
+            // Cache invalidate CB AF unused metadata
+            for (i = pCb->prvMdRdIdx; i < pAfCb->numPrivateMetadata; i++)
+            {
+                Cache_inv(pAfCb->pafPrivateMetadata[i].pMdBuf, pAfCb->pafPrivateMetadata[i].size, Cache_Type_ALLD, 0); // only update metadata package size
+            }
+            Cache_wait();
+
+            // Read CB AF metadata
+            while (((pCb->prvMdRdIdx < pAfCb->numPrivateMetadata) && 
+                (pAfCb->pafPrivateMetadata[pCb->prvMdRdIdx].offset >= pCb->pcmRdIdx) &&
+                (pAfCb->pafPrivateMetadata[pCb->prvMdRdIdx].offset < (pCb->pcmRdIdx + numSampsRd)) &&
+                (pAfCb->pafPrivateMetadata[pCb->prvMdRdIdx].size)) &&
+                (prvMdWrtIdx < PAF_MAX_NUM_PRIVATE_MD))
+            {
+                // Write Read AF metadata offset.
+                //  Compute relative offset of PCM samples being read from CB AF.
+                //  Compute absolute offset of PCM samples written to Read AF by 
+                //  adding relative offset to Read AF PCM write index.
+                pAfRd->pafPrivateMetadata[prvMdWrtIdx].offset = 
+                    (pAfCb->pafPrivateMetadata[pCb->prvMdRdIdx].offset - pCb->pcmRdIdx) +   // offset relative to samples being read from CB AF
+                    pcmWrtIdx;                                                              // absolute offset into samples being written to read AF
+                // Write Read AF metadata size    
+                pAfRd->pafPrivateMetadata[prvMdWrtIdx].size = pAfCb->pafPrivateMetadata[pCb->prvMdRdIdx].size;
+                // Write Read AF metadata payload
+                memcpy(pAfRd->pafPrivateMetadata[prvMdWrtIdx].pMdBuf, pAfCb->pafPrivateMetadata[pCb->prvMdRdIdx].pMdBuf, pAfCb->pafPrivateMetadata[pCb->prvMdRdIdx].size);
+                
+                pCb->prvMdRdIdx++;  // update CB metadata read index
+                prvMdWrtIdx++;      // update CB metadata write index
+            }
+            
+            // Update CB control
+            pCb->pcmRdIdx += numSampsRd; // update PCM read index
+            if (pCb->pcmRdIdx >= pAfCb->sampleCount) 
+            {
+                // Finished reading PCM samples from current CB AF.
+                // Move to next AF in CB.
+                
+                // Update audio frame read index.
+                // Wrap index if required.
+                pCb->afRdIdx++;
+                if (pCb->afRdIdx >= pCb->maxNumAfCb)
+                {
+                    pCb->afRdIdx = 0;
+                }
+                
+                // Reset PCM read index
+                pCb->pcmRdIdx = 0;
+                // Reset metadata read index
+                pCb->prvMdRdIdx = 0;
+                
+                // Update number of audio frames in circular buffer
+                pCb->numAfCb--;
+            }
+            
+            // Update PCM write index
+            pcmWrtIdx += numSampsRd;
+
+            // Update total number of samples read
+            totNumSampsRd += numSampsRd;
+            
+            if (totNumSampsRd < pCb->strFrameLen)
+            {
+                //
+                // Need to read another AF from CB to obtain all PCM/metadata for Read AF
+                //
+                
+                // Get pointer to current CB Audio Frame
+                pAfCb = &pCb->afCb[pCb->afRdIdx];
+                
+                // Cache invalidate CB AF
+                Cache_inv(pAfCb, sizeof(PAF_AudioFrame), Cache_Type_ALLD, 0);
+                Cache_wait();
+            }
         }
-        pAfRd->numPrivateMetadata = numMetadata; //number of metadata associated with current 256 segment of audio samples
+       
+        pAfRd->sampleCount = totNumSampsRd;         // write Read AF sample count
+        pAfRd->numPrivateMetadata = prvMdWrtIdx;    // write Read AF number of metadata
         
-        pCb->pcmRdIdx += pCb->strFrameLen; // update PCM read index
-        if (pCb->pcmRdIdx >= pAfCb->sampleCount) 
+        pCb->numPcmSampsPerCh -= totNumSampsRd;     // update PCM samples per channel
+       
+        if (totNumSampsRd < pCb->strFrameLen)
         {
-            // update audio frame read index
-            pCb->afRdIdx++;
-            if (pCb->afRdIdx >= pCb->maxNumAfCb)
+            // Clear remaining Read AF PCM samples
+            for (i = 0; i < pCb->maxAFChanNum; i++)
             {
-                pCb->afRdIdx = 0;
+                if ((streamMask >> i) & 0x1)
+                {
+                    memset(&pAfRd->data.sample[i][pcmWrtIdx], 0, (pCb->strFrameLen-totNumSampsRd));
+                }
             }
             
-            // update PCM read index
-            pCb->pcmRdIdx = 0;
+            if (pCb->writerActiveFlag == 1)
+            {
+                //
+                // UNDerflow, 
+                // read stopped due to insufficient PCM samples in CB to fill Read AF
+                //
             
-            // update number of audio frames in circular buffer
-            pCb->numAfCb--;
+                // 
+                // Increment underflow count
+                //
+                pCb->errPcmUndCnt++;
+
+                // Mute output on underflow
+                cbReadMuteWithLastAfInfo(pCb, pAfRd);
+                
+#if 0 // (***) FL: shows timing of CB underflow
+                // debug
+                {
+                    static Uint8 toggleState = 0;
+                    if (toggleState == 0)
+                        GPIOSetOutput(GPIO_PORT_0, GPIO_PIN_107);
+                    else
+                        GPIOClearOutput(GPIO_PORT_0, GPIO_PIN_107);
+                    toggleState = ~(toggleState);
+                }
+#endif
+
+                #ifdef CB_RW_OP_CAP_PP // debug
+                if (pCb->cb_opCnt < CB_OP_COUNT_MAX)
+                {
+                    if ((pCb->cb_samples_op != NULL) && (pCb->cb_op_owner != NULL))
+                    {
+                        // log sample count
+                        pCb->cb_samples_op[pCb->cb_opCnt] = 0;  // due to underflow
+                        pCb->cb_op_owner[pCb->cb_opCnt] = CB_OP_R;
+                        // log idxs
+                        pCb->cb_afRdIdx[pCb->cb_opCnt] = pCb->afRdIdx;
+                        pCb->cb_afWrtIdx[pCb->cb_opCnt] = pCb->afWrtIdx;
+                        pCb->cb_numAfCb[pCb->cb_opCnt] = pCb->numAfCb; // numAfCb might not be pointing to this instance
+                        pCb->cb_opCnt++;
+                    }
+                }
+                #endif
+
+                // Write back circular buffer configuration.
+                Cache_wb(pCb, sizeof(PAF_AST_DecOpCircBuf), Cache_Type_ALLD, 0);
+                Cache_wait();    
+                
+                // Leave the gate
+                GateMP_leave(gateHandle, key);
+                
+                return ASP_DECOP_CB_PCM_READ_UNDERFLOW;
+            }
         }
         
-        // FL: this update of Last AF is handled above
-        //memcpy (&pCb->lastAf, pAfRd, sizeof(PAF_AudioFrame));
+        // Read AF complete, update Last CB AF Info
+        cbUpdateLastAfInfo(pCb, pAfRd);
 
+#if 0 // (***) FL: shows timing of successful CB read
         {
             static Uint8 toggleState = 0;
             if (toggleState == 0)
@@ -870,8 +1104,27 @@ Int cbReadAf(
                 GPIOClearOutput(GPIO_PORT_0, GPIO_PIN_106);
             toggleState = ~(toggleState);
         }
+#endif
+
+#ifdef CB_RW_OP_CAP_PP // debug
+        if (pCb->cb_opCnt < CB_OP_COUNT_MAX)
+        {
+            if ((pCb->cb_samples_op != NULL) && (pCb->cb_op_owner != NULL))
+            {
+                // log sample count
+                pCb->cb_samples_op[pCb->cb_opCnt] = pAfRd->sampleCount;
+                pCb->cb_op_owner[pCb->cb_opCnt] = CB_OP_R;
+                // log idxs
+                pCb->cb_afRdIdx[pCb->cb_opCnt] = pCb->afRdIdx;
+                pCb->cb_afWrtIdx[pCb->cb_opCnt] = pCb->afWrtIdx;
+                pCb->cb_numAfCb[pCb->cb_opCnt] = pCb->numAfCb; // numAfCb might not be pointing to this instance
+                pCb->cb_opCnt++;
+            }
+        }
+#endif
     }
     
+#if 0
     if (pCb->drainFlag == 1)
     {
         //
@@ -883,6 +1136,7 @@ Int cbReadAf(
             pCb->drainFlag = 0;
         }
     }
+#endif
     
     // Write back circular buffer configuration.
     // NOTE: Probably only a subset of this information needs to be updated.
@@ -935,6 +1189,7 @@ static Void cbReadAfMute(
 }
 #endif
 
+#if 0 // FL: unused
 // Init last audio frame configuration info 
 static Void cbInitLastAfInfo(
     PAF_AudioFrame *pAfRd      // last audio frame stored in CB instance
@@ -956,6 +1211,7 @@ static Void cbInitLastAfInfo(
     pAfRd->numPrivateMetadata  = 0;                             /* number of valid private metadata (0 or 1 if metadata filtering enabled) */
     pAfRd->bsMetadata_offset   = 0;                             /* offset into audio frame for change in bsMetadata_type field */
 }
+#endif
 
 // Update last audio frame configuration info 
 static Void cbUpdateLastAfInfo(
@@ -976,7 +1232,7 @@ static Void cbUpdateLastAfInfo(
 }
 
 // Generate mute AF on circular buffer read using the last AF configuration info 
-static Void cbReadMuteWithLastAfInfo (
+static Void cbReadMuteWithLastAfInfo(
     PAF_AST_DecOpCircBuf *pCb,    // decoder output circular buffer control
     PAF_AudioFrame *pAfRd         // audio frame into which to read
 )