【音视频】TS协议解析

参考博客:https://blog.csdn.net/rell336/article/details/38109621?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

一、TS协议

1.1 TS流和其他流的关系

1. ES(Elementary Stream,基本码流)

  • 定义:未经分段的原始媒体流,是连续的音频、视频或其他信息(如字幕)的原始数据序列。
  • 特点
    • 不分段,以连续码流形式存在。
    • 是最底层的媒体数据,直接由编码器生成(如H.264视频流、AAC音频流)。
    • 不包含传输控制信息,无法直接用于传输。

2. PES(Packetized Elementary Stream,分组的基本码流)

  • 定义:将ES流分割为长度可变的数据包,并添加包头后形成的流,用于承载ES流。
  • 作用:为ES流添加传输所需的控制信息,实现ES流的分组化传输。
  • 结构
    • PES包头:包含时间戳(PTS/DTS)、数据长度、流类型等信息。
    • PES有效载荷:分割后的ES流片段。
  • 特点
    • 数据包长度可变,适合在可靠环境(如本地存储)中使用。

3. TS(Transport Stream,传输流)

  • 定义:由固定长度(188字节)的数据包组成,用于在不可靠环境(如广播电视、网络传输)中传输多个节目。
  • 构成
    • 包含一个或多个节目,每个节目由音频、视频等ES流通过PES分组后封装而成。
    • 插入PSI/SI表(节目特定信息/服务信息),用于描述节目结构和服务信息。
  • 关键特性
    • 固定包长:每个TS包为188字节(含4字节包头),便于硬件快速处理。
    • 独立解码:从流中任意位置均可开始解码,容错性强。
    • PSI/SI重复传输:PSI/SI表按一定频率重复发送,确保接收端能随时获取节目信息。

4. PS(Program Stream,节目流)

  • 定义:用于传输单个节目,由可变长度数据包组成的流,适用于可靠存储或传输环境(如DVD光盘)。
  • 与TS流的核心区别
    • 包长度:PS包长度可变,TS包长度固定(188字节)。
    • 应用场景:PS流适合本地存储(如文件),TS流适合实时传输(如广播电视)。

四者关系

  1. ES流是原始媒体数据(如视频帧、音频采样)。
  2. PES流是ES流的分组形式,添加时间戳等控制信息。
  3. TS流/PS流是PES流的进一步封装:
    • TS流:固定包长,含多节目和PSI/SI,适合传输。
    • PS流:可变包长,单节目,适合存储。

简单来说,ES→PES→TS/PS的过程,是媒体数据从原始形式到可传输/存储形式的封装链。

1.2 TS包

TS包的⻓度:188 B或204 B,204 B⻓度是在188B后⾯增加了16 B的CRC校验数据。
在这里插入图片描述

  • sync_byte: 1B,固定值0x47,TS包的标识符,正常的TS包在0x47的包头标识符往后188/204B之后仍然是0x47【下⼀个TS包的标识符】

  • transport_error_Indicator: 1bit,当其为1时,表示该TS包中⾄少有⼀个不可纠正的错误位,只有在错误纠正之后,该位才能重新置0【实际获取TS包之后,该位为1的包丢弃】

  • payload_unit_start_indicator: 1bit,对于PSI数据包,该位为1时,表示该TS包是某个Section的第⼀个包,并且该包含有pointer_field,该变量的值意义在于,除了调整字段之外,往后pointer_field个字节开始,才是有效数据。对于空包来说,该值为0。

  • transport_priority: 1bit,表示传输优先级,对于相同PID的TS包,该字段置1的TS包拥有更⾼的优先级。PID:13bit,PID可以标识存储于TS包中有效净荷的数据的类型。PID⽤于TS包阶段⽤于鉴别各种PSI/SI信息表、电视节⽬,区分⾳视频的PES包等,是辨别码流信息性质的关键。

  • transport_scrambling_control:2bit,⽤来指示传送流包Payload的加扰⽅式。【传送流包⾸部包括调整字段,则不应被加扰;空包也不加扰。】

Transport_scrambling_control描述
00未加扰
01用户定义
10用户定义
11用户定义
  • adaption_field_control:2bit,表示传送流包⾸部是否跟随调整字段/Payload【如果全部是调整字段则不含payload】

  • continuity_counter: 4bit,随着具有相同PID的TS包增加⽽增加,当它达到最⼤(31)时,⼜恢复为0,如果adaption_field_control = 00/10,该连续计数器不增加,因为不含payload。

1.3解析TS包

1.3.1获取包⻓

