2

現在、オーディオ ストリーミング用の組み込みアプリケーションを作成しています。組み込みアプリは、wifi 経由で送信されるオーディオ パケットを受信し、パケットをバッファリングしてから、オーディオ データをデコーダ チップに送信します。私はリングバッファの実装を書いています(ここのstackoverflowに関する優れた投稿の助けを借りて)が、時々奇妙な動作が発生します。オーディオに関しては、再生中に曲の一部が繰り返されるのが聞こえます。これは、テール ポインターがバッファーの先頭に 2 回設定されていることが原因であることがわかりました。

(私の実装では、ヘッド ポインターは有効なデータの終わりをマークし、テール ポインターは有効なデータの始まりをマークします)

たとえば、次のように表示されます。

  • バッファの先頭へのヘッド ポインタのリセット
  • バッファの先頭にテールポインタをリセット
  • バッファの先頭にテール ポインタをリセット <- ここでオーディオの繰り返しが聞こえます
  • バッファの先頭へのヘッド ポインタのリセット

リング バッファの実装は次のとおりです。

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

どんな助け/洞察も大歓迎です。私は今、本当に明白なことを見落としているだけだと確信しています。

ありがとうございました!

4

1 に答える 1

3

コールバックからこのリングバッファに読み取り、他の場所から書き込むと、リングバッファには保護がないため、明らかにあらゆる種類の奇妙なバグが発生します。ミューテックス、セマフォ、割り込み無効化、または特定のシステムに適用されるものを追加する必要があります。

また、危険なオプティマイザーのバグ(*)から保護するために、コールバックとアプリケーションの残りの部分の間で共有されるすべての変数を揮発性として宣言する必要があります。それに加えて、競合状態から保護するために、このような変数もミューテックスで保護する必要がある場合があります。

上記のバグのいずれかが原因であるかどうかをストレステストする良い方法は、マルチスレッドアプリを作成することです。バッファに書き込む10以上のスレッドと、バッファから読み書きする10以上のスレッドを作成します。出力をログに記録し、奇数の値またはガベージが出力されるかどうかを確認します。


(*)(この発言はスレッドセーフとは関係がないので、揮発性がスレッドセーフに十分ではないとコメントを投稿しないでください。私はそれを言ったことがなく、まだその愚かな議論はありません。また。)

于 2012-05-15T19:33:02.930 に答える