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

一、引言

由《音视频入门基础: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.mzph.cn/bicheng/64240.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为HarmonyOS NEXT 原生应用开发:鸿蒙中组件的组件状态管理、组件通信 组件状态管理小案例(好友录)!

文章目录 组件状态管理一、State装饰器1. State装饰器的特点2. State装饰器的使用 二、Prop装饰器&#xff08;父子单向通信&#xff09;1. Prop装饰器的特点2. Prop装饰器的使用示例 三、Link装饰器&#xff08;父子双向通信&#xff09;1. Link装饰器的特点3. Link使用示例 四…

Pytorch | 对比Pytorch中的十种优化器:基于CIFAR10上的ResNet分类器

Pytorch | 对比Pytorch中的十种优化器&#xff1a;基于CIFAR10上的ResNet分类器 CIFAR10数据集ResNet提出背景网络结构特点工作原理优势 代码实现分析utils.pymain.py导入必要的库设备选择与数据预处理定义加载训练集和测试集主函数部分训练部分测试部分 结果10种优化器对应的训…

Linux系统操作03|chmod、vim

上文&#xff1a; Linux系统操作02|基本命令-CSDN博客 目录 六、chmod&#xff1a;给文件设置权限 1、字母法 2、数字法&#xff08;用的最多&#xff09; 七、vim&#xff1a;代码编写和文本编辑 1、启动和退出 1️⃣启动 2️⃣退出 2、vim基本操作 六、chmod&#x…

【漏洞复现】eking管理易Html5Upload接口存在任意文件上传漏洞

🏘️个人主页: 点燃银河尽头的篝火(●’◡’●) 如果文章有帮到你的话记得点赞👍+收藏💗支持一下哦 【漏洞复现】eking管理易Html5Upload接口存在任意文件上传漏洞 一、漏洞概述1.1漏洞简介1.2组件描述1.3漏洞描述二、漏洞复现2.1 应用协议2.2 环境搭建2.3 漏洞复现三、…

徐州数字孪生工业互联网可视化技术,赋能新型工业化智能制造工厂

#徐州数字孪生工业互联网#在当下智能制造的热潮之下&#xff0c;徐州作为中国制造业的重要基地&#xff0c;正积极拥抱数字化转型&#xff0c;通过数字孪生工业互联网可视化技术&#xff0c;赋能新型工业化智能制造工厂&#xff0c;引领制造业向更高效、更智能、更绿色的方向发…

C# 探险之旅:第二十四节 - 类型class基础,一场“类”似的奇妙冒险

嘿&#xff0c;勇敢的探险家们&#xff01;欢迎来到C#王国的“类”似奇妙冒险&#xff01;今天&#xff0c;我们要深入探索一个神秘而强大的领域——class&#xff08;类&#xff09;。想象一下&#xff0c;class就像C#世界里的一块魔法土地&#xff0c;每块土地上都能孕育出独…

解决MyBatis在 Oracle 中使用 IN 语句不能超过 1000 问题

在 Oracle 数据库中&#xff0c;IN 语句常用于查询某个字段是否属于一组特定的值。对于大多数开发者而言&#xff0c;IN 是一种简单直观的查询方式&#xff0c;能够提升开发效率&#xff0c;避免过多的 OR 语句。然而&#xff0c;许多人在使用 IN 语句时可能遇到一个问题&#…

(五)机器学习 - 数据分布

数据分布&#xff08;Data Distribution&#xff09;是指数据在不同值或值区间内的分布情况&#xff0c;它描述了数据点在整个数据集中是如何分散或集中的。数据分布可以通过多种方式来分析和表示&#xff0c;包括图形和数值方法。 常见的数据分布特征和描述数据分布的方法&…

C# 探险之旅:第二十三节 - 字符(char):字符小精灵的独舞

嘿&#xff0c;探险家们&#xff01;欢迎再次踏上C#王国的奇妙旅程。这一节里&#xff0c;我们要深入探索一个非常基础但又极其重要的角色——字符(char)。想象一下&#xff0c;你正在参加一场由单个字母和数字组成的精灵舞会&#xff0c;每个精灵都代表着一个独特的字符。让我…

【JAVA】Java项目实战—分布式微服务项目:分布式文件存储系统

Java分布式微服务项目&#xff1a;分布式文件存储系统 一、背景介绍 随着互联网的快速发展和用户数据量的激增&#xff0c;软件开发中&#xff0c;传统的单体应用架构逐渐无法满足高并发、高可用和可扩展的需求。微服务架构应运而生&#xff0c;它将一个大型应用拆分为多个小…

苹果电脑可以安装windows操作系统吗?Mac OS X/OS X/macOS傻傻分不清?macOS系统的Java支持?什么是macOS的五大API法王?

苹果电脑可以安装windows操作系统吗? 先抛开虚拟机安装&#xff0c;苹果电脑可以安装Windows操作系统。苹果公司提供了一个名为Boot Camp的软件&#xff0c;它允许用户在Mac电脑上安装Windows操作系统。通过Boot Camp&#xff0c;用户可以在启动电脑时选择是要进入macOS还是Wi…

基于stm32的红外测温系统设计(论文+源码)

1总体方案设计 本课题为基于STM32的红外测温系统设计&#xff0c;在此将系统架构设计如图3.1所示&#xff0c; 整个系统包括STM32F103单片机&#xff0c;红外测温模块MLX90614&#xff0c;显示模块OLED12864&#xff0c;蜂鸣器以及按键等构成&#xff0c;在功能上&#xff0c;…

网易游戏分享游戏场景中MongoDB运行和分析实践

在游戏行业中&#xff0c;数据库的稳定和性能直接影响了游戏质量和用户满意度。在竞争激烈的游戏市场中&#xff0c;一个优秀的数据库产品无疑能为游戏的开发和后期的运营奠定良好的基础。伴随着MongoDB在不同类型游戏场景中的应用越来越广泛&#xff0c;许多知名的游戏公司都在…

【杭州电商商城系统开发建设】

杭州电商商城系统开发建设是一项综合性的工程&#xff0c;它涉及到多个方面的内容。以下是对杭州电商商城系统开发建设的详细分析&#xff1a; 需求分析&#xff1a;深入了解用户需求&#xff0c;包括用户群体特征、购物习惯、支付偏好等&#xff0c;为系统设计提供基础。明确…

排序算法(5):归并排序

问题 排序 [30, 24, 5, 58, 18, 36, 12, 42, 39] 归并排序 归并排序采用分治法&#xff0c;将序列分成若干子序列&#xff0c;每个子序列有序后再合并成有序的完整序列。 在数组排序中&#xff0c;如果只有一个数&#xff0c;那么它本身就是有序的。如果有两个数&#xff0…

前端将base64转pdf页面预览

前端将base64转pdf页面预览 <embed :src"pdfList" width"100%" height"100%" type"application/pdf" />pdfList.value data:application/pdf;base64,${res}//后端传jpg或pdf格式可直接 :src“返回内容”显示

MySQL 主从复制与高可用架构

一、MySQL 主从复制概述 &#xff08;一&#xff09;定义与作用 MySQL 主从复制是一种允许在多个 MySQL 数据库服务器之间进行数据同步的技术。简单来说&#xff0c;就是可以把数据从一个 MySQL 服务器&#xff08;主服务器、主节点&#xff09;复制到一个或多个从节点&#…

JSSIP的使用及问题(webRTC,WebSockets)

简介 项目中有一个需要拨打电话的功能&#xff0c;要求实时的进行音频接听&#xff0c;并且可以在电话接听或者挂断等情况下做出相应的操作。jssip作为一个强大的实现实时通信的javascript库&#xff0c;这不门当户对了嘛。 jssip&#xff08;官网&#xff1a; JsSIP - the J…

DP3复现代码运行逻辑全流程(六)—— gen_demonstration_adroit.sh 演示生成与可视化

用于生成演示、培训和评估的脚本都在 Scripts/ 文件夹中 DP3 通过 gen_demonstration 生成演示&#xff0c;即训练数据&#xff0c;例如: bash scripts/gen_demonstration_adroit.sh hammer 这将在 Adroit 环境中生成锤子任务的演示。数据将自动保存在 3D-Diffusion-Policy/…

Python常用字符串排序●sorted()函数--一行语句简洁实现

在Python等编程中&#xff0c;时常会用到字符串排序。 今天在这里只讲讲最常用的Python字符串排序。 同时&#xff0c;只讲sorted()函数方法。 给定一个字符串列表&#xff1a; sl [共和国, 中国, 中华人民共和国, 大中华, 人民共和国]。 第一种排序方法是不使用任何参数…