TS包的包⻓有两种——188B或者204B,在解析TS包之前,必须要先判断TS包包⻓,以便后续进⾏分析。

1. 标准固定长度
  • 常规 TS 包:最常见的长度是 188 字节(包括 4 字节包头 + 184 字节负载)。这是 DVB(欧洲数字电视标准)、ATSC(美国数字电视标准)等系统的默认长度。
  • 扩展长度:在某些场景(如 DVB-T2、ISDB-Tb)中,TS 包可能被封装到更大的帧结构中,此时 TS 包长度可能为 204 字节(188 字节 + 16 字节前向纠错码)。但这种情况下,核心 TS 包本身仍为 188 字节,只是外部添加了额外的保护机制。
2. 通过同步字节识别包边界

TS 包的起始位置由 同步字节(固定值 0x47)标识。接收端通过检测连续的 0x47 来定位每个 TS 包的开始,并根据固定长度(如 188 字节)截取完整的包。例如:

0x47 [TS包头4字节] [负载184字节] 0x47 [下一个TS包...]

如果遇到非 0x47 的字节,则表示传输错误或包丢失。

具体流程如下图(以188B为例):

在这里插入图片描述

1.3.2 解析TS包头

在获取包⻓之后,就要对包头信息进⾏解析并获取有效数据,需要定义⼀个结构体存储数据:

/*TS包包头的结构体*/
typedef struct CSTSPacketHeader_S
{BYTE ucSyncByte; //TS包的标识符BYTE ucTransport_error_indicator; //传输错误指示器,当值为时,表示该包有误BYTE ucPayload_unit_start_indicator; //有效净荷开始标记// 注:如果想要节省存储空间,可以使用位域的方式定义结构体。BYTE——unsigned char,Word——unsigned short intBYTE ucTransport_priority; //传输的优先级WORD wPID; //TS包的ID,用于区分不同的sectionBYTE ucTransport_scrambling_control; //指示ts传送流包有效净荷的加扰方式BYTE ucAdaptation_field_control; //指示是否有调整字段和有效净荷BYTE ucContinuity_counter; //随着相同PID TS包的增加而增加
} TSPacketHeader;

假定包⻓为188,我们每获取⼀个TS包,就装进⼀个⻓度为188的BYTE型数组⾥,前4个BYTE就是包头的数据了,获取数据可以逻辑与,左右移,逻辑或的⽅法进⾏,具体例⼦如下:

pstTSHeader->wPID = ((pucTSBuffer[1]& 0x1f) << 8) | pucTSBuffer[2];

1.3.3 判断TS包的有效性

在⼀个码流中,并不全部都是有效的TS包,需要将⼀些⽆效TS包剔除

⽆效TS包的情况分为五种:

  1. 该TS包往后188B不是0x47的包头标识符(TS包都是连续发送,如果出现包不连续的地⽅,说
    明该包数据传送时出错);
  2. TS包存在错误,即transport_error_Indicator的值为1;
  3. TS包全是调整字段(空包),即adaption_field_control的值为10(⼆进制);
  4. TS包的调整字段属于保留的情况,即adaption_field_control的值为00(⼆进制);
  5. TS包被加扰,即transport_scrambling_control不为00,(如果有做解扰可以去掉这种情况);

对于这五种情况的TS包我们⼀律丢弃,直接获取下⼀个TS包。

1.3.4 确定payload的起始位置

1. 定位 TS 包起始

每个 TS 包以 同步字节(固定值 0x47)开始。找到同步字节后,后续的 187 字节属于当前 TS 包。

2. 解析 TS 包头(4 字节)

TS 包头的结构如下:

字节0: 同步字节 (0x47)
字节1: TEI(1) | PUSI(1) | TP(1) | PID(13)
字节2: PID(继续)
字节3: TSC(2) | AFC(2) | CC(4)

其中关键字段:

  • AFC (Adaptation Field Control):位于字节 3 的第 4-5 位(2 比特),指示自适应区和有效载荷的存在情况:
    • 01无自适应区,仅有效载荷(Payload 从第 5 字节开始)。
    • 10仅有自适应区,无有效载荷(Payload 为空)。
    • 11自适应区后接有效载荷(需进一步计算 Payload 起始)。
    • 00:保留值,不应使用。
3. 根据 AFC 计算 Payload 起始
情况 1:AFC = 01(仅有效载荷)
  • Payload 起始位置:第 5 字节(即包头后的第一个字节)。
情况 2:AFC = 10(仅自适应区)
  • 无有效载荷,TS 包仅包含包头和自适应区。
