修复FFMPEG 复用 PATPMT发送间隔小于25ms的错误

Posted standardzero

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了修复FFMPEG 复用 PATPMT发送间隔小于25ms的错误相关的知识,希望对你有一定的参考价值。

分析ffmpeg源码

分析问题

mpegtsenc.c

找到发送PAT、PMT的函数

/* send SDT, PAT and PMT tables regularly */
static void retransmit_si_info(AVFormatContext *s, int force_pat, int64_t dts)
{
    MpegTSWrite *ts = s->priv_data;
    int i;

    if (++ts->sdt_packet_count == ts->sdt_packet_period ||
        (dts != AV_NOPTS_VALUE && ts->last_sdt_ts == AV_NOPTS_VALUE) ||
        (dts != AV_NOPTS_VALUE && dts - ts->last_sdt_ts >= ts->sdt_period*90000.0)
    ) {
        ts->sdt_packet_count = 0;
        if (dts != AV_NOPTS_VALUE)
            ts->last_sdt_ts = FFMAX(dts, ts->last_sdt_ts);
        mpegts_write_sdt(s);
    }
    if (++ts->pat_packet_count == ts->pat_packet_period ||
        (dts != AV_NOPTS_VALUE && ts->last_pat_ts == AV_NOPTS_VALUE) ||
        (dts != AV_NOPTS_VALUE && dts - ts->last_pat_ts >= ts->pat_period*90000.0) ||
        force_pat) {
        ts->pat_packet_count = 0;
        if (dts != AV_NOPTS_VALUE)
            ts->last_pat_ts = FFMAX(dts, ts->last_pat_ts);
        mpegts_write_pat(s);
        for (i = 0; i < ts->nb_services; i++)
            mpegts_write_pmt(s, ts->services[i]);
    }
}

从源码分析,可以知道PAT、PMT的发送条件

if (++ts->pat_packet_count == ts->pat_packet_period ||
        (dts != AV_NOPTS_VALUE && ts->last_pat_ts == AV_NOPTS_VALUE) ||
        (dts != AV_NOPTS_VALUE && dts - ts->last_pat_ts >= ts->pat_period*90000.0) ||
        force_pat)

dts != AV_NOPTS_VALUE && ts->last_pat_ts == AV_NOPTS_VALUE:第一次发送PAT、PMT用到的是这个条件。

++ts->pat_packet_count == ts->pat_packet_period:这个条件是定时100ms发送PAT、PMT;ts->pat_packet_period 这个字段在函数mpegts_init里面有定义。

ts->last_pat_ts >= ts->pat_period*90000.0: 这个条件用户自定义发表间隔时会生效,ts->pat_period 这个字段默认值是INT_MAX。

ts->pat_period:这个字段可以通过如下接口进行用户自定义设置:

av_opt_set(ofmt_ctx->priv_data, "pat_period",   "0.01",      0);

:ffmpeg 巧妙的地方时,你用户自定义后,定时100ms发送的条件就会失效。

force_pat: 强制发送PAT、PMT,这个参数是函数入参,故需要查看函数retransmit_si_info被谁调用。

搜索代码可知,只有mpegts_write_pes调用此函数。

static void mpegts_write_pes(AVFormatContext *s, AVStream *st,
                             const uint8_t *payload, int payload_size,
                             int64_t pts, int64_t dts, int key, int stream_id)
{
    MpegTSWriteStream *ts_st = st->priv_data;
    MpegTSWrite *ts = s->priv_data;
    uint8_t buf[TS_PACKET_SIZE];
    uint8_t *q;
    int val, is_start, len, header_len, write_pcr, is_dvb_subtitle, is_dvb_teletext, flags;
    int afc_len, stuffing_len;
    int64_t pcr = -1; /* avoid warning */
    int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE);
    int force_pat = st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && key && !ts_st->prev_payload_key;

    av_assert0(ts_st->payload != buf || st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO);
    if (ts->flags & MPEGTS_FLAG_PAT_PMT_AT_FRAMES && st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
        force_pat = 1;
    }

    is_start = 1;
    while (payload_size > 0) {
        retransmit_si_info(s, force_pat, dts);
        force_pat = 0;

        write_pcr = 0;

分析源码可知,force_pat遇到视频关键帧时,会被赋值为1。

由此分析可知,当定时发送完PAT、PMT后,25ms内来了一帧关键帧,也会发送PAT、PMT,导致出现PAT、PMT间隔小于25ms这个错误。

修改源码解决问题

修改函数retransmit_si_info:将发送PAT、PMT条件中的force_pat去掉即可

/* send SDT, PAT and PMT tables regularly */
static void retransmit_si_info(AVFormatContext *s, int force_pat, int64_t dts)
{
    MpegTSWrite *ts = s->priv_data;
    int i;

    if (++ts->sdt_packet_count == ts->sdt_packet_period ||
        (dts != AV_NOPTS_VALUE && ts->last_sdt_ts == AV_NOPTS_VALUE) ||
        (dts != AV_NOPTS_VALUE && dts - ts->last_sdt_ts >= ts->sdt_period*90000.0)
    ) {
        ts->sdt_packet_count = 0;
        if (dts != AV_NOPTS_VALUE)
            ts->last_sdt_ts = FFMAX(dts, ts->last_sdt_ts);
        mpegts_write_sdt(s);
    }

    #if 0
    if (++ts->pat_packet_count == ts->pat_packet_period ||
        (dts != AV_NOPTS_VALUE && ts->last_pat_ts == AV_NOPTS_VALUE) ||
        (dts != AV_NOPTS_VALUE && dts - ts->last_pat_ts >= ts->pat_period*90000.0) ||
        force_pat)
    #endif
    if (++ts->pat_packet_count == ts->pat_packet_period ||
        (dts != AV_NOPTS_VALUE && ts->last_pat_ts == AV_NOPTS_VALUE) ||
        (dts != AV_NOPTS_VALUE && dts - ts->last_pat_ts >= ts->pat_period*90000.0))
        {
        ts->pat_packet_count = 0;
        if (dts != AV_NOPTS_VALUE)
            ts->last_pat_ts = FFMAX(dts, ts->last_pat_ts);
        mpegts_write_pat(s);
        for (i = 0; i < ts->nb_services; i++)
            mpegts_write_pmt(s, ts->services[i]);
    }
}

以上是关于修复FFMPEG 复用 PATPMT发送间隔小于25ms的错误的主要内容,如果未能解决你的问题,请参考以下文章

FFmpeg 进行不必要的复用

码分复用

FFMPEG 多路复用视频和音频(来自另一个视频) - 映射问题

FFmpegffmpeg 命令查询一 ( 版本 | 编译配置 | 复用格式 | 编解码器 )

FFmpegffmpeg 命令查询一 ( 版本 | 编译配置 | 复用格式 | 编解码器 )

ffmpeg 捕捉摄像头 间隔