当前位置: 首页 > article >正文

音视频入门基础:MPEG2-TS专题(9)——FFmpeg源码中,解码TS Header的实现

一、引言

FFmpeg源码对MPEG2-TS传输流/TS文件解复用时,在通过read_packet函数读取出一个transport packet后,会调用handle_packet函数来处理该transport packet:

static int handle_packets(MpegTSContext *ts, int64_t nb_packets)
{
//...
    for (;;) {
//...
        ret = read_packet(s, packet, ts->raw_packet_size, &data);
        if (ret != 0)
            break;
        ret = handle_packet(ts, data, avio_tell(s->pb));
//...
    }
//...
}

二、handle_packet函数

(一)handle_packet函数的定义

FFmpeg源码中使用handle_packet函数来处理一个transport packet,该函数的前半部分实现解码一个transport packet的TS Header。该函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/mpegts.c中:

/* handle one TS packet */
static int handle_packet(MpegTSContext *ts, const uint8_t *packet, int64_t pos)
{
    MpegTSFilter *tss;
    int len, pid, cc, expected_cc, cc_ok, afc, is_start, is_discontinuity,
        has_adaptation, has_payload;
    const uint8_t *p, *p_end;

    pid = AV_RB16(packet + 1) & 0x1fff;
    is_start = packet[1] & 0x40;
    tss = ts->pids[pid];
    if (ts->auto_guess && !tss && is_start) {
        add_pes_stream(ts, pid, -1);
        tss = ts->pids[pid];
    }
    if (!tss)
        return 0;
    if (is_start)
        tss->discard = discard_pid(ts, pid);
    if (tss->discard)
        return 0;
    ts->current_pid = pid;

    afc = (packet[3] >> 4) & 3;
    if (afc == 0) /* reserved value */
        return 0;
    has_adaptation   = afc & 2;
    has_payload      = afc & 1;
    is_discontinuity = has_adaptation &&
                       packet[4] != 0 && /* with length > 0 */
                       (packet[5] & 0x80); /* and discontinuity indicated */

    /* continuity check (currently not used) */
    cc = (packet[3] & 0xf);
    expected_cc = has_payload ? (tss->last_cc + 1) & 0x0f : tss->last_cc;
    cc_ok = pid == 0x1FFF || // null packet PID
            is_discontinuity ||
            tss->last_cc < 0 ||
            expected_cc == cc;

    tss->last_cc = cc;
    if (!cc_ok) {
        av_log(ts->stream, AV_LOG_DEBUG,
               "Continuity check failed for pid %d expected %d got %d\n",
               pid, expected_cc, cc);
        if (tss->type == MPEGTS_PES) {
            PESContext *pc = tss->u.pes_filter.opaque;
            pc->flags |= AV_PKT_FLAG_CORRUPT;
        }
    }

    if (packet[1] & 0x80) {
        av_log(ts->stream, AV_LOG_DEBUG, "Packet had TEI flag set; marking as corrupt\n");
        if (tss->type == MPEGTS_PES) {
            PESContext *pc = tss->u.pes_filter.opaque;
            pc->flags |= AV_PKT_FLAG_CORRUPT;
        }
    }

    p = packet + 4;
    if (has_adaptation) {
        int64_t pcr_h;
        int pcr_l;
        if (parse_pcr(&pcr_h, &pcr_l, packet) == 0)
            tss->last_pcr = pcr_h * 300 + pcr_l;
        /* skip adaptation field */
        p += p[0] + 1;
    }
   
//...
    return 0;
}

形参ts:既是输入型参数也是输出型参数,指向一个MpegTSContext类型变量。MpegTSContext结构体声明如下,存贮MPEG2-TS的上下文信息:

typedef struct MpegTSContext MpegTSContext;


struct MpegTSContext {
    const AVClass *class;
    /* user data */
    AVFormatContext *stream;
    /** raw packet size, including FEC if present */
    int raw_packet_size;

    int64_t pos47_full;

    /** if true, all pids are analyzed to find streams */
    int auto_guess;

    /** compute exact PCR for each transport stream packet */
    int mpeg2ts_compute_pcr;

