终端接收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;
}
posted @ 2020-03-24 11:07  standardzero  阅读(2509)  评论(0编辑  收藏  举报