音视频入门基础: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 右值引用
目录 左值 右值 左值引用与右值引用比较 左值引用总结: 右值引用总结: 左值引用的使用场景: 引用传参和做返回值都可以提高效率(减少拷贝) 左值引用的短板: 右值引用和移动语义解决上述问题: 下面就是有移动…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...