3
\$\begingroup\$

I am currently writing an embedded application for audio streaming purposes. The embedded app will receive audio packets being sent over WiFi, buffer the packets, then send the audio data over to a decoder chip. I have a ring buffer implementation written, but am getting some weird behavior sometimes. In terms of audio, I am hearing some parts of the song repeat during playback. I found out that this is due to the tail pointer being set to the beginning of the buffer twice.

(In my implementation, the head pointer marks the end of valid data whereas the tail pointer marks the beginning of valid data)

For example, I see:

  • Head pointer reset to start of buffer
  • Tail pointer reset to start of buffer
  • Tail pointer reset to start of buffer <-- this is where I hear audio repeating
  • Head pointer reset to start of buffer

Here is the ring buffer implementation:

typedef struct ring_buffer
{
    UINT8 *buffer;      /* data buffer */
    UINT8 *buffer_end;  /* end of data buffer */
    size_t capacity;    /* maximum number of mp3Bytes in the buffer */
    size_t count;       /* number of mp3Bytes in the buffer */
    size_t typesize;    /* size of each mp3Byte in the buffer */
    UINT8 *head;        /* ring buffer head pointer */
    UINT8 *tail;        /* ring buffer tail pointer */
} ring_buffer;

PUBLIC UINT8
AppAudioStream_RingBufInit(ring_buffer *rb, size_t capacity, size_t typesize)
{   
    /* alloc buffer of size capacity * typesize */
    rb->buffer = malloc(capacity * typesize);
    if(rb->buffer == NULL)
    {
        printf("ring buffer init fail\r\n");
        return RING_BUF_INIT_FAIL;
    }

    /* init rb buffer to 0 */
    memset(rb->buffer, 0, capacity * typesize);

    /* rb struct element init */
    rb->capacity = capacity;
    rb->buffer_end = rb->buffer + capacity * typesize;
    rb->count = 0;
    rb->typesize = typesize;
    rb->head = rb->buffer;
    rb->tail = rb->buffer;

    return RING_BUF_INIT_DONE;
}

PUBLIC VOID
AppAudioStream_RingBufWrite(ring_buffer *rb, UINT8 *mp3Byte)
{   
    /* default: allow overwriting if ring buffer is full */
    memcpy(rb->head, mp3Byte, rb->typesize);
    rb->head = rb->head + rb->typesize;
    if(rb->head == rb->buffer_end) {
        printf("head back to start\r\n");
        rb->head = rb->buffer;
    }

    if(rb->count == rb->capacity) {
        printf("buffer full\r\n");
        if (rb->head > rb->tail)
            rb->tail = rb->tail + rb->typesize;
    } else { 
        rb->count++;
    }
}

PUBLIC VOID
AppAudioStream_RingBufRead(ring_buffer *rb, UINT8 *mp3Byte)
{
    /* insert 'comfort noise' if the ring buffer is empty */
    if(rb->count == 0){
        printf("buffer empty\r\n");
        *mp3Byte = NOISE_BYTE;
    } else {
        /* copy data to mp3Byte and increase tail pointer */
        memcpy(mp3Byte, rb->tail, rb->typesize);
        rb->tail = rb->tail + rb->typesize;
        if(rb->tail == rb->buffer_end) {
            printf("TAIL back to start\r\n");
            printf("Tbuffer count: %i\r\n", rb->count);
            rb->tail = rb->buffer;
        }   
        rb->count--;
    }
}

Here is how the ring buffer write function is called:

while (1)
{
    AppAudioStream_BufRecv(sd, dataLen, &addr);
}

PUBLIC VOID 
AppAudioStream_BufRecv(int sd, INT32 dataLen, struct sockaddr_in *addr)
{
    INT32 addrlen = sizeof(struct sockaddr_in);
    UINT8 j, i = 0;
    UINT8 *audioByte;

    /* listen to incoming audio data packets */
    dataLen = recvfrom(sd, (char *) appRxBuf, sizeof(appRxBuf), 0, 
                       (struct sockaddr *)&addr, &addrlen);

    /* set pointer to first element in recieve buffer */
    audioByte = appRxBuf;

    /* buffer received packets into FIFO */
    while (dataLen > 0)
    {
        /* write 1 byte into audio FIFO */
        AppAudioStream_RingBufWrite(&audioFIFO, audioByte);

        /* increase pointer index and update # of bytes left to write */
        audioByte++;
        dataLen--;
    }

    /* wait until buffer is 2/3 full to start decoding */
    if (audioFIFO.count >= FIFO_TWO_THIRD_FULL 
        && audioStreamStatus == GSN_STREAM_BUFFERING) {
        audioStreamStatus = GSN_STREAM_START; 
        //printf("stream start\r\n");
    }
}

The ring buffer read function is called in a callback which happens every 2 ms (this is basically an ISR):

PRIVATE VOID 
AppAudioStream_DecoderCb(UINT32* pDummy, UINT32 TimerHandle)
{
    UINT8 spiWriteCount = 0;
    UINT8 mp3Byte;
    int i = 0;
    GSN_SPI_NUM_T spiPortNumber = GSN_SPI_NUM_0;

    /* read 32 bytes of data from FIFO and write to SPI */
    while (spiWriteCount < DATA_WRITE_AMT)
    {
        /* set stream status to decoding */
        audioStreamStatus = GSN_STREAM_DECODING;

        /* read 1 byte of audio data from FIFO */
        AppAudioStream_RingBufRead(&audioFIFO, &mp3Byte);

        /* write 1 byte of audio data out to VS1053 */
        AppSpi_SdiByteWrite(spiPortNumber, &mp3Byte);

        /* increase byte written count */
        spiWriteCount++; 
     }
}

I am sure I am just overlooking something really obvious right now.

\$\endgroup\$
1
  • \$\begingroup\$ For small embedded systems, it's a good old trick to align the buffer on a 2 << (n+1) boundary, and then after each increment operation on head/tail pointers to and them with ~(2<<n). This works on only if you increment the pointers. It keeps the n-th bit always zeroed, as it's effectively the carry bit. \$\endgroup\$ Jun 14 '12 at 4:15
3
\$\begingroup\$

If I'm right you need some synchronization/locking to make visible every data change properly for every thread since two threads use the ring buffer at the same time: AppAudioStream_BufRecv and AppAudioStream_DecoderCb.

Two posts from SO which look useful:

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I think you are right. The AppAudioStream_BufRecv is running in one thread, but the decoderCb is being executed every time a periodic timer expires. According to the documentation, the periodic timer will issue an interrupt and call the callbacks of all expired timers. But either way, the fact remains that the same buffer is being read from/written to (maybe at the same time), which as you pointed out is not a good idea. Thank you for the pointers, and my apologies that I can't up vote your reply since I don't have enough reputation. \$\endgroup\$
    – cllee
    May 15 '12 at 22:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.