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

ops/2024/12/17 15:51:42/

一、引言

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

二、pmt_cb函数定义

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

static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
{MpegTSContext *ts = filter->u.section_filter.opaque;MpegTSSectionFilter *tssf = &filter->u.section_filter;struct Program old_program;SectionHeader h1, *h = &h1;PESContext *pes;AVStream *st;const uint8_t *p, *p_end, *desc_list_end;int program_info_length, pcr_pid, pid, stream_type;int desc_list_len;uint32_t prog_reg_desc = 0; /* registration descriptor */int stream_identifier = -1;struct Program *prg;int mp4_descr_count = 0;Mp4Descr mp4_descr[MAX_MP4_DESCR_COUNT] = { { 0 } };int i;av_log(ts->stream, AV_LOG_TRACE, "PMT: len %i\n", section_len);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 != PMT_TID)return;if (!h->current_next)return;if (skip_identical(h, tssf))return;av_log(ts->stream, AV_LOG_TRACE, "sid=0x%x sec_num=%d/%d version=%d tid=%d\n",h->id, h->sec_num, h->last_sec_num, h->version, h->tid);if (!ts->scan_all_pmts && ts->skip_changes)return;prg = get_program(ts, h->id);if (prg)old_program = *prg;elseclear_program(&old_program);if (ts->skip_unknown_pmt && !prg)return;if (prg && prg->nb_pids && prg->pids[0] != ts->current_pid)return;if (!ts->skip_clear)clear_avprogram(ts, h->id);clear_program(prg);add_pid_to_program(prg, ts->current_pid);pcr_pid = get16(&p, p_end);if (pcr_pid < 0)return;pcr_pid &= 0x1fff;add_pid_to_program(prg, pcr_pid);update_av_program_info(ts->stream, h->id, pcr_pid, h->version);av_log(ts->stream, AV_LOG_TRACE, "pcr_pid=0x%x\n", pcr_pid);program_info_length = get16(&p, p_end);if (program_info_length < 0)return;program_info_length &= 0xfff;while (program_info_length >= 2) {uint8_t tag, len;tag = get8(&p, p_end);len = get8(&p, p_end);av_log(ts->stream, AV_LOG_TRACE, "program tag: 0x%02x len=%d\n", tag, len);program_info_length -= 2;if (len > program_info_length)// something else is broken, exit the program_descriptors_loopbreak;program_info_length -= len;if (tag == IOD_DESCRIPTOR) {get8(&p, p_end); // scopeget8(&p, p_end); // labellen -= 2;mp4_read_iods(ts->stream, p, len, mp4_descr + mp4_descr_count,&mp4_descr_count, MAX_MP4_DESCR_COUNT);} else if (tag == REGISTRATION_DESCRIPTOR && len >= 4) {prog_reg_desc = bytestream_get_le32(&p);len -= 4;}p += len;}p += program_info_length;if (p >= p_end)goto out;// stop parsing after pmt, we found headerif (!ts->pkt)ts->stop_parse = 2;if (prg)prg->pmt_found = 1;for (i = 0; i < MAX_STREAMS_PER_PROGRAM; i++) {st = 0;pes = NULL;stream_type = get8(&p, p_end);if (stream_type < 0)break;pid = get16(&p, p_end);if (pid < 0)goto out;pid &= 0x1fff;if (pid == ts->current_pid)goto out;stream_identifier = parse_stream_identifier_desc(p, p_end) + 1;/* now create stream */if (ts->pids[pid] && ts->pids[pid]->type == MPEGTS_PES) {pes = ts->pids[pid]->u.pes_filter.opaque;if (ts->merge_pmt_versions && !pes->st) {st = find_matching_stream(ts, pid, h->id, stream_identifier, i, &old_program);if (st) {pes->st = st;pes->stream_type = stream_type;pes->merged_st = 1;}}if (!pes->st) {pes->st = avformat_new_stream(pes->stream, NULL);if (!pes->st)goto out;pes->st->id = pes->pid;}st = pes->st;} else if (is_pes_stream(stream_type, prog_reg_desc)) {if (ts->pids[pid])mpegts_close_filter(ts, ts->pids[pid]); // wrongly added sdt filter probablypes = add_pes_stream(ts, pid, pcr_pid);if (ts->merge_pmt_versions && pes && !pes->st) {st = find_matching_stream(ts, pid, h->id, stream_identifier, i, &old_program);if (st) {pes->st = st;pes->stream_type = stream_type;pes->merged_st = 1;}}if (pes && !pes->st) {st = avformat_new_stream(pes->stream, NULL);if (!st)goto out;st->id = pes->pid;}} else {int idx = ff_find_stream_index(ts->stream, pid);if (idx >= 0) {st = ts->stream->streams[idx];}if (ts->merge_pmt_versions && !st) {st = find_matching_stream(ts, pid, h->id, stream_identifier, i, &old_program);}if (!st) {st = avformat_new_stream(ts->stream, NULL);if (!st)goto out;st->id = pid;st->codecpar->codec_type = AVMEDIA_TYPE_DATA;if (stream_type == 0x86 && prog_reg_desc == AV_RL32("CUEI")) {mpegts_find_stream_type(st, stream_type, SCTE_types);mpegts_open_section_filter(ts, pid, scte_data_cb, ts, 1);}}}if (!st)goto out;if (pes && !pes->stream_type)mpegts_set_stream_info(st, pes, stream_type, prog_reg_desc);add_pid_to_program(prg, pid);if (prg) {prg->streams[i].idx = st->index;prg->streams[i].stream_identifier = stream_identifier;prg->nb_streams++;}av_program_add_stream_index(ts->stream, h->id, st->index);desc_list_len = get16(&p, p_end);if (desc_list_len < 0)goto out;desc_list_len &= 0xfff;desc_list_end  = p + desc_list_len;if (desc_list_end > p_end)goto out;for (;;) {if (ff_parse_mpeg2_descriptor(ts->stream, st, stream_type, &p,desc_list_end, mp4_descr,mp4_descr_count, pid, ts) < 0)break;if (pes && prog_reg_desc == AV_RL32("HDMV") &&stream_type == 0x83 && pes->sub_st) {av_program_add_stream_index(ts->stream, h->id,pes->sub_st->index);pes->sub_st->codecpar->codec_tag = st->codecpar->codec_tag;}}p = desc_list_end;}if (!ts->pids[pcr_pid])mpegts_open_pcr_filter(ts, pcr_pid);out:for (i = 0; i < mp4_descr_count; i++)av_free(mp4_descr[i].dec_config_descr);
}

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

形参filter:输出型参数,指向一个MpegTSFilter类型变量。执行pmt_cb函数后,(PESContext *)(((MpegTSContext *)(filter->u.section_filter.opaque))->pids[pid]->u.pes_filter.opaque)会得到从TS program map section中解析出来的属性。pid为该节目音频或视频流的PID。

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

section_len:输入型参数。该TS program map section的长度,单位为字节。

返回值:无

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

pmt_cb函数中首先通过下面语句让指针p_end指向该TS program map section中有效数据的末尾,即CRC_32属性的开头;让指针p指向该TS program map 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;

宏PMT_TID定义如下:

#define PMT_TID         0x02 /* Program Map section */

判断SectionHeader中的table_id属性是否为PMT_TID(0x02),PMT表的table_id固定为0x02,如果不是,表示这不是PMT表,pmt_cb函数直接返回:

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

判断SectionHeader中的current_next_indicator属性的值,如果值为1,表示发送的TS program map section为当前有效的,pmt_cb函数继续往下执行;值为0表示发送的该Section尚未有效并且下一个Section将生效,pmt_cb函数直接返回:

    if (!h->current_next)return;

h->id是该TS program map section的program_number属性。从《音视频入门基础:MPEG2-TS专题(15)——FFmpeg源码中,解析Program association section的实现》可以知道,FFmpeg源码解析完PAT表的Section后,ts->stream->programs会得到从PAT表的Section中解析出来的属性。所以在pmt_cb函数中通过语句:prg = get_program(ts, h->id),让变量prg拿到之前保存在ts->stream->programs中的该节目对应的program_number属性和program_map_PID属性的信息:

prg = get_program(ts, h->id);if (prg)old_program = *prg;elseclear_program(&old_program);if (ts->skip_unknown_pmt && !prg)return;if (prg && prg->nb_pids && prg->pids[0] != ts->current_pid)return;if (!ts->skip_clear)clear_avprogram(ts, h->id);clear_program(prg);add_pid_to_program(prg, ts->current_pid);

读取TS program map section中的PCR_PID属性(PCR所在transport packet的PID),赋值给变量pcr_pid:

    pcr_pid = get16(&p, p_end);if (pcr_pid < 0)return;pcr_pid &= 0x1fff;

将上述得到的PCR_PID属性赋值给ts->stream->programs[i]->pcr_pid,将version_number属性赋值给ts->stream->programs[i]->pmt_version,i为该节目是TS流中的第几个节目:

    add_pid_to_program(prg, pcr_pid);update_av_program_info(ts->stream, h->id, pcr_pid, h->version);

读取TS program map section中的program_info_length属性,赋值给变量program_info_length:

    program_info_length = get16(&p, p_end);if (program_info_length < 0)return;program_info_length &= 0xfff;

program_info_length属性不小于2,表示program_info_length属性之后存在节目描述信息,读取节目描述信息:

    while (program_info_length >= 2) {uint8_t tag, len;tag = get8(&p, p_end);len = get8(&p, p_end);av_log(ts->stream, AV_LOG_TRACE, "program tag: 0x%02x len=%d\n", tag, len);program_info_length -= 2;if (len > program_info_length)// something else is broken, exit the program_descriptors_loopbreak;program_info_length -= len;if (tag == IOD_DESCRIPTOR) {get8(&p, p_end); // scopeget8(&p, p_end); // labellen -= 2;mp4_read_iods(ts->stream, p, len, mp4_descr + mp4_descr_count,&mp4_descr_count, MAX_MP4_DESCR_COUNT);} else if (tag == REGISTRATION_DESCRIPTOR && len >= 4) {prog_reg_desc = bytestream_get_le32(&p);len -= 4;}p += len;}

循环读取TS program map section中的stream_type属性(媒体流的类型),赋值给变量stream_type:

    for (i = 0; i < MAX_STREAMS_PER_PROGRAM; i++) {st = 0;pes = NULL;stream_type = get8(&p, p_end);if (stream_type < 0)break;//...
}

循环读取TS program map section中的elementary_PID属性(节目的音频或视频PID),赋值给变量pid:

        pid = get16(&p, p_end);if (pid < 0)goto out;pid &= 0x1fff;

将上述得到的elementary_PID属性和PCR_PID属性保存到指针pes指向的PESContext结构中:

else if (is_pes_stream(stream_type, prog_reg_desc)) {if (ts->pids[pid])mpegts_close_filter(ts, ts->pids[pid]); // wrongly added sdt filter probablypes = add_pes_stream(ts, pid, pcr_pid);if (ts->merge_pmt_versions && pes && !pes->st) {st = find_matching_stream(ts, pid, h->id, stream_identifier, i, &old_program);if (st) {pes->st = st;pes->stream_type = stream_type;pes->merged_st = 1;}}if (pes && !pes->st) {st = avformat_new_stream(pes->stream, NULL);if (!st)goto out;st->id = pes->pid;}} 

将上述得到的stream_type属性保存到指针pes指向的PESContext结构中:

        if (pes && !pes->stream_type)mpegts_set_stream_info(st, pes, stream_type, prog_reg_desc);

将上述得到的elementary_PID属性保存到指针prg指向的内存中。由于prg等于((MpegTSContext *)(filter->u.section_filter.opaque))->prg,而上述代码中又存在:MpegTSContext *ts = filter->u.section_filter.opaque 以及 pes = ts->pids[pid]->u.pes_filter.opaque。所以执行pmt_cb函数后,(PESContext *)(((MpegTSContext *)(filter->u.section_filter.opaque))->pids[pid]->u.pes_filter.opaque)会得到从TS program map section中解析出来的属性,pid为该节目音频或视频流的PID:

        add_pid_to_program(prg, pid);if (prg) {prg->streams[i].idx = st->index;prg->streams[i].stream_identifier = stream_identifier;prg->nb_streams++;}


http://www.ppmy.cn/ops/142677.html

相关文章

Docker之mysql主从复制

1、​​主从搭建步骤 1.1 新建主服务器容器实例3307 docker run -p 3307:3306 --name mysql-master \ -v /mydata/mysql-master/log:/var/log/mysql \ -v /mydata/mysql-master/data:/var/lib/mysql \ -v /mydata/mysql-master/conf:/etc/mysql \ -e MYSQL_ROOT_PASSWORDroot …

活着就好20241217

亲爱的朋友们&#xff0c;大家早上好&#xff01;&#x1f31e; 今天是17号&#xff0c;星期二&#xff0c;2024年12月的第十七天&#xff0c;同时也是第50周的第八天&#xff0c;农历甲辰[龙]年十一月初十三日。在这晨光熹微的美好时刻&#xff0c;愿那温暖而明媚的阳光轻轻拂…

nVisual关于钉钉小程序打开项目及调试说明

关于钉钉小程序开发者工具的使用对于没有接触过的人可能比较陌生。如果需要部署钉钉小程序是需要对钉钉小程序开发者工具有一定的了解的&#xff0c;需要在此做部分上线前的测试及在开发者工具中上传项目包&#xff0c;故此做部分工具的解释。 分三部分来进行解释&#xff1a;…

QT图形/视图架构详解(一)

场景、视图与图形项 图形/视图架构主要由 3 个部分组成&#xff0c;即场景、视图和图形项&#xff0c;三者的关系如图所示&#xff1a; 场景、视图和图形项的关系 场景&#xff08;QGraphicsScene 类&#xff09; 场景不是界面组件&#xff0c;它是不可见的。场景是一个抽象的…

网络安全—认证技术

通过了解以前前辈们使用的消息认证慢慢渐进到现代的完整的认证体系。所以在学习的时候也很蒙圈&#xff0c;因为前期的很多技术都是有很严重的不可靠性&#xff0c;因此本篇用作加深理解认证技术的发展史还有到目前为止完整的不可抵赖的认证体系是如何形成的&#xff0c;并且了…

全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之复合运算符

&#xff08;一&#xff09;、复合运算符 在C中&#xff0c;可以通过“赋值语句”来修改变量的值。赋值语句的格式&#xff1a; 变量名 值或者表达式&#xff1b;其中""称为"赋值运算符"。 除此之外&#xff0c;在赋值运算符当中&#xff0c;C有复合赋…

网络安全协议之比较(SSH、PKI、SET、SSL)

一、SSH介绍 什么是SSH&#xff1f;   传统的网络服务程序&#xff0c;如&#xff1a;ftp、pop和telnet在本质上都是不安全的&#xff0c;因为它们在网络上用明文传送口令和数据&#xff0c; 别有用心的人非常容易就可以截获这些口令和数据。而且&#xff0c;这些服务程序的…

MySQL笛卡尔积

MySQL的笛卡尔积是通过将两个表的所有行组合生成的结果集。它是一种比较耗时的操作&#xff0c;因此通常建议在实际应用中尽量避免使用笛卡尔积。 在MySQL中&#xff0c;可以通过使用CROSS JOIN来实现笛卡尔积。例如&#xff0c;假设有两个表A和B&#xff0c;可以使用以下语法…