1. 现象

ffplay播放RTSP流媒体时控制台频繁打印

jitter buffer full
RTP: missed 88 packets

同时画面卡顿。

2. 分析

找到FFMPEG 3.3.2源码FFmpeg\libavformat\rtpdec.crtp_parse_one_packet函数代码:

if ((s->seq == 0 && !s->queue) || s->queue_size <= 1) {
    /* First packet, or no reordering */
    return rtp_parse_packet_internal(s, pkt, buf, len);
} else {
    uint16_t seq = AV_RB16(buf + 2);
    int16_t diff = seq - s->seq;

    if (diff < 0) {
        /* Packet older than the previously emitted one, drop */
        av_log(s->ic, AV_LOG_WARNING,
               "RTP: dropping old packet received too late\n");
        return -1;
    } else if (diff <= 1) {
        /* Correct packet */
        rv = rtp_parse_packet_internal(s, pkt, buf, len);
        return rv;
    } else {
        /* Still missing some packet, enqueue this one. */
        rv = enqueue_packet(s, buf, len);
        if (rv < 0)
            return rv;
        *bufptr = NULL;

        /* Return the first enqueued packet if the queue is full,
         * even if we're missing something */
        if (s->queue_len >= s->queue_size) {
            av_log(s->ic, AV_LOG_WARNING, "jitter buffer full\n");
            return rtp_parse_queued_packet(s, pkt);
        }
        return -1;
    }
}

看上去是s->queue_len >= s->queue_size时就输出"jitter buffer full",前者应该是变化的,后者是通过rtsp.c中的ff_rtsp_open_transport_ctx() -> ff_rtp_parse_open()来设置的,ff_rtsp_open_transport_ctx()代码如下:

RTSPState *rt = s->priv_data;
AVStream *st = NULL;

int reordering_queue_size = rt->reordering_queue_size;
if (reordering_queue_size < 0) {
    if (rt->lower_transport == RTSP_LOWER_TRANSPORT_TCP || !s->max_delay)
        reordering_queue_size = 0;
    else
        reordering_queue_size = RTP_REORDER_QUEUE_DEFAULT_SIZE;
}

/* open the RTP context */
if (rtsp_st->stream_index >= 0)
    st = s->streams[rtsp_st->stream_index];
if (!st)
    s->ctx_flags |= AVFMTCTX_NOHEADER;

if (CONFIG_RTSP_MUXER && s->oformat && st) {
    int ret = ff_rtp_chain_mux_open((AVFormatContext **)&rtsp_st->transport_priv,
                                    s, st, rtsp_st->rtp_handle,
                                    RTSP_TCP_MAX_PACKET_SIZE,
                                    rtsp_st->stream_index);

    /* Ownership of rtp_handle is passed to the rtp mux context */
    rtsp_st->rtp_handle = NULL;
    if (ret < 0)
        return ret;
    st->time_base = ((AVFormatContext*)rtsp_st->transport_priv)->streams[0]->time_base;
} else if (rt->transport == RTSP_TRANSPORT_RAW) {
    return 0; // Don't need to open any parser here
} else if (CONFIG_RTPDEC && rt->transport == RTSP_TRANSPORT_RDT && st)
    rtsp_st->transport_priv = ff_rdt_parse_open(s, st->index,
                                        rtsp_st->dynamic_protocol_context,
                                        rtsp_st->dynamic_handler);
else if (CONFIG_RTPDEC)
    rtsp_st->transport_priv = ff_rtp_parse_open(s, st,
                                     rtsp_st->sdp_payload_type,
                                     reordering_queue_size);

rtsp.c开头有下面代码,表明上面第3行的rt->reordering_queue_size可通过命令行设置,并且初始值等于-1

#define COMMON_OPTS() \
    { "reorder_queue_size", "set number of packets to buffer for handling of reordered packets", OFFSET(reordering_queue_size), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, DEC }, \
    { "buffer_size",        "Underlying protocol send/receive buffer size",                  OFFSET(buffer_size),           AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, DEC|ENC } \

再结合ff_rtsp_open_transport_ctx()的4-9行,说明如果使用rtsp_transport tcp来指明TCP传输并且没有设置reordering_queue_size,那么调用ff_rtp_parse_open()传递的queue_size等等于0,接下来开头rtp_parse_one_packet()函数的第1行,判断出s->queue_size <= 1就不会打印”jitter buffer full”。

再参考下下面代码:

static int has_next_packet(RTPDemuxContext *s)
{
    return s->queue && s->queue->seq == (uint16_t) (s->seq + 1);
}

int64_t ff_rtp_queued_packet_time(RTPDemuxContext *s)
{
    return s->queue ? s->queue->recvtime : 0;
}

static int rtp_parse_queued_packet(RTPDemuxContext *s, AVPacket *pkt)
{
    int rv;
    RTPPacket *next;

    if (s->queue_len <= 0)
        return -1;

    if (!has_next_packet(s))
        av_log(s->ic, AV_LOG_WARNING,
                "RTP: missed %d packets\n", s->queue->seq - s->seq - 1);

    /* Parse the first packet in the queue, and dequeue it */
    rv   = rtp_parse_packet_internal(s, pkt, s->queue->buf, s->queue->len);
    next = s->queue->next;
    av_freep(&s->queue->buf);
    av_freep(&s->queue);
    s->queue = next;
    s->queue_len--;
    return rv;
}

3. 原因及对策

所以发生该问题时,原因有两种:

3.1. 采用UDP传输,默认的或手动设置的reordering_queue_size值过小。

解决办法:继续增大reordering_queue_size

3.2. 采用TCP传输,手动设置的reordering_queue_size值过小。

解决办法:不设置reordering_queue_size

4. 查看reordering_queue_size大小

4.1. UDP传输时reordering_queue_size

ffmpeg -rtsp_transport udp -i rtsp://192.168.5.120:8554/desktop -loglevel verbose

可看到

[rtsp @ 0000000000537fe0] setting jitter buffer size to 500

其实就是rtsp.c中的RTP_REORDER_QUEUE_DEFAULT_SIZE

4.2. TCP传输时reordering_queue_size

ffmpeg -rtsp_transport tcp -i rtsp://192.168.5.120:8554/desktop -loglevel verbose

可看到

[rtsp @ 0000000000647fe0] setting jitter buffer size to 0

5. reordering_queue_size的意义

根据rtpdec.crtp_parse_one_packet(),大概UDP传输RTP时会出现乱序,比如上一个RTP序号是88,新接收到RTP包序号100,此时ffplay将继续等待序号为89的RTP包而不解析序号100的包,如果现在积攒了500个RTP包,但89号包还没有到达,则打印该警告,并且解析离88最近的那个RTP包。