情况 3:AFC = 11(自适应区 + 有效载荷)
  1. 读取自适应区长度
    • 包头后的第 1 个字节(即第 5 字节)是 自适应区长度adaptation_field_length),表示自适应区的总字节数(不包含自身)。
  2. 计算 Payload 起始
    • Payload 起始位置 = 包头(4 字节) + 自适应区长度字节(1 字节) + 自适应区内容(adaptation_field_length 字节)。
    • 公式:Payload起始 = 5 + adaptation_field_length
4. 示例计算

假设 TS 包数据如下(十六进制):

47 40 00 11 05 FF 00 01 02 03 61 62 63 ...

解析步骤:

  1. 同步字节47 → 确认 TS 包起始。
  2. AFC 字段:字节 3 为 11 → 二进制 0001 0001 → AFC = 11(自适应区 + 有效载荷)。
  3. 自适应区长度:第 5 字节为 05 → 自适应区内容长度为 5 字节。
  4. Payload 起始:5(包头 + 长度字节) + 5(自适应区内容) = 第 10 字节61)。

二、 Section

2.1 Section的概念

  • ⼀个TS数据包的最⼤净荷为184个字节,当⼀个PSI/SI表的字节⻓度⼤于184字节时,就要对这个表进⾏分割,形成段(section)来传送。

  • 分段机制主要是将⼀个数据表分割成多个数据段。 在PSI/SI表到TS包的转换过程中,段起到了中介的作⽤。由于⼀个数据包只有188字节,⽽段的⻓度是可变的,EIT表的段限⻓4096字节,其余PSI/SI表的段限⻓为1024字节。因此,⼀个段要分成⼏部分插⼊到TS包的payload中。

  • 从TS码流中可以获取到TS包,TS包要组成Section,才能提取到想要的信息,所以⾸先要懂得怎么组section。

组Section之前要了解TS包在码流中发送的⼀些情况:

  1. TS包发送的时候PID是⽆序的,连续的TS包的PID可能都是不⼀样的;
  2. TS包发送的时候Section是相对有序的,也就是说,对于同⼀个PID的TS包,只有发完了⼀个Section,才会发送下⼀个Section,不然⽆法区分该TS包属于哪⼀个Section,并且对于这个Section,TS包是有序发送的,否则数据会被打乱;
  3. 某个Section的第⼀个TS包有PSI/SI表的⼀些表头信息(table_id,section_length等信息),我称之为SectionHeader,后⾯的TS包就没有,所以接收某个Section必须先拿到⾸包。

2.2 TS包组Section

  • TS包组section⾸先要找到该section的第⼀个TS包(下⾯简称为⾸包),⾸包含有该section的⻓度,可以⽤来判断⼀个section是不是组完了。

  • 通过判断TS包包头中的Payload Unit Start Indicator,该值为1的话,就说明这个TS包是⾸包,可以开始组⼀个section,⾸包含有Section的头部,结构类似下表。

说明:uimsbf 表示无符号整数,最高位在前;bslbf 表示二进制符号位在前(布尔类或标志位常用) ,可用于解析 MPEG - 2 传输流中节目关联段(PAT)的结构。

字段名位宽编码类型
table_id8uimsbf
section_syntax_indicator1bslbf
‘0’1bslbf
reserved2bslbf
section_length12uimsbf
transport_stream_id16uimsbf
reserved2bslbf
version_number5uimsbf
current_next_indicator1bslbf
section_number8uimsbf
last_section_number8uimsbf

拿到⾸包之后,要获取section的⻓度,有效数据的第⼆个字节的后四位和第三个字节组成⼀个12bit的字段,该值就是section_length后⾯数据的⻓度,如果算上前⾯三个字节,整个section的⻓度就是section_length += 3

将section_length和TS包有效⻓度进⾏对⽐,

  1. 如果section_length > TS包的有效数据,证明后⾯还有其他的TS包,将section_length减去TS包有效数据⻓度,获得剩余⻓度;
  2. 如果是section_length <= TS包的有效数据,证明该section已经结束了。

如果⼀个section还没组完,那么就要获取后续的TS包,后续的TS包应该是和原来相同PID,并且TS包头中continuity_counter要⽐原来的⼤1(31的话要变成0),拿到包后要与剩余⻓度进⾏对⽐,重复上⾯的步骤。

2.3 组多个Section和判全判重

对于⼀些PSI/SI表来说,由于数据较多,有时候不⽌⼀个section,怎么针对这个表将所有的section组全?

子表 Section 组包、判全与判重机制说明

在处理 PSI/SI 表(以 PAT 等为例)的分段 Section 时,需通过以下逻辑实现组包、判全与判重:

