音视频入门基础:MPEG2-TS专题(15)——FFmpeg源码中,解析Program association section的实现

发布于:2024-12-18 ⋅ 阅读:(63) ⋅ 点赞:(0)

一、引言

由《音视频入门基础:MPEG2-TS专题(14)——PAT简介》可以知道,PAT表(Program association table)由一个或多个段(Program association section,即组成PAT表的段)组成。当某个PAT表非常大时,只要接收到一个Program association section的完整数据就可以进行解析,而不需要接收到完整的表时才开始解析工作,解析完各个Program association section后再把这些信息汇总起来。FFmpeg源码中,通过pat_cb函数解析Program association section。

二、pat_cb函数定义

pat_cb函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/mpegts.c中:

static void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
{
    MpegTSContext *ts = filter->u.section_filter.opaque;
    MpegTSSectionFilter *tssf = &filter->u.section_filter;
    SectionHeader h1, *h = &h1;
    const uint8_t *p, *p_end;
    int sid, pmt_pid;
    int nb_prg = 0;
    AVProgram *program;

    av_log(ts->stream, AV_LOG_TRACE, "PAT:\n");
    hex_dump_debug(ts->stream, section, section_len);

    p_end = section + section_len - 4;
    p     = section;
    if (parse_section_header(h, &p, p_end) < 0)
        return;
    if (h->tid != PAT_TID)
        return;
    if (!h->current_next)
        return;
    if (ts->skip_changes)
        return;

    if (skip_identical(h, tssf))
        return;
    ts->id = h->id;

    for (;;) {
        sid = get16(&p, p_end);
        if (sid < 0)
            break;
        pmt_pid = get16(&p, p_end);
        if (pmt_pid < 0)
            break;
        pmt_pid &= 0x1fff;

        if (pmt_pid == ts->current_pid)
            break;

        av_log(ts->stream, AV_LOG_TRACE, "sid=0x%x pid=0x%x\n", sid, pmt_pid);

        if (sid == 0x0000) {
            /* NIT info */
        } else {
            MpegTSFilter *fil = ts->pids[pmt_pid];
            struct Program *prg;
            program = av_new_program(ts->stream, sid);
            if (program) {
                program->program_num = sid;
                program->pmt_pid = pmt_pid;
            }
            if (fil)
                if (   fil->type != MPEGTS_SECTION
                    || fil->pid != pmt_pid
                    || fil->u.section_filter.section_cb != pmt_cb)
                    mpegts_close_filter(ts, ts->pids[pmt_pid]);

            if (!ts->pids[pmt_pid])
                mpegts_open_section_filter(ts, pmt_pid, pmt_cb, ts, 1);
            prg = add_program(ts, sid);
            if (prg) {
                unsigned prg_idx = prg - ts->prg;
                if (prg->nb_pids && prg->pids[0] != pmt_pid)
                    clear_program(prg);
                add_pid_to_program(prg, pmt_pid);
                if (prg_idx > nb_prg)
                    FFSWAP(struct Program, ts->prg[nb_prg], ts->prg[prg_idx]);
                if (prg_idx >= nb_prg)
                    nb_prg++;
            } else
                nb_prg = 0;
        }
    }
    ts->nb_prg = nb_prg;

    if (sid < 0) {
        int i,j;
        for (j=0; j<ts->stream->nb_programs; j++) {
            for (i = 0; i < ts->nb_prg; i++)
                if (ts->prg[i].id == ts->stream->programs[j]->id)
                    break;
            if (i==ts->nb_prg && !ts->skip_clear)
                clear_avprogram(ts, ts->stream->programs[j]->id);
        }
    }
}

该函数的作用是:解析TS流中的Program association section,提取出里面的属性。

形参filter:输出型参数,指向一个MpegTSFilter类型变量。执行pat_cb函数后,数组((MpegTSContext *)(filter->u.section_filter.opaque))->prg会得到从Program association section中解析出来的属性。

形参section:输入型参数。存放一个Program association section(PAT section)的数据,即“将一个或多个包含PAT表信息的transport packet去掉它们TS Header和pointer_field后的有效数据组合起来后”的数据。

section_len:输入型参数。该Program association section的长度,单位为字节。

返回值:无

三、pat_cb函数的内部实现分析

pat_cb函数中首先通过下面语句让指针p_end指向该Program association section中有效数据的末尾,即CRC_32属性的开头;让指针p指向Program association section的开头:

     const uint8_t *p, *p_end;   
//...    
     p_end = section + section_len - 4;
     p     = section;

通过parse_section_header函数解析Section Header,这样指针h就会得到从Section Header中解析出来的属性。关于parse_section_header函数的用法可以参考:《音视频入门基础:MPEG2-TS专题(13)——FFmpeg源码中,解析Section Header的实现》:

    if (parse_section_header(h, &p, p_end) < 0)
        return;

宏PAT_TID定义如下:

#define PAT_TID         0x00 /* Program Association section */

判断SectionHeader中的table_id属性是否为PAT_TID(0x00),PAT表的table_id固定为0x00,如果不是,表示这不是PAT表,pat_cb函数直接返回:

    if (h->tid != PAT_TID)
        return;

判断SectionHeader中的current_next_indicator属性的值,如果值为1,表示该PAT当前有效,pat_cb函数继续往下执行;值为0表示下一个PAT有效,pat_cb函数直接返回:

    if (!h->current_next)
        return;

读取SectionHeader中的program_number属性:

    for (;;) {
        sid = get16(&p, p_end);
        if (sid < 0)
            break;
    //...
    }

读取SectionHeader中的network_PID或program_map_PID属性:

        pmt_pid = get16(&p, p_end);
        if (pmt_pid < 0)
            break;
        pmt_pid &= 0x1fff;

如果读取到的network_PID或program_map_PID的值等于当前Section的PID,由于network_PID是NIT的PID,program_map_PID是PMT的PID,当前Section的PID是PAT表的PID,所以这时表示出错了,通过break语句跳出循环:

        if (pmt_pid == ts->current_pid)
            break;

如果program_number的值为0x0000,变量pmt_pid的值为network_PID(NIT的PID);否则变量pmt_pid的值为program_map_PID(PMT的PID):

        if (sid == 0x0000) {
            /* NIT info */
        } else {
           //...
        }

数组ts->stream->programs中的每个元素都对应一个PMT表的信息。通过下面代码让ts->stream->programs[i]->program_num赋值为program_number属性的值,让ts->stream->programs[i]->pmt_pid赋值为program_map_PID属性的值,i为该节目是TS流中的第几个节目。使得后续可以通过遍历ts->stream->programs找到对应的PMT表的信息:

            MpegTSFilter *fil = ts->pids[pmt_pid];
            struct Program *prg;
            program = av_new_program(ts->stream, sid);
            if (program) {
                program->program_num = sid;
                program->pmt_pid = pmt_pid;
            }

让ts->prg[ts->nb_prg]->id赋值为program_number属性的值:

            prg = add_program(ts, sid);

让ts->prg[ts->nb_prg]->pid[nb_pids]赋值为program_map_PID属性的值:

                add_pid_to_program(prg, pmt_pid);


网站公告

今日签到

点亮在社区的每一天
去签到