    /** fix dvb teletext pts                                 */
    int fix_teletext_pts;

    int64_t cur_pcr;    /**< used to estimate the exact PCR */
    int64_t pcr_incr;   /**< used to estimate the exact PCR */

    /* data needed to handle file based ts */
    /** stop parsing loop */
    int stop_parse;
    /** packet containing Audio/Video data */
    AVPacket *pkt;
    /** to detect seek */
    int64_t last_pos;

    int skip_changes;
    int skip_clear;
    int skip_unknown_pmt;

    int scan_all_pmts;

    int resync_size;
    int merge_pmt_versions;
    int max_packet_size;

    int id;

    /******************************************/
    /* private mpegts data */
    /* scan context */
    /** structure to keep track of Program->pids mapping */
    unsigned int nb_prg;
    struct Program *prg;

    int8_t crc_validity[NB_PID_MAX];
    /** filters for various streams specified by PMT + for the PAT and PMT */
    MpegTSFilter *pids[NB_PID_MAX];
    int current_pid;

    AVStream *epg_stream;
    AVBufferPool* pools[32];
};

形参packet:输入型参数,存贮该transport packet的数据。

形参pos:输入型参数,文件位置指针当前位置相对于TS文件的文件首的偏移字节数。

返回值:返回0表示成功,返回一个负数表示出错。

(二)handle_packet函数中,解码TS Header的固定长度部分的实现

handle_packet函数中,首先通过下面语句将TS Header中的PID属性读取出来,赋值给变量pid。关于AV_RB16函数的用法可以参考:《FFmpeg源码:AV_RB32、AV_RB16、AV_RB8宏定义分析》:

    pid = AV_RB16(packet + 1) & 0x1fff;

将TS Header中的payload_unit_start_indicator属性读取出来,经过运算赋值给变量is_start。所以is_start的值为true时表示payload_unit_start_indicator属性为1,为false时表示payload_unit_start_indicator属性为0:

    is_start = packet[1] & 0x40;

得到上述解析出来的PID属性对应的用于PAT和PMT表指定的各种流的过滤器,赋值给变量tss:

    tss = ts->pids[pid];
    if (ts->auto_guess && !tss && is_start) {
        add_pes_stream(ts, pid, -1);
        tss = ts->pids[pid];
    }
    if (!tss)
        return 0;

根据调用者的programs selection,决定该pid是否被丢弃:

    if (is_start)
        tss->discard = discard_pid(ts, pid);
    if (tss->discard)
        return 0;

将TS Header中的adaptation_field_control属性读取出来,赋值给变量afc。如果afc的值为0,表示adaptation_field_control属性的值为0,表示保留 (供未来使用),此时handle_packet函数直接返回,解码器不对该transport packet进行处理:

    afc = (packet[3] >> 4) & 3;
    if (afc == 0) /* reserved value */
        return 0;

adaptation_field_control属性的的值为'10'时表示该transport packet仅有适配域,为'11'时表示适配域和载荷都存在。将“适配域是否存在”赋值给变量has_adaptation,has_adaptation为1表示适配域存在,has_adaptation为0表示不存在:

    has_adaptation   = afc & 2;

adaptation_field_control属性的的值为'01'时表示该transport packet无适配域仅有载荷,为'11'时表示适配域和载荷都存在。将“载荷是否存在”赋值给变量has_payload,has_payload为1表示载荷存在,has_payload为0表示不存在:

    has_payload      = afc & 1;

如果该transport packet适配域存在(has_adaptation为真),并且适配域长度adaptation_field_length不为0(packet[4] != 0),并且不连续指示位discontinuity_indicator为1(packet[5] & 0x80为真),变量is_discontinuity的值为1,表示当前分组(当前transport packet)处于不连续状态:

    is_discontinuity = has_adaptation &&
                       packet[4] != 0 && /* with length > 0 */
                       (packet[5] & 0x80); /* and discontinuity indicated */

将TS Header中的continuity_counter属性读取出来,赋值给变量cc。 tss->last_cc保存同一个PID的上一个transport packet的continuity_counter属性:

    /* continuity check (currently not used) */
    cc = (packet[3] & 0xf);
