音视频入门基础:MPEG2-TS专题(9)——FFmpeg源码中,解码TS Header的实现
一、引言
FFmpeg源码对MPEG2-TS传输流/TS文件解复用时,在通过read_packet函数读取出一个transport packet后,会调用handle_packet函数来处理该transport packet:
static int handle_packets(MpegTSContext *ts, int64_t nb_packets)
{
//...for (;;) {
//...ret = read_packet(s, packet, ts->raw_packet_size, &data);if (ret != 0)break;ret = handle_packet(ts, data, avio_tell(s->pb));
//...}
//...
}
二、handle_packet函数
(一)handle_packet函数的定义
FFmpeg源码中使用handle_packet函数来处理一个transport packet,该函数的前半部分实现解码一个transport packet的TS Header。该函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/mpegts.c中:
/* handle one TS packet */
static int handle_packet(MpegTSContext *ts, const uint8_t *packet, int64_t pos)
{MpegTSFilter *tss;int len, pid, cc, expected_cc, cc_ok, afc, is_start, is_discontinuity,has_adaptation, has_payload;const uint8_t *p, *p_end;pid = AV_RB16(packet + 1) & 0x1fff;is_start = packet[1] & 0x40;tss = ts->pids[pid];if (ts->auto_guess && !tss && is_start) {add_pes_stream(ts, pid, -1);tss = ts->pids[pid];}if (!tss)return 0;if (is_start)tss->discard = discard_pid(ts, pid);if (tss->discard)return 0;ts->current_pid = pid;afc = (packet[3] >> 4) & 3;if (afc == 0) /* reserved value */return 0;has_adaptation = afc & 2;has_payload = afc & 1;is_discontinuity = has_adaptation &&packet[4] != 0 && /* with length > 0 */(packet[5] & 0x80); /* and discontinuity indicated *//* continuity check (currently not used) */cc = (packet[3] & 0xf);expected_cc = has_payload ? (tss->last_cc + 1) & 0x0f : tss->last_cc;cc_ok = pid == 0x1FFF || // null packet PIDis_discontinuity ||tss->last_cc < 0 ||expected_cc == cc;tss->last_cc = cc;if (!cc_ok) {av_log(ts->stream, AV_LOG_DEBUG,"Continuity check failed for pid %d expected %d got %d\n",pid, expected_cc, cc);if (tss->type == MPEGTS_PES) {PESContext *pc = tss->u.pes_filter.opaque;pc->flags |= AV_PKT_FLAG_CORRUPT;}}if (packet[1] & 0x80) {av_log(ts->stream, AV_LOG_DEBUG, "Packet had TEI flag set; marking as corrupt\n");if (tss->type == MPEGTS_PES) {PESContext *pc = tss->u.pes_filter.opaque;pc->flags |= AV_PKT_FLAG_CORRUPT;}}p = packet + 4;if (has_adaptation) {int64_t pcr_h;int pcr_l;if (parse_pcr(&pcr_h, &pcr_l, packet) == 0)tss->last_pcr = pcr_h * 300 + pcr_l;/* skip adaptation field */p += p[0] + 1;}//...return 0;
}
形参ts:既是输入型参数也是输出型参数,指向一个MpegTSContext类型变量。MpegTSContext结构体声明如下,存贮MPEG2-TS的上下文信息:
typedef struct MpegTSContext MpegTSContext;struct MpegTSContext {const AVClass *class;/* user data */AVFormatContext *stream;/** raw packet size, including FEC if present */int raw_packet_size;int64_t pos47_full;/** if true, all pids are analyzed to find streams */int auto_guess;/** compute exact PCR for each transport stream packet */int mpeg2ts_compute_pcr;/** fix dvb teletext pts */int fix_teletext_pts;int64_t cur_pcr; /**< used to estimate the exact PCR */int64_t pcr_incr; /**< used to estimate the exact PCR *//* data needed to handle file based ts *//** stop parsing loop */int stop_parse;/** packet containing Audio/Video data */AVPacket *pkt;/** to detect seek */int64_t last_pos;int skip_changes;int skip_clear;int skip_unknown_pmt;int scan_all_pmts;int resync_size;int merge_pmt_versions;int max_packet_size;int id;/******************************************//* private mpegts data *//* scan context *//** structure to keep track of Program->pids mapping */unsigned int nb_prg;struct Program *prg;int8_t crc_validity[NB_PID_MAX];/** filters for various streams specified by PMT + for the PAT and PMT */MpegTSFilter *pids[NB_PID_MAX];int current_pid;AVStream *epg_stream;AVBufferPool* pools[32];
};
形参packet:输入型参数,存贮该transport packet的数据。
形参pos:输入型参数,文件位置指针当前位置相对于TS文件的文件首的偏移字节数。
返回值:返回0表示成功,返回一个负数表示出错。
(二)handle_packet函数中,解码TS Header的固定长度部分的实现
handle_packet函数中,首先通过下面语句将TS Header中的PID属性读取出来,赋值给变量pid。关于AV_RB16函数的用法可以参考:《FFmpeg源码:AV_RB32、AV_RB16、AV_RB8宏定义分析》:
pid = AV_RB16(packet + 1) & 0x1fff;
将TS Header中的payload_unit_start_indicator属性读取出来,经过运算赋值给变量is_start。所以is_start的值为true时表示payload_unit_start_indicator属性为1,为false时表示payload_unit_start_indicator属性为0:
is_start = packet[1] & 0x40;
得到上述解析出来的PID属性对应的用于PAT和PMT表指定的各种流的过滤器,赋值给变量tss:
tss = ts->pids[pid];if (ts->auto_guess && !tss && is_start) {add_pes_stream(ts, pid, -1);tss = ts->pids[pid];}if (!tss)return 0;
根据调用者的programs selection,决定该pid是否被丢弃:
if (is_start)tss->discard = discard_pid(ts, pid);if (tss->discard)return 0;
将TS Header中的adaptation_field_control属性读取出来,赋值给变量afc。如果afc的值为0,表示adaptation_field_control属性的值为0,表示保留 (供未来使用),此时handle_packet函数直接返回,解码器不对该transport packet进行处理:
afc = (packet[3] >> 4) & 3;if (afc == 0) /* reserved value */return 0;
adaptation_field_control属性的的值为'10'时表示该transport packet仅有适配域,为'11'时表示适配域和载荷都存在。将“适配域是否存在”赋值给变量has_adaptation,has_adaptation为1表示适配域存在,has_adaptation为0表示不存在:
has_adaptation = afc & 2;
adaptation_field_control属性的的值为'01'时表示该transport packet无适配域仅有载荷,为'11'时表示适配域和载荷都存在。将“载荷是否存在”赋值给变量has_payload,has_payload为1表示载荷存在,has_payload为0表示不存在:
has_payload = afc & 1;
如果该transport packet适配域存在(has_adaptation为真),并且适配域长度adaptation_field_length不为0(packet[4] != 0),并且不连续指示位discontinuity_indicator为1(packet[5] & 0x80为真),变量is_discontinuity的值为1,表示当前分组(当前transport packet)处于不连续状态:
is_discontinuity = has_adaptation &&packet[4] != 0 && /* with length > 0 */(packet[5] & 0x80); /* and discontinuity indicated */
将TS Header中的continuity_counter属性读取出来,赋值给变量cc。 tss->last_cc保存同一个PID的上一个transport packet的continuity_counter属性:
/* continuity check (currently not used) */cc = (packet[3] & 0xf);
//...tss->last_cc = cc;
如果该transport packet的载荷存在(变量has_payload为真),让变量expected_cc赋值为同一个PID的上一个transport packet的continuity_counter属性的值加1;如果载荷不存在,让变量expected_cc赋值为同一个PID的上一个transport packet的continuity_counter属性的值:
expected_cc = has_payload ? (tss->last_cc + 1) & 0x0f : tss->last_cc;
从《音视频入门基础:MPEG2-TS专题(3)——TS Header简介》可以知道,PID为0x1FFF(pid == 0x1FFF)时,该transport packet为null packet(空包);continuity_counter属性用于检查同一个PID的transport packet的连续性,每当一个transport packet中包含载荷时,该计数器加1。所以下面语句的意思是:检测continuity_counter属性的合法性,如果合法,变量cc_ok的值为1,不合法,值为0:
cc_ok = pid == 0x1FFF || // null packet PIDis_discontinuity ||tss->last_cc < 0 ||expected_cc == cc;
如果continuity_counter属性不合法,打印错误日志:"Continuity check failed for pid %d expected %d got %d\n":
if (!cc_ok) {av_log(ts->stream, AV_LOG_DEBUG,"Continuity check failed for pid %d expected %d got %d\n",pid, expected_cc, cc);if (tss->type == MPEGTS_PES) {PESContext *pc = tss->u.pes_filter.opaque;pc->flags |= AV_PKT_FLAG_CORRUPT;}}
如果TS header的transport_error_indicator属性的值为1(packet[1] & 0x80为真),表示该transport packet损坏,打印错误日志:"Packet had TEI flag set; marking as corrupt\n":
if (packet[1] & 0x80) {av_log(ts->stream, AV_LOG_DEBUG, "Packet had TEI flag set; marking as corrupt\n");if (tss->type == MPEGTS_PES) {PESContext *pc = tss->u.pes_filter.opaque;pc->flags |= AV_PKT_FLAG_CORRUPT;}}
(三)handle_packet函数中,解码TS Header的适配域的实现
解析完TS Header的固定长度部分,handle_packet函数接下来会解析适配域。handle_packet函数中通过调用parse_pcr函数来解析适配域中的PCR:
p = packet + 4;if (has_adaptation) {int64_t pcr_h;int pcr_l;if (parse_pcr(&pcr_h, &pcr_l, packet) == 0)tss->last_pcr = pcr_h * 300 + pcr_l;/* skip adaptation field */p += p[0] + 1;}
parse_pcr函数定义如下:
/* return the 90kHz PCR and the extension for the 27MHz PCR. return* (-1) if not available */
static int parse_pcr(int64_t *ppcr_high, int *ppcr_low, const uint8_t *packet)
{int afc, len, flags;const uint8_t *p;unsigned int v;afc = (packet[3] >> 4) & 3;if (afc <= 1)return AVERROR_INVALIDDATA;p = packet + 4;len = p[0];p++;if (len == 0)return AVERROR_INVALIDDATA;flags = *p++;len--;if (!(flags & 0x10))return AVERROR_INVALIDDATA;if (len < 6)return AVERROR_INVALIDDATA;v = AV_RB32(p);*ppcr_high = ((int64_t) v << 1) | (p[4] >> 7);*ppcr_low = ((p[4] & 1) << 8) | p[5];return 0;
}
形参ppcr_high:输出型参数,执行parse_pcr函数后,*ppcr_high会得到PCR域中的program_clock_reference_base属性。
形参ppcr_low:输出型参数,执行parse_pcr函数后,*ppcr_low会得到PCR域中的program_clock_reference_extension属性。
形参packet:输入型参数,存贮该transport packet的数据。
返回值:返回0表示解析成功,返回一个负数表示解析失败。
parse_pcr函数中,首先将TS Header中的adaptation_field_control属性读取出来,赋值给变量afc。如果afc不大于1,表示adaptation_field_control属性的值为'00'或'01',此时TS Header中没有适配域,parse_pcr函数返回AVERROR_INVALIDDATA:
afc = (packet[3] >> 4) & 3;if (afc <= 1)return AVERROR_INVALIDDATA;
将TS Header适配域中的adaptation_field_length属性读取出来,赋值给变量len,这样变量len就会存贮适配域长度。如果适配域长度为0(len == 0),返回AVERROR_INVALIDDATA:
p = packet + 4;len = p[0];p++;if (len == 0)return AVERROR_INVALIDDATA;
判断适配域中PCR_flag的是否为0,如果为0(!(flags & 0x10)为真),表示适配域中没有PCR域 ,返回AVERROR_INVALIDDATA:
flags = *p++;len--;if (!(flags & 0x10))return AVERROR_INVALIDDATA;
得到PCR域中的program_clock_reference_base属性,赋值给*ppcr_high,得到PCR域中的program_clock_reference_extension属性,赋值给*ppcr_low:
v = AV_RB32(p);*ppcr_high = ((int64_t) v << 1) | (p[4] >> 7);*ppcr_low = ((p[4] & 1) << 8) | p[5];
————————————————分隔符————————————————
回到handle_packet函数,当解析适配域成功(parse_pcr(&pcr_h, &pcr_l, packet) == 0为真)时,通过语句:tss->last_pcr = pcr_h * 300 + pcr_l 计算出PCR。可以看到FFmpeg源码中计算PCR的方法和《音视频入门基础:MPEG2-TS专题(8)——TS Header中的适配域》中介绍的公式:PCR = program_clock_reference_base × 300 + program_clock_reference_extension 是一样的:
if (has_adaptation) {int64_t pcr_h;int pcr_l;if (parse_pcr(&pcr_h, &pcr_l, packet) == 0)tss->last_pcr = pcr_h * 300 + pcr_l;/* skip adaptation field */p += p[0] + 1;}
相关文章:
音视频入门基础:MPEG2-TS专题(9)——FFmpeg源码中,解码TS Header的实现
一、引言 FFmpeg源码对MPEG2-TS传输流/TS文件解复用时,在通过read_packet函数读取出一个transport packet后,会调用handle_packet函数来处理该transport packet: static int handle_packets(MpegTSContext *ts, int64_t nb_packets) { //..…...

解决“磁盘已插上,但Windows系统无法识别“问题
电脑上有2块硬盘,一块是500GB的固态硬盘,另一块是1000GB的机械硬盘,按下开机键,发现500G的固态硬盘识别了,但1000GB的机械硬盘却无法识别。后面为了描述方便,将"500GB的固态硬盘"称为X盘…...

论文笔记-WWW2024-ClickPrompt
论文笔记-WWW2024-ClickPrompt: CTR Models are Strong Prompt Generators for Adapting Language Models to CTR Prediction ClickPrompt: CTR模型是大模型适配CTR预测任务的强大提示生成器摘要1.引言2.预备知识2.1传统CTR预测2.2基于PLM的CTR预测 3.方法3.1概述3.2模态转换3.…...

53 基于单片机的8路抢答器加记分
目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 首先有三个按键 分别为开始 暂停 复位,然后八个选手按键,开机显示四条杠,然后按一号选手按键,数码管显示30,这…...

【java数据结构】二叉树OJ题
【java数据结构】二叉树OJ题 一、检查两颗树是否相同二、另一颗树的子树三、翻转二叉树四、对称二叉树五、判断一颗二叉树是否是平衡二叉树六、给定一个二叉树, 找到该树中两个指定节点的最近公共祖先七、根据一棵树的前序遍历与中序遍历构造二叉树练习:八、二叉树前…...

IIC和SPI的时序图
SCL的变化快慢决定了通信速率,当SCL为低电平的时候,无论SDA是1还是0都不识别: ACK应答:当从设备为低电平的时候识别为从设备有应答: 谁接收,谁应答: 起始位和停止位: IIC的时序图&am…...

MySQL数据库表的操作
1、总述 今天我跟大家分享MySQL数据库中表的创建,查看,修改,删除。 2、创建表 create table table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎; 说明࿱…...
.net core 创建linux服务,并实现服务的自我更新
目录 创建服务创建另一个服务,用于执行更新操作给你的用户配置一些systemctl命令权限 创建服务 /etc/systemd/system下新建服务配置文件:yourapp.service,内容如下: [Unit] Descriptionyourapp Afternetwork.target[Service] Ty…...

springboot338it职业生涯规划系统--论文pf(论文+源码)_kaic
毕 业 设 计(论 文) 题目:it职业生涯规划系统的设计与实现 摘 要 互联网发展至今,无论是其理论还是技术都已经成熟,而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播,搭配信息管理工具可以…...
oracle将select作为字段查询
在Oracle中,如果你想将一个SELECT语句作为字段的值,你可以使用子查询或者使用WITH子句(也称为公用表表达式CTE)。以下是两种方法的示例: 方法1:使用子查询 语法如下: SELECTcolumn1,(SELECT …...
Java数据结构和算法相关面试题
天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…...

网络安全风险评估
项目背景 随着信息化技术的快速发展,特别是面向社会、政府机构、企业等业务系统的投入使用,各组织机构对网络和信息系统安全防护都提出了新的要求。为满足安全需求,需对组织机构的网络和信息系统的安全进行一次系统全面的评估,以…...

ADAM优化算法与学习率调度器:深度学习中的关键工具
深度学习模型的训练效果离不开优化算法和学习率的选择。ADAM(Adaptive Moment Estimation)作为深度学习领域中广泛应用的优化算法之一,以其高效性和鲁棒性成为许多任务的默认选择。而学习率调度器则是优化算法的“助推器”,帮助训…...
岛屿数量C++11新特性
每日一题 200. 岛屿数量 class Solution {//使用深度的优先搜索来搜索岛屿图//遍历整个图片 当char数组的值为1时开始从这个点开始往外扩散搜索//注意处理边界 图不是正方形 public:int ans;int d[4][2] {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};int N;int M;void dfs(vector<…...

Git 快速入门:全面了解与安装步骤
Git 快速入门:全面了解与安装步骤 一、关于Git 1.1 简介 Git 是一个开源的分布式版本控制系统,由 Linus Torvalds 于 2005 年创建,最初是为了更好地管理 Linux 内核开发而设计。 Git用于跟踪计算机文件的变化,特别是源代码文件…...

基于域自适应的双光融合
目录 引言DAF-Net编码器-解码器分支编码器部分融合层解码器部分 域自适应层概述多核最大均值差异(MK-MMD)第一阶段:编码器-解码器分支训练训练过程损失函数 第二阶段:融合层训练训练过程损失函数 实验与结果总结 文章声明…...
迭代器模式 (Iterator Pattern)
文章目录 迭代器模式 (Iterator Pattern)原理优点缺点示例代码场景描述1. 定义迭代器接口2. 定义集合接口3. 实现具体集合类4. 客户端代码输出结果 UML 类图使用场景优化与扩展小结 迭代器模式 (Iterator Pattern) 迭代器模式是一种 行为型设计模式,用于顺序访问集…...

039集——渐变色之:CAD中画彩虹()(CAD—C#二次开发入门)
(来左边儿 跟我一起画个龙,在你右边儿 画一道彩虹 ~~~~~~~~~~~ ) 效果如下: namespace AcTools {public class Class1{public Wform.Timer timer;//定时器需建在类下面public static DateTime startTime;[CommandM…...

如何将 GitHub 私有仓库(private)转换为公共仓库(public)
文章目录 如何将 GitHub 私有仓库转换为公共仓库步骤 1: 登录 GitHub步骤 2: 导航到目标仓库步骤 3: 访问仓库设置步骤 4: 更改仓库可见性步骤 5: 确认更改步骤 6: 验证更改注意事项 如何将 GitHub 私有仓库转换为公共仓库 在软件开发领域,GitHub 是一个广受欢迎的…...

C++11 右值引用
目录 左值 右值 左值引用与右值引用比较 左值引用总结: 右值引用总结: 左值引用的使用场景: 引用传参和做返回值都可以提高效率(减少拷贝) 左值引用的短板: 右值引用和移动语义解决上述问题: 下面就是有移动…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...
Python常用模块:time、os、shutil与flask初探
一、Flask初探 & PyCharm终端配置 目的: 快速搭建小型Web服务器以提供数据。 工具: 第三方Web框架 Flask (需 pip install flask 安装)。 安装 Flask: 建议: 使用 PyCharm 内置的 Terminal (模拟命令行) 进行安装,避免频繁切换。 PyCharm Terminal 配置建议: 打开 Py…...