自学内容网 自学内容网

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

一、引言

FFmpeg源码中,通过pat_cb函数解析PAT表。

二、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流中的PAT表,提取出里面的属性。

形参filter:输出型参数,指向一个MpegTSFilter类型变量。

形参section:输入型参数。指向一个Program association section(PAT section)的数据,即一个PAT表去掉了TS Header和pointer_field后的有效数据。

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

返回值:无

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

pat_cb函数中首先通过下面语句让指针p_end指向PAT表中有效数据的末尾,即CRC_32属性的开头;让指针p指向PAT表的开头,即PAT section中去掉了TS Header和pointer_field后的有效数据:

     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当前有效,接续往下执行;值为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;
            }


原文地址:https://blog.csdn.net/u014552102/article/details/144148558

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!