1. 子表 Section 数量与组包逻辑

每个子表的 Section 头部包含 last_section_number 字段,规则如下:

  • 含义:标识当前子表最后一个 Section 的编号(section_number)。
  • 数量计算:子表总 Section 数 = last_section_number + 1(因 section_number 从 0 开始计数 )。
  • 组包方式:以链表(或数组)存储同一子表的 Section,按 section_number 顺序拼接,还原完整子表数据。
2. 版本控制(判重核心逻辑)

每个 Section 头部包含 version_number 字段,用于标识子表版本:

  • 版本变更:若新 Section 的 version_number 与已存储子表版本不一致,说明子表已更新,需:
    • 丢弃所有旧版本 Section。
    • 重新初始化接收流程,采集新版本 Section。
  • 版本未变:继续校验 section_number,判断是否重复或缺失。
3. 判全与判重逻辑

通过 标记数组 跟踪 Section 接收状态,流程如下:

步骤操作逻辑关键判断
1初始化标记数组last_section_number + 1 为长度,初始值全为 0(未接收)
2接收新 Section解析其 section_number
3判重校验若标记数组中 section_number 对应下标值为 1,说明该 Section 已接收过,丢弃
4标记接收状态若未重复,将标记数组中 section_number 对应下标值置为 1
5判全校验遍历标记数组,若下标 0 ~ last_section_number 对应值全为 1,说明子表所有 Section 已收全,可组装完整子表

更多资料:https://github.com/0voice

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/88557.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/88557.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp 日期组件可选择年月

month-picker 月份选择器组件 组件介绍 month-picker 是一个用于选择年月的自定义组件&#xff0c;基于 uni-app 开发&#xff0c;提供了简洁的月份选择功能。 解决弹框底部出现底部页面区域 safe-area属性设为true时&#xff0c;即可解决这个问题效果如图功能特点 支持选择年份…

从真人到数字分身:3D人脸扫描设备在高校数字人建模教学中的应用

在影视、动漫、游戏等数字创意产业蓬勃发展的当下&#xff0c;超写实虚拟数字人凭借其高度逼真的形象&#xff0c;成为行业关注的焦点。无论是影视特效中栩栩如生的角色&#xff0c;还是游戏里精致的NPC&#xff0c;超写实虚拟数字人的制作都离不开先进的技术支撑。而3D人脸扫描…

你以为大数据只是存?其实真正的“宝藏”藏在这招里——数据挖掘!

你以为大数据只是存&#xff1f;其实真正的“宝藏”藏在这招里——数据挖掘&#xff01; 曾经我也天真地以为&#xff0c;搞大数据就是会写几个SQL、部署个Hadoop集群&#xff0c;结果真到项目现场&#xff0c;甲方爸爸一句&#xff1a;“给我挖掘一下用户的购买意图”&#xf…

LeetCode经典题解:128、最长连续序列

“最长连续序列”是一道极具代表性的数组处理问题&#xff0c; 本文将带你从直观思路出发&#xff0c;逐步推导出最优解法&#xff0c;并通过场景化记忆技巧掌握核心逻辑。 一、题目描述 题目&#xff1a;给定一个未排序的整数数组 nums&#xff0c;找出数字连续的最长序列&…

电力分析仪的“双语对话”:CCLinkIE与Modbus TCP的无缝连接

在工业自动化领域&#xff0c;协议兼容性问题如同“方言壁垒”&#xff0c;让不同品牌、不同系统的设备难以高效协同。对于电力分析仪这类关键设备而言&#xff0c;如何打破CCLinkIE与Modbus TCP协议的“语言障碍”&#xff0c;已成为工程师优化系统集成的核心课题。 为何需要协…

暑假复习篇之文本编译器

一、知识点补充【在此次示例代码上显示的关键用法】知识点1、JMenuBar&#xff1a;菜单栏的容器&#xff0c;通常添加到JFrame的顶部。关键用法&#xff1a;add&#xff1a; 添加菜单到菜单栏2、JMenu&#xff1a;菜单条目&#xff08;“文件” “编辑” 等&#xff09;&#x…

Linux自动化构建工具(一)

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录Li…

目标检测流程图绘制

目标检测流程图绘制作为一个长期科研的苦命人&#xff0c;我一般采用Processon。 一、目标检测流程图绘制的 “量身定制” 体验 Processon 的绘图元素库对目标检测领域极度友好&#xff0c;从基础模块到复杂结构都能精准匹配&#xff1a;   核心组件一键调用&#xff1a;在右…

GitHub 趋势日报 (2025年07月09日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图970genai-toolbox780WebAgent650rustfs451prompt-eng-interactive-tutorial246ai-a…

多云环境下的成本管理挑战与对策 ——资源碎片化治理与华为CloudMatrix破局之道

一、危机&#xff1a;多云成本失控已成企业“隐形杀手”成本超支概率激增据Gartner 2024报告&#xff0c;采用多云策略的企业成本超支概率比单云企业高47%&#xff0c;主因资源碎片化导致的闲置浪费和管控失效。触目惊心的数据&#xff1a;73%企业云成本占营收超20%&#xff0c…

Linux的基础I/O

目录 1、理解“文件” 1.1 狭义理解 1.2 广义理解 1.3 文件操作的归类认知 1.4 系统角度 2、回顾C文件接口 2.1 文件的打开与关闭 2.2 文件的读写函数 2.3 stdin & stdout & stderr 3、系统文件I/O 3.1 一种传标志位的方式 3.2 文件的系统调用接口 3.2.1 o…

广告匹配策略的智能化之路:人工智能大模型的方法和步骤

摘要 广告匹配策略是指根据用户的需求和偏好&#xff0c;向用户推荐最合适的广告的方法。广告匹配策略的优化是数字化营销的核心问题之一&#xff0c;也是提升广告效果和收益的关键因素。本文介绍了如何利用人工智能大模型&#xff0c;从数据分析、广告推荐、策略优化、效果评…

飞算JavaAI:重塑Java开发的“人机协同“新模式

引言 在Java开发领域&#xff0c;“效率"与"质量"的平衡始终是开发者面临的核心挑战——重复编码消耗精力、复杂业务易出漏洞、老系统重构举步维艰。飞算JavaAI的出现&#xff0c;并非简单地用AI替代人工&#xff0c;而是构建了一套"AI处理机械劳动&#…

运行ssh -T git@github.com报错

运行ssh -T gitgithub.com报错 no such identity: /root/.ssh/id_rsa: No such file or directory gitssh.github.com: Permission denied (publickey). 如果我用的是ed25519而非rsa&#xff0c;有id_ed25519 则需要打开~/.ssh/config检查一下是否写错了 vim ~/.ssh/config 然后…

20250710-2-Kubernetes 集群部署、配置和验证-网络组件存在的意义?_笔记

一、网络组件的作用&#xfeff;1. 部署网络组件的目的&#xfeff;核心功能&#xff1a;执行kubectl apply -f calico.yaml命令的主要目的是为Kubernetes集群部署网络组件必要性&#xff1a;解决Pod间的跨节点通信问题建立集群范围的网络平面&#xff0c;使所有Pod处于同一网络…

【牛客刷题】dd爱科学1.0

文章目录 一、题目介绍1.1 题目描述1.2 输入描述:1.3 输出描述:1.4 示例1二、解题思路2.1 核心策略2.2 算法流程2.3 正确性证明三、算法实现四、关键步骤解析五、复杂度分析六、正确性验证七、算法对比7.1 暴力搜索法7.2 动态规划7.3 三种解法对比分析一、题目介绍 1.1 题目描…

跑步-Java刷题 蓝桥云课

目录 题目链接 题目 解题思路 代码 题目链接 竞赛中心 - 蓝桥云课 题目 解题思路 用数组记录每个月有多少天,再使用一个int型变量记录是星期几,遍历即可 代码 import java.util.Scanner; // 1:无需package // 2: 类名必须Main, 不可修改public class Main {public stat…

Qt常用控件之QWidget(二)

Qt常用控件&#xff08;二&#xff09;1.window frame2.windowTitle3.windowIcon&#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f680;系列专栏&#xff1a;【Qt的学习】 &#x1f4dd;&#x1f4dd;本篇…

飞算Java AI:专为 Java 开发者打造的智能开发引擎

目录 一&#xff0c;核心功能 1&#xff0c;智能编码&#xff08;AI Coding&#xff09; 2&#xff0c;AI 驱动测试&#xff08;AI Testing&#xff09; 3&#xff0c;智能运维&#xff08;AIOps&#xff09; 4&#xff0c;工程化支持 二、注册与上手&#xff1a;3 分钟快…

基于开源AI大模型AI智能名片S2B2C商城小程序源码的私域流量新生态构建

摘要&#xff1a;私域流量并非新生概念&#xff0c;企业持续构建和经营“企业 - 客户”关系是其持续存在的关键&#xff0c;且会随时代发展自我完善迭代。本文探讨了开源AI大模型AI智能名片S2B2C商城小程序源码在私域流量领域的应用价值。通过分析私域流量发展现状与挑战&#…