終端接收FFMEPG推送的流出現音頻卡頓問題


TOC

1. 終端音頻卡頓的可能情況

分析問題有一個很有用的鏈路分析法,將鏈路切分為多個環節,分析每個環節從而找到問題根源。

解碼框圖

接收碼流數據 -> 解復用 -> 音視頻解碼 -> 音視頻同步 -> 音視頻(顯卡、聲卡)輸出

音頻輸出環節分析:

音頻卡頓是人感官聽到的,也就是聲卡發出的聲音有卡頓;
聲卡卡頓的可能原因:1. 聲卡硬件異常 2.聲卡收到的音頻有問題

  1. 聲卡硬件異常的問題,只能換硬件。
  2. 聲卡的上一環節送過來的聲音有卡頓。

音視頻同步環節分析:

  1. 音頻解碼后的數據出現音頻卡頓
  2. 解碼后的音頻數據送給sample buffer出現了溢出。

音頻解碼器環節分析:

音頻解碼器工作比較簡單,收到數據就進行解碼,解碼后就將數據發送給下一級。

  1. 編碼的原始數據就是音頻卡頓。
  2. 接收解復用的數據時候,發送過來的數據量抖動太大,audio buffer 出現溢出。
  3. 音頻的PTS小於音頻包到達的時間,大部分解碼器都會直接扔掉。

解復用環節分析:

這一環節一般不會出問題。

分析總結:

分析了整個鏈路的每個環節,音頻卡頓的幾種可能:

  1. 音頻來得太遲(PTS < 到達時間),音頻包被丟棄了
  2. 接收到的流碼率抖動太大,解碼端的相關音頻buffer出現溢出
  3. 原始音頻數據就是卡頓。
  4. 聲卡硬件問題

情況1和情況2都可以使用相關工具分析判斷;

情況3需要錄流,軟件解碼出音頻PCM,播放PCM可以知道;

情況4用不同的終端對比測試可以判斷。

2. 解決方案

2.1 音頻PTS錯誤問題

max_delay: 這個參數可以拉開PCR和PTS直接的差距,從而解決音頻包的PTS小於音頻包到達時間;ffmpeg 默認是0.7s, 可以根據需要設置它的值。

設置max_delay參數的接口:

av_dict_set(&dicCtx, "max_delay", "1000000", 0)

2.2 輸出碼率抖動問題

核心問題就是讓FFMPEG碼流平滑輸出

ffmpeg 命令行輸出平滑碼率:

ffmpeg -re -i input.ts -c copy -muxrate 7000000 -f mpegts "udp://227.40.50.60:1234?pkt_size=1316&fifo_size=27887&bitrate=7000000&burst_bits=1000000"

說明:

input.ts:輸入流

muxrate:設置復用碼率,ffmpeg通過填充空包達到CBR流,這個值不能低於input.ts流的實際碼率。

fifo_size:ffmpeg采用av_fifo來緩存一定量數據用於控制平滑輸出,從ffmpeg的源碼中,這里設置27887,實際上ffmpeg內部分配大小為27887*188bytes的,可以
libavformat/udp.c中查看。

bitrate:控制碼率平滑輸出,需要設置和muxrate參數一樣,否則從av_fifo中輸出和輸入對應不上導致av_fifo會出現上下溢情況。

burst_bits:設置突發碼率的,這里設置為1000000突發碼率,控制平滑輸出時,防止輸入流那邊有突發,需要設置一個突發閥值。

音視頻+表+空包的碼率(muxrate)是恆定的,ffmpeg 通過調整空包碼率來保持復用碼率(CBR碼率)恆定;

但是這個恆定的碼率是秒級別的,直接輸出的話,碼率還是會抖動,bitrate參數是控制平滑輸出的;

若碼率6Mbit/s,一秒內要輸出的數據量就是5Mbit,平滑處理的時,將6Mbit的數據,分成10等分(舉例說明),每隔0.1s就發送500Kbit的數據,達到平滑輸出的目的。

FFMPEG 平滑輸出的核心代碼:

ffmpeg處理UDP平滑輸出代碼再libavformat/udp.c中的circular_buffer_task_tx接口中,如下代碼:


static void *circular_buffer_task_tx( void *_URLContext)
{
    URLContext *h = _URLContext;
    UDPContext *s = h->priv_data;
    int old_cancelstate;
    int64_t target_timestamp = av_gettime_relative();
    int64_t start_timestamp = av_gettime_relative();
    int64_t sent_bits = 0;


    //根據上面設置的burst_bits計算出碼率突發值,單位為us
    int64_t burst_interval = s->bitrate ? (s->burst_bits * 1000000 / s->bitrate) : 0;


    //max_delay根據平滑碼率bitrate值及每一次發送UDP包的大小來設置的,單位為us,一般UDP發送  
     大小設置 為1316字節,即h->max_packet_size值為1316,確定好max_delay后用於下面控制碼率 
      平滑輸 出。
    int64_t max_delay = s->bitrate ?  ((int64_t)h->max_packet_size * 8 * 1000000 / s->bitrate + 1) : 0;


    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelstate);
    pthread_mutex_lock(&s->mutex);


    if (ff_socket_nonblock(s->udp_fd, 0) < 0) {
        av_log(h, AV_LOG_ERROR, "Failed to set blocking mode");
        s->circular_buffer_error = AVERROR(EIO);
        goto end;
    }


    for(;;) {
        int len;
        const uint8_t *p;
        uint8_t tmp[4];
        int64_t timestamp;


        len=av_fifo_size(s->fifo);


        while (len<4) {
            if (s->close_req)
                goto end;
            if (pthread_cond_wait(&s->cond, &s->mutex) < 0) {
                goto end;
            }
            len=av_fifo_size(s->fifo);
        }


        av_fifo_generic_read(s->fifo, tmp, 4, NULL);
        len=AV_RL32(tmp);


        av_assert0(len >= 0);
        av_assert0(len <= sizeof(s->tmp));


        av_fifo_generic_read(s->fifo, s->tmp, len, NULL);


        pthread_mutex_unlock(&s->mutex);
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancelstate);


       //控制碼率平滑輸出
        if (s->bitrate) {
            timestamp = av_gettime_relative();
            if (timestamp < target_timestamp) {
                int64_t delay = target_timestamp - timestamp;
                if (delay > max_delay) {
                    delay = max_delay;
                    start_timestamp = timestamp + delay;
                    sent_bits = 0;
                }
                av_usleep(delay);
            } else {
                if (timestamp - burst_interval > target_timestamp) {
                    start_timestamp = timestamp - burst_interval;
                    sent_bits = 0;
                }
            }
            sent_bits += len * 8;
            target_timestamp = start_timestamp + sent_bits * 1000000 / s->bitrate;
        }


        p = s->tmp;
        while (len) {
            int ret;
            av_assert0(len > 0);
            if (!s->is_connected) {
                ret = sendto (s->udp_fd, p, len, 0,
                            (struct sockaddr *) &s->dest_addr,
                            s->dest_addr_len);
            } else
                ret = send(s->udp_fd, p, len, 0);
            if (ret >= 0) {
                len -= ret;
                p   += ret;
            } else {
                ret = ff_neterrno();
                if (ret != AVERROR(EAGAIN) && ret != AVERROR(EINTR)) {
                    pthread_mutex_lock(&s->mutex);
                    s->circular_buffer_error = ret;
                    pthread_mutex_unlock(&s->mutex);
                    return NULL;
                }
            }
        }


        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelstate);
        pthread_mutex_lock(&s->mutex);
    }


end:
    pthread_mutex_unlock(&s->mutex);
    return NULL;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM