音视频入门基础: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++;}
相关文章:
音视频入门基础:MPEG2-TS专题(17)——FFmpeg源码中,解析TS program map section的实现
一、引言 由《音视频入门基础:MPEG2-TS专题(16)——PMT简介》可以知道,PMT表(Program map table)由一个或多个段(Transport stream program map section,简称TS program map sectio…...
了解https原理,对称加密/非对称加密原理,浏览器与服务器加密的进化过程,https做了些什么
最开始的加密 浏览器与服务器之间需要防止传输的数据被黑客破解。因此,浏览器在发送数据时会对数据进行加密,并把加密的密钥(或密钥的某些部分)放在数据的某一个区域中。服务器收到数据后,会提取密钥并用它来解密数据…...
山西省第十八届职业院校技能大赛高职组 5G 组网与运维赛项规程
山西省第十八届职业院校技能大赛高职组 5G 组网与运维赛项规程 一、赛项名称 赛项编号:GZ035 赛项名称:5G 组网与运维 赛项组别:高职学生组、教师组 二、竞赛目的 2019 年 6 月 6 日,5G 牌照正式发放,标志着我国全面进…...
tcpdump编译 wireshark远程抓包
https://github.com/westes/flex/releases/download/v2.6.4/flex-2.6.4.tar.gz tar -zxvf flex-2.6.4.tar.gz ./configure CFLAGS-D_GNU_SOURCE make sudo make installwget http://ftp.gnu.org/gnu/bison/bison-3.2.1.tar.gz ./configure make sudo make install以上两个库是…...
Web开发 -前端部分-CSS
CSS CSS(Cascading Style Sheet):层叠样式表,用于控制页面的样式(表现)。 一 基础知识 1 标题格式 标题格式一: 行内样式 <!DOCTYPE html> <html lang"en"><head><meta…...
用 Python Turtle 绘制流动星空:编程中的璀璨星河
用 Python Turtle 绘制流动星空:编程中的璀璨星河 🐸 前言 🐸🐞往期绘画>>点击进所有绘画🐞🐋 效果图 🐋🐉 代码 🐉 🐸 前言 🐸 夜空中繁星…...
Java从入门到工作2 - IDEA
2.1、项目启动 从git获取到项目代码后,用idea打开。 安装依赖完成Marven/JDK等配置检查数据库配置启动相关服务 安装依赖 如果个别依赖从私服下载不了,可以去maven官网下载补充。 如果run时提示程序包xx不存在,在项目目录右键Marven->Re…...
fastadmin批量压缩下载远程视频文件
后端代码 // 批量下载并压缩 public function downloadAll(){$ids input(ids);$row $this->model->where(id, in, $ids)->field(id,title,video_url)->select();if (!$row) {$this->error(记录不存在);}$arr [];$tempFiles []; // 用来存储临时下载的视频文…...
【保姆级】Mac如何安装+切换Java环境
本文从如何下载不同版本的JDK,到如何丝滑的切换JDK,以及常见坑坑的处理方法,应有尽有,各位看官走过路过不要错过~~~ 下载⏬ 首先上官网: https://www.oracle.com/ 打不开的话可以使用下面👇这个中文的 https://www.oracle.com/cn/java/technologies/downloads/a…...
2024首届世界酒中国菜国际地理标志产品美食文化节成功举办篇章
2024首届世界酒中国菜国际地理标志产品美食文化节成功举办,开启美食文化交流新篇章 近日,首届世界酒中国菜国际地理标志产品美食文化节在中国国际地理标志大厦成功举办,这场为期三天的美食文化盛会吸引了来自世界各地的美食爱好者、行业专家…...
Springboot静态资源
默认位置 静态资源访问目录下的资源可以直接访问,默认的四个位置 classpath:/META-INF/resources/(默认加载,不受自定义配置的影响) classpath:/resources/ classpath:/static/ classpath:/public/ 如果在静态目录下存在favic…...
MTK修改配置更改产品类型ro.build.characteristics
文章目录 需求场景实际问题 参考资料解决方案MTK 修改方案修改点一:build\core\product_config.mk修改点二:build\make\core\main.mk修改是否成功,adb 验证 实战项目中解决案例 需求场景 更改产品设备属性 table-phone-device,使…...
SQL 查询中的动态字段过滤
这段代码是一个 SQL 查询中的动态字段过滤部分,使用了 MyBatis 的 标签和 标签。以下是逐步的解释: <!-- 动态字段过滤 --><if test"parameters ! null and parameters.size() > 0"><foreach collection"parameters&qu…...
数字IC后端零基础入门基础理论(Day1)
数字IC后端设计导入需要用到的input数据如下图所示。 数字后端零基础入门系列 | Innovus零基础LAB学习Day9 Netlist: 设计的Gate level(门级)网表。下图所示为一个计数器设计综合后的门级netlist。 从这个netlist中我们看到这个设计顶层的名字叫counte…...
【LC】240. 搜索二维矩阵 II
题目描述: 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性: 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1: 输入:matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,…...
Redis应用—4.在库存里的应用
大纲 1.库存模块设计 2.库存缓存分片和渐进式同步方案 3.基于缓存分片的下单库存扣减方案 4.商品库存设置流程与异步落库的实现 6.库存入库时"缓存分片写入 渐进式写入 写入失败进行MQ补偿"的实现 7.库存扣减时"基于库存分片依次扣减 合并扣减 扣不了…...
selenium获取请求头
【原创】Selenium获取请求头、响应头-腾讯云开发者社区-腾讯云 selenium 4.0.0 selenium-wire 5.1.0 python 3.10 from seleniumwire import webdriver import time from selenium.webdriver.common.by import By import re def get_request_headers(driver):"""…...
Rust中自定义Debug调试输出
在 Rust 中,通过为类型实现 fmt::Debug,可以自定义该类型的调试输出。fmt::Debug 是标准库中的一个格式化 trait,用于实现 {:?} 格式的打印。这个 trait 通常通过自动派生(#[derive(Debug)])来实现,但你也…...
docker离线安装、linux 安装docker
之前写过一篇docker的离线安装,现在从头再看繁琐了,服务器换了,既然要重搭一遍就要改进一下了。下面步入正题: 1.下载离线软件包 https://download.docker.com/linux/static/stable/x86_64/docker-20.10.6.tgz 2.下载安装工具包…...
卓易通:鸿蒙Next系统的蜜糖还是毒药?
哈喽,我是老刘 最近很多人都在问鸿蒙next系统新上线的卓易通和出境易两款应用。 老刘分析了一下这个软件的一些细节,觉得还是蛮有意思的,我觉得可以从使用体验、底层原理和对鸿蒙生态的影响这三个角度来分析一下。 使用体验 性能 看到了一些测…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...
自然语言处理——文本分类
文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益(IG) 分类器设计贝叶斯理论:线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别, 有单标签多类别文本分类和多…...
