当前位置: 首页 > news >正文

【音视频】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.dtcms.com/a/272495.html

相关文章:

  • 音频 SDP 文件格式
  • 基于多模态感知的裂缝2D及3D检测方案
  • Boost.Asio学习(3):异步读写
  • windows对\和/敏感吗?
  • 小白成长之路-NFS文件存储及论坛项目搭建(php)
  • C++之unordered_set和unordered_map基本介绍
  • jmeter如何让一个线程组中的多个请求同时触发
  • PyTorch中torch.eq()、torch.argmax()函数的详解和代码示例
  • 多线程交替打印ABC
  • Windows安装DevEco Studio
  • 解决问题:在cmd中能查看到pnpm版本,在vscode终端中却报错
  • [5种方法] 如何将iPhone短信保存到电脑
  • 搜索算法在前端的实践
  • G5打卡——Pix2Pix算法
  • Vue前端导出页面为PDF文件
  • 【HDLBits习题 2】Circuit - Sequential Logic(4)More Circuits
  • AI驱动的业务系统智能化转型:从静态配置到动态认知的范式革命
  • 基础 IO
  • Spring Boot中的中介者模式:终结对象交互的“蜘蛛网”困境
  • JAVA JVM的内存区域划分
  • Redis的常用命令及`SETNX`实现分布式锁、幂等操作
  • Redis Stack扩展功能
  • K8S数据流核心底层逻辑剖析
  • AI进化论06:连接主义的复兴——神经网络的“蛰伏”与“萌动”
  • k8s集群--证书延期
  • 项目进度管控依赖Excel,如何提升数字化能力
  • 调度器与闲逛进程详解,(操作系统OS)
  • UI前端与数字孪生结合案例分享:智慧城市的智慧能源管理系统
  • 数据结构笔记10:排序算法
  • Windows 本地 使用mkcert 配置HTTPS 自签名证书