2012-05-15 61 views
2

我目前正在爲音頻流編寫一個嵌入式應用程序。嵌入式應用程序將接收通過wifi發送的音頻數據包,緩衝數據包,然後將音頻數據發送到解碼器芯片。我寫了一個環形緩衝區實現(在stackoverflow中有一個很好的帖子幫助),但是有時會出現一些奇怪的行爲。在音頻方面,我聽到播放過程中重複播放歌曲的某些部分。我發現這是由於尾指針被設置爲兩次緩衝區的開始。C尾指針環形緩衝區問題(用於音頻流)

  • 頭指針復位啓動:

    例如,我看到(我在執行中,頭指針,而尾指針標記有效數據的開始,標誌着有效數據的結束)緩衝區的

  • 尾指針復位啓動緩衝的
  • 尾指針復位啓動緩衝<的 - 這是我聽到的聲音重複
  • 頭指針復位啓動緩衝

這裏是環形緩衝區實現:

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--; 
    } 
} 

這裏是環形緩衝區寫入功能是如何調用:

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"); 
    } 
} 

環形緩衝區讀取函數在每2毫秒發生一次回調中調用(這基本上是一個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++; 
    } 
} 

任何幫助/見解非常感謝。我相信我現在只是忽略了一些非常明顯的東西。

謝謝!

+1

要求陌生人通過檢查發現代碼中的錯誤並不是富有成效的。你應該使用調試器來隔離一個更簡單的[測試用例](http://sscce.org),然後再回來。 –

+1

看起來像一個多線程應用程序,對吧?如果是這樣,你需要保護環形緩衝區。 – ugoren

+0

@Suma不,它不。閱讀[代碼審查常見問題](http://codereview.stackexchange.com/faq)。 – Lundin

回答

3

如果你從回調中讀到這個環形緩衝區並從別處寫入到環形緩衝區,那麼顯然你會得到所有奇怪的錯誤,因爲環形緩衝區沒有保護。您需要添加互斥量,信號量,中斷禁用或適用於您的特定系統的任何內容。

此外,回調和應用程序其餘部分之間共享的所有變量應聲明爲volatile,以防止危險的優化器錯誤(*)。除此之外,這些變量也可能需要使用互斥體進行保護,以防止競爭條件。

如果上面提到的任何錯誤都是原因,一個壓力測試的好方法是創建一個多線程應用程序。創建10個以上寫入緩衝區的線程,以及10個以上線程從中讀取並打印。記錄輸出結果,看看是否有奇數值或垃圾打印。


(*)(這句話有什麼做線程安全的,所以請不來張貼有關揮發性不是足夠的線程安全的意見,因爲我從來沒有說,這是和我再也不會有那麼愚蠢的辯論了。)

+0

謝謝你指出這一點,它很有意義。 – cllee

+0

所有這些都不適用於單線程程序。 –

+0

@ n.m。確實,關於多線程的部分不適用於單線程程序。除了那個明顯的陳述之外,實現ISR的單線程程序與多線程程序共享相同的問題。如何處理回調取決於操作系統,但通常不需要任何互斥量守護程序。 – Lundin