音视频入门基础: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系统新上线的卓易通和出境易两款应用。 老刘分析了一下这个软件的一些细节,觉得还是蛮有意思的,我觉得可以从使用体验、底层原理和对鸿蒙生态的影响这三个角度来分析一下。 使用体验 性能 看到了一些测…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...
