音视频入门基础: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系统新上线的卓易通和出境易两款应用。 老刘分析了一下这个软件的一些细节,觉得还是蛮有意思的,我觉得可以从使用体验、底层原理和对鸿蒙生态的影响这三个角度来分析一下。 使用体验 性能 看到了一些测…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
jdbc查询mysql数据库时,出现id顺序错误的情况
我在repository中的查询语句如下所示,即传入一个List<intager>的数据,返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致,会导致返回的id是从小到大排列的,但我不希望这样。 Query("SELECT NEW com…...
【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解
一、前言 在HarmonyOS 5的应用开发模型中,featureAbility是旧版FA模型(Feature Ability)的用法,Stage模型已采用全新的应用架构,推荐使用组件化的上下文获取方式,而非依赖featureAbility。 FA大概是API7之…...
python学习day39
图像数据与显存 知识点回顾 1.图像数据的格式:灰度和彩色数据 2.模型的定义 3.显存占用的4种地方 a.模型参数梯度参数 b.优化器参数 c.数据批量所占显存 d.神经元输出中间状态 4.batchisize和训练的关系 import torch import torchvision import torch.nn as nn imp…...