//...
    tss->last_cc = cc;

如果该transport packet的载荷存在(变量has_payload为真),让变量expected_cc赋值为同一个PID的上一个transport packet的continuity_counter属性的值加1;如果载荷不存在,让变量expected_cc赋值为同一个PID的上一个transport packet的continuity_counter属性的值:

    expected_cc = has_payload ? (tss->last_cc + 1) & 0x0f : tss->last_cc;

从《音视频入门基础:MPEG2-TS专题(3)——TS Header简介》可以知道,PID为0x1FFF(pid == 0x1FFF)时,该transport packet为null packet(空包);continuity_counter属性用于检查同一个PID的transport packet的连续性,每当一个transport packet中包含载荷时,该计数器加1。所以下面语句的意思是:检测continuity_counter属性的合法性,如果合法,变量cc_ok的值为1,不合法,值为0:

    cc_ok = pid == 0x1FFF || // null packet PID
            is_discontinuity ||
            tss->last_cc < 0 ||
            expected_cc == cc;

如果continuity_counter属性不合法,打印错误日志:"Continuity check failed for pid %d expected %d got %d\n":

    if (!cc_ok) {
        av_log(ts->stream, AV_LOG_DEBUG,
               "Continuity check failed for pid %d expected %d got %d\n",
               pid, expected_cc, cc);
        if (tss->type == MPEGTS_PES) {
            PESContext *pc = tss->u.pes_filter.opaque;
            pc->flags |= AV_PKT_FLAG_CORRUPT;
        }
    }

如果TS header的transport_error_indicator属性的值为1(packet[1] & 0x80为真),表示该transport packet损坏,打印错误日志:"Packet had TEI flag set; marking as corrupt\n":

    if (packet[1] & 0x80) {
        av_log(ts->stream, AV_LOG_DEBUG, "Packet had TEI flag set; marking as corrupt\n");
        if (tss->type == MPEGTS_PES) {
            PESContext *pc = tss->u.pes_filter.opaque;
            pc->flags |= AV_PKT_FLAG_CORRUPT;
        }
    }

(三)handle_packet函数中,解码TS Header的适配域的实现

解析完TS Header的固定长度部分,handle_packet函数接下来会解析适配域。handle_packet函数中通过调用parse_pcr函数来解析适配域中的PCR:

    p = packet + 4;
    if (has_adaptation) {
        int64_t pcr_h;
        int pcr_l;
        if (parse_pcr(&pcr_h, &pcr_l, packet) == 0)
            tss->last_pcr = pcr_h * 300 + pcr_l;
        /* skip adaptation field */
        p += p[0] + 1;
    }

parse_pcr函数定义如下:

/* return the 90kHz PCR and the extension for the 27MHz PCR. return
 * (-1) if not available */
static int parse_pcr(int64_t *ppcr_high, int *ppcr_low, const uint8_t *packet)
{
    int afc, len, flags;
    const uint8_t *p;
    unsigned int v;

    afc = (packet[3] >> 4) & 3;
    if (afc <= 1)
        return AVERROR_INVALIDDATA;
    p   = packet + 4;
    len = p[0];
    p++;
    if (len == 0)
        return AVERROR_INVALIDDATA;
    flags = *p++;
    len--;
    if (!(flags & 0x10))
        return AVERROR_INVALIDDATA;
    if (len < 6)
        return AVERROR_INVALIDDATA;
    v          = AV_RB32(p);
    *ppcr_high = ((int64_t) v << 1) | (p[4] >> 7);
    *ppcr_low  = ((p[4] & 1) << 8) | p[5];
    return 0;
}

形参ppcr_high:输出型参数,执行parse_pcr函数后,*ppcr_high会得到PCR域中的program_clock_reference_base属性。

形参ppcr_low:输出型参数,执行parse_pcr函数后,*ppcr_low会得到PCR域中的program_clock_reference_extension属性。

形参packet:输入型参数,存贮该transport packet的数据。

返回值:返回0表示解析成功,返回一个负数表示解析失败。

parse_pcr函数中,首先将TS Header中的adaptation_field_control属性读取出来,赋值给变量afc。如果afc不大于1,表示adaptation_field_control属性的值为'00'或'01',此时TS Header中没有适配域,parse_pcr函数返回AVERROR_INVALIDDATA:

    afc = (packet[3] >> 4) & 3;
    if (afc <= 1)
        return AVERROR_INVALIDDATA;

将TS Header适配域中的adaptation_field_length属性读取出来,赋值给变量len,这样变量len就会存贮适配域长度。如果适配域长度为0(len == 0),返回AVERROR_INVALIDDATA:

    p   = packet + 4;
    len = p[0];
    p++;
    if (len == 0)
        return AVERROR_INVALIDDATA;

判断适配域中PCR_flag的是否为0,如果为0(!(flags & 0x10)为真),表示适配域中没有PCR域 ,返回AVERROR_INVALIDDATA:

    flags = *p++;
    len--;
    if (!(flags & 0x10))
        return AVERROR_INVALIDDATA;

得到PCR域中的program_clock_reference_base属性,赋值给*ppcr_high,得到PCR域中的program_clock_reference_extension属性,赋值给*ppcr_low:

    v          = AV_RB32(p);
    *ppcr_high = ((int64_t) v << 1) | (p[4] >> 7);
    *ppcr_low  = ((p[4] & 1) << 8) | p[5];

————————————————分隔符————————————————

回到handle_packet函数,当解析适配域成功(parse_pcr(&pcr_h, &pcr_l, packet) == 0为真)时,通过语句:tss->last_pcr = pcr_h * 300 + pcr_l 计算出PCR。可以看到FFmpeg源码中计算PCR的方法和《音视频入门基础:MPEG2-TS专题(8)——TS Header中的适配域》中介绍的公式:PCR = program_clock_reference_base × 300 + program_clock_reference_extension 是一样的:

    if (has_adaptation) {
        int64_t pcr_h;
        int pcr_l;
        if (parse_pcr(&pcr_h, &pcr_l, packet) == 0)
            tss->last_pcr = pcr_h * 300 + pcr_l;
        /* skip adaptation field */
        p += p[0] + 1;
    }


http://www.kler.cn/a/415648.html

相关文章:

  • Linux自动化部署方法(Linux Automated Deployment Method)
  • 一次完整的CNAS软件测试实验室内部审核流程
  • 大数据新视界 -- 大数据大厂之 Hive 数据安全:权限管理体系的深度解读(上)(15/ 30)
  • 【无标题】多模态对齐
  • ceph的用户管理和cephx认证
  • Python编程实例-实现环形缓冲(Ring Buffer)
  • wareshark分析mysql协议的数据包
  • 【Jenkins】docker 部署 Jenkins 踩坑笔记
  • 微信小程序:实现定时拍照与自动上传功能攻略——静音版
  • 【C++】深入探讨基础输入输出及类型转换问题
  • cesium 3Dtiles变量
  • 2024年陕西科技大学数据结构程序填空题+预测
  • 精准零售驱动下的中国零售业变革与“开源 2+1 链动小程序”应用探究
  • 网络爬虫的原理
  • 从语法、功能、社区和使用场景来比较 Sass 和 LESS
  • AI一键生成3D动画:腾讯最新方案,为小程序带来革命性变化
  • AI开发:逻辑回归 - 实战演练- 垃圾邮件的识别(二)
  • 爬虫技术:探索网络世界的钥匙
  • [Redis#10] scan | db_0 | redis_cli | RESP | C++-redis启动教程
  • 多线程——01
  • Vue-TreeSelect组件最下级隐藏No sub-options
  • 动态规划-斐波那契数列模型
  • Electron文件写入、读取(作用:公共全局变量,本地存储)
  • Python蒙特卡罗MCMC:优化Metropolis-Hastings采样策略Fisher矩阵计算参数推断应用—模拟与真实数据...
  • 海康面阵、线阵、读码器及3D相机接线说明
  • springboot-vue excel上传导出