【音视频】TS协议介绍
参考博客:
https://2048.csdn.net/68270d17606a8318e85713f3.html?dp_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6NDE4ODgwNSwiZXhwIjoxNzUyNjM1MjcwLCJpYXQiOjE3NTIwMzA0NzAsInVzZXJuYW1lIjoiQW50b25pbzkxNSJ9.oPiMSZSr-cNiR5VmlmOqC8Mr_YNz3H_8O9koA3c_NkQ&spm=1001.2101.3001.6650.6&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Eactivity-6-123919042-blog-130913479.235%5Ev43%5Epc_blog_bottom_relevance_base5&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Eactivity-6-123919042-blog-130913479.235%5Ev43%5Epc_blog_bottom_relevance_base5&utm_relevant_index=7
一、TS协议概述
TS(Transport Stream,传输流)协议是一种用于音频、视频和数据等多媒体内容传输和存储的容器格式,是 MPEG-2 标准(ISO/IEC 13818-1)的一部分。它最初主要为广播电视领域设计,用于在不可靠的传输环境(如卫星、电缆、IP 网络)中实现稳定的多媒体数据传输,后来在 IPTV、网络流媒体(如 HLS 协议中 TS 作为分片文件格式)等领域也得到了广泛应用。
二、TS协议结构
TS(Transport Stream,传输流),一种常见的视频封装格式,是基于MPEG-2的封装格式(所以也叫MPEG-TS),后缀为.ts
2.1 TS的分层结构
TS文件分为三层,如下所示(可以倒序看更好理解):
- TS层(Transport Stream):在PES层基础上加入了数据流识别信息和传输信息。一个ts视频文件含有多个ts单元包。
- PES层(Packet Elemental Stream):在ES层基础上加入时间戳(PTS/DTS)等信息。每个ts单元含有一个pes头+多个es包。
- ES层(Elementary Stream):压缩编码后的音视频数据。
如下图所示,xB表示字节数不确定,4B表示字节数为4个
2.2 TS层
-
TS传输流,是由固定⻓度的包组成,含有独⽴时间基准的⼀个或多个节⽬,适⽤于误码较多的环境,并且从流的任意⼀段开始都可以独⽴解码。PS(Program Stream):节⽬流,PS流与TS流的区别在于,PS流的包结构是可变⻓度,⽽TS流的包结构是固定⻓度。
-
ts包大小固定为188字节,ts层分为三个部分:ts header、adaptation field、payload。
-
ts header固定4个字节;每188字节单元就有一个ts header。
-
adaptation field可能存在也可能不存在,主要作用是给不足188字节的数据做填充。
-
payload是 PES 数据,或者PAT,PMT等。PAT、PMT是解析ts寻找音视频流很重要的表。
ts Header + adaptation field 格式如下:
2.2.1 TS Header
TS Header格式如下:
字段名称 | 位宽 | 说明 |
---|---|---|
sync_byte | 8bit | 同步字节,固定为0x47 |
transport_error_indicator | 1bit | 传输错误指示符,表明在ts头的adapt域后由一个无用字节,通常都为0,这个字节算在adapt域长度内 |
payload_unit_start_indicator | 1bit | 负载单元起始标示符,一个完整的数据包开始时标记为1 |
transport_priority | 1bit | 传输优先级,0为低优先级,1为高优先级,通常取0 |
pid | 13bit | pid值(Packet ID号码,唯一的号码对应不同的包) |
transport_scrambling_control | 2bit | 传输加扰控制,00表示未加密 |
adaptation_field_control | 2bit | 是否包含自适应区, ‘00’ 保留; ‘01’ 为无自适应域,仅含有效负载; ‘10’ 为仅含自适应域,无有效负载; ‘11’ 为同时带有自适应域和有效负载。 |
continuity_counter | 4bit | 递增计数器,从0 - f,起始值不一定取0,但必须是连续的 |
PID信息非常关键,它直接表征本次TS包的用途。比较重要的是PAT和PMT。
- 常用的PID值:
PID取值 | PID值使用描述 |
---|---|
0x0000 | 节目关联表(program association table, PAT) |
0x0001 | 条件访问表(conditional access table, CAT) |
0x0002 | 传送流描述表(transport stream description table, TSDT) |
0x0003~0x000F | 保留 |
0x0010~0x1FFE | 可以分配为network PID, Program map PID, elementary PID, 或其它 |
0x1FFF | 空包(8191) |
- ts 层的内容是通过 PID 值来标识的,主要内容包括:PAT 表、PMT 表、⾳频流、视频流。
- 解析 ts 流要先找到 PAT 表,只要找到 PAT 就可以找到 PMT,然后就可以找到⾳视频流了。
- PAT 表的和 PMT 表需要定期插⼊ ts 流,因为⽤户随时可能加⼊ ts 流,这个间隔⽐较⼩,通常每隔⼏个视频帧就要加⼊ PAT和 PMT。
- PAT 和 PMT 表是必须的,还可以加⼊其它表如 SDT(业务描述表)等,不过 hls 流只要有PAT 和 PMT 就可以播放了。
不同类型的表有不同的作用:
- PAT 表:主要的作⽤就是指明了 PMT 表的 PID 值。
- PMT 表:主要的作⽤就是指明了⾳视频流的 PID 值。
- ⾳频流/视频流:承载⾳视频内容。
2.1.2 Adaptation field
-
在MPEG-2 TS中,为了传送打包后长度不足188B的不完整TS,或者为了在系统层插入节目时钟参考(program clock reference, PCR),需要在TS包中插入可变字节的调整字段。
-
调整字段其中一个重要作用是解决编解码器的音视频同步问题。一般在视频帧中的TS包的调整字段中,每隔一定传输时间,传送系统时钟27MHz的一个抽样值给接收机,作为解码器解码时的时钟参考信息PCR。
-
PCR通常每隔100ms至少被传输一次。PCR的数值所表示的是解码器在读完这个抽样值的最后那个字节时,解码器本地时钟所应处的状态。通常情况下,PCR不直接改变解码器的本地时钟,而是作为参考基准来调整本地时钟,使之与PCR趋于一致。
字段名称 | 位宽 | 说明 |
---|---|---|
Adaptation_field_length | 8b | 其数值指示的是之后数据长度 |
Discontinuity_indicator | 1b | 1代表当前TS包不连续状态为真 |
Random_access_indicator | 1b | 为1时表明下一个相同PID的包应该含有PTS和原始访问点 |
Elementrary_stream_priority_indicator | 1b | 为1时表明比相同PID包的优先级高 |
PCR_flag | 1b | 为1时代表调整字段中含有PCR(节目时钟参考,用于恢复与编码端一致的时钟顺序),其结构尚未列出(下同) |
OPCR_flag | 1b | 为1时代表调整字段中含有OPCR。 |
Splicing_point_flag | 1b | 为1时代表调整字段中存在拼接点splice_countdown |
Transport_private_data_flag | 1b | 为1时代表调整字段中存在至少一个私有数据 |
Adapatation_filed_extension_flag | 1b | 为1时代表存在调整字段的扩展 |
⾃适应区的⻓度要包含传输错误指示符标识的⼀个字节。pcr 是节⽬时钟参考,pcr、dts、pts 都是对同⼀个系统时钟的采样值,pcr 是递增的,因此可以将其设置为 dts 值,⾳频数据不需要 pcr。如果没有字段,ipad 是可以播放的,但 vlc ⽆法播放。打包 ts 流时 PAT 和 PMT 表是没有 adaptation field 的,不够的⻓度直接补 0xff 即可。视频流和⾳频流都需要加 adaptation field,通常加在⼀个帧的第⼀个 ts包和最后⼀个 ts 包⾥,中间的 ts 包不加。
如下图所示:
2.1.3 payload
前边提到了PAT和PMT,它们都是PSI之一(节目专用信息(Program Special Information, PSI)。MPEG-2 TS传送的TS包携带两类信息:
- 已压缩的音视频(PES)
- 与之相关的符号化表(PSI),由传送包PES的PID来标识。
如果是PSI,那么payload内容为PAT表结构、PMT表结构;如果为音视频,那么payload内容为PES包。
PAT 格式如下表
字段名称 | 位宽 | 说明 |
---|---|---|
table_id | 8b | PAT表固定为0x00 |
section_syntax_indicator | 1b | 固定为1 |
zero | 1b | 固定为0 |
reserved | 2b | 固定为11 |
section_length | 12b | 后面数据的长度 |
transport_stream_id | 16b | 传输流ID,固定为0x0001 |
reserved | 2b | 固定为11 |
version_number | 5b | 版本号,固定为00000,如果PAT有变化则版本号加1 |
current_next_indicator | 1b | 固定为1,表示这个PAT表可以用,如果为0则要等待下一个PAT表 |
section_number | 8b | 固定为0x00 |
last_section_number | 8b | 固定为0x00 |
开始循环 | ||
program_number | 16b | 节目号为0x0000时表示这是NIT,节目号为0x0001时,表示这是PMT |
reserved | 3b | 固定为111 |
PID | 13b | 节目号对应内容的PID值 |
结束循环 | ||
CRC32 | 32b | 前面数据的CRC32校验码 |
PMT 格式如下表
字段名称 | 位宽 | 说明 |
---|---|---|
table_id | 8b | PMT表取值随意,0x02 |
section_syntax_indicator | 1b | 固定为1 |
zero | 1b | 固定为0 |
reserved | 2b | 固定为11 |
section_length | 12b | 后面数据的长度 |
program_number | 16b | 频道号码,表示当前的PMT关联到的频道,取值0x0001 |
reserved | 2b | 固定为11 |
version_number | 5b | 版本号,固定为00000,如果PAT有变化则版本号加1 |
current_next_indicator | 1b | 固定为1 |
section_number | 8b | 固定为0x00 |
last_section_number | 8b | 固定为0x00 |
reserved | 3b | 固定为111 |
PCR_PID | 13b | PCR(节目参考时钟)所在TS分组的PID,指定为视频PID |
reserved | 4b | 固定为1111 |
program_info_length | 12b | 节目描述信息,指定为0x000表示没有 |
开始循环 | ||
stream_type | 8b | 流类型,标志是Video还是Audio还是其他数据,h.264编码对应0x1b,aac编码对应0x0f,mp3编码对应0x03 |
reserved | 3b | 固定为111 |
elementary_PID | 13b | 与stream_type对应的PID |
reserved | 4b | 固定为1111 |
ES_info_length | 12b | 描述信息,指定为0x000表示没有 |
结束循环 | ||
CRC32 | 32b | 前面数据的CRC32校验码 |
- 程序在读取N环的时候会读取该节目所有的码流列表及其PID,解析的时候可以根据PID来分离。
- N环描述符包括的信息如下图所示。节目时钟参考PCR的PID和视频的PID是相等的。
- 由PAT得出所有的节目列表,选定收看的节目后,筛选出等于该节目PID的TS包,就可以得到该节目的所有码流的PID映射表,这样接收机就可以只接收PID等于该节目的码流的TS包即可收看该节目。
2.2 pes 层:Packet Elemental Stream
pes 层是在每⼀个视频/⾳频帧上加⼊了时间戳等信息,pes 包内容很多,这⾥只留下最常⽤的。
pes 层格式如下图:
pes 层内容如下表:
字段名称 | 位宽 | 说明 |
---|---|---|
pes start code | 3B | 开始码,固定为0x000001 |
stream id | 1B | 音频取值(0xc0 - 0xdf),通常为0xc0 视频取值(0xe0 - 0xef),通常为0xe0 |
pes packet length | 2B | 后面pes数据的长度,0表示长度不限制, 只有视频数据长度会超过0xffff |
flag | 1B | 通常取值0x80,表示数据不加密、无优先级、备份的数据 |
flag | 1B | 取值0x80表示只含有pts,取值0xc0表示含有pts和dts |
pes data length | 1B | 后面数据的长度,取值5或10 |
pts | 5B | 33bit值 |
dts | 5B | 33bit值 |
-
pts 是显示时间戳、dts 是解码时间戳,视频数据两种时间戳都需要,⾳频数据的 pts 和 dts 相同,所以只需要 pts。
-
有 pts 和 dts 两种时间戳是 B 帧引起的,I 帧 和 P 帧的 pts 等于 dts。如果⼀个视频没有B 帧,则 pts 永远和 dts 相同。从⽂件中顺序读取视频帧,取出的帧顺序和 dts 顺序相同。dts 算法⽐较简单,初始值 + 增量即可,pts 计算⽐较复杂,需要在 dts 的基础上加偏移量。
-
⾳频的 pes 中只有 pts(同 dts),视频的 I、P 帧两种时间戳都要有,视频 B 帧只要 pts(同 dts)。
-
打包 pts 和 dts 就需要知道视频帧类型,但是通过容器格式我们是⽆法判断帧类型的,必须解析 h.264内容才可以获取帧类型。
举例说明:
. I P B B B P
读取顺序: 1 2 3 4 5 6
dts 顺序: 1 2 4 5 6 3
pts 顺序: 1 2 3 4 5 6
点播视频 DTS 算法:
dts = 初始值 + 90000 / video_frame_rate
- 初始值可以随便指定,但是最好不要取 0。
- video_frame_rate 就是帧率,比如 23、30。
- pts 和 dts 是以 timestamp 为单位的,1s = 90000 time scale,一帧就应该是 90000/video_frame_rate 个 timescale。
- 用一帧的 timescale 除以采样频率就可以转换为一帧的播放时长。
点播音频 DTS 算法:
dts = 初始值 + (90000 * audio_samples_per_frame) / audio_sample_rate
- audio_samples_per_frame 这个值与编解码相关,aac 取值 1024,mp3 取值 1158。
- audio_sample_rate 是采样率,比如 24000、41000。
- AAC 一般解码出来是每声道 1024 个 sample,也就是说一帧的时长为 1024/sample_rate 秒。所以每帧时间戳依次为 0, 1024/sample_rate, …, 1024*n/sample_rate 秒。
注: 直播视频的 dts 和 pts 应该直接用直播数据流中的时间,不应该按公式计算。
2.3 es 层:Elementary Stream
es 层指的就是⾳视频数据。这⾥只介绍 h.264 视频和 aac ⾳频。
2.3.1 h.264 视频
-
打包 h.264 数据时必须给视频数据加上⼀个 nalu(Network Abstraction Layer Unit),nalu 包括nalu header 和 nalu type,nalu header 固定为 0x00000001(帧开始)或 0x000001(帧中)。
-
h.264 的数据是由 slice 组成的,slice 的内容包括:视频、sps、pps 等。nalu type 决定了后⾯的h.264 数据内容。
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|F|NRI| TYPE |
+-+-+-+-+-+-+-+-+
- F:1bit,forbidden_zero_bit,h.264 规定必须取 0。
- NRI:2bits,nal_ref_idc,取值为 0~3,指示这个 nalu 的重要性,I 帧、sps、pps 通常取 3,P 帧常取 2,B 帧通常取 0
- Type:5bits,取值如下表所示:
nal_unit_type | 说明 |
---|---|
0 | 未使用 |
1 | 非IDR图像片,IDR指关键帧 |
2 | 片分区A |
3 | 片分区B |
4 | 片分区C |
5 | IDR图像片,即关键帧 |
6 | 补充增强信息单元(SEI) |
7 | SPS序列参数集 |
8 | PPS图像参数集 |
9 | 分解符 |
10 | 序列结束 |
11 | 码流结束 |
12 | 填充 |
13~23 | 保留 |
24~31 | 未使用 |
打包 es 层数据时 pes 头和 es 数据之间要加⼊⼀个 type=9 的 nalu,关键帧 slice 前必须要加⼊type=7 和 type=8 的 nalu,⽽且是紧邻的。如下图所示:
2.3.2 aac⾳频
打包aac⾳频必须加上⼀个adts(Audio Data Transport Stream)头,共7Byte,adts包括fixed_header和variable_header两部分,各28bit。
fixed_header
字段名称 | 位宽 | 说明 |
---|---|---|
syncword | 12b | 固定为0xfff |
id | 1b | 0表示MPEG-4,1表示MPEG-2 |
layer | 2b | 固定为00 |
protection_absent | 1b | 固定为1 |
profile | 2b | 取值0~3,1表示aac |
sampling_frequency_index | 4b | 表示采样率,0: 96000 Hz,1: 88200 Hz,2: 64000 Hz,3: 48000 Hz,4: 44100 Hz,5: 32000 Hz,6: 24000 Hz,7: 22050 Hz,8: 16000 Hz,9: 12000 Hz,10: 11025 Hz,11: 8000 Hz,12: 7350 Hz |
private_bit | 1b | 固定为0 |
channel_configuration | 3b | 取值0~7,1: 1 channel: front-center,2: 2 channels: front-left, front-right,3: 3 channels: front-center, front-left, front-right,4: 4 channels: front-center, front-left, front-right, back-center |
original_copy | 1b | 固定为0 |
home | 1b | 固定为0 |
variable_header
字段名称 | 位宽 | 说明 |
---|---|---|
syncword | 12b | 固定为0xfff |
id | 1b | 0表示MPEG-4,1表示MPEG-2 |
layer | 2b | 固定为00 |
protection_absent | 1b | 固定为1 |
profile | 2b | 取值0~3,1表示aac |
sampling_frequency_index | 4b | 表示采样率,0: 96000 Hz,1: 88200 Hz,2: 64000 Hz,3: 48000 Hz,4: 44100 Hz,5: 32000 Hz,6: 24000 Hz,7: 22050 Hz,8: 16000 Hz,9: 12000 Hz,10: 11025 Hz,11: 8000 Hz,12: 7350 Hz |
private_bit | 1b | 固定为0 |
channel_configuration | 3b | 取值0~7,1: 1 channel: front-center,2: 2 channels: front-left, front-right,3: 3 channels: front-center, front-left, front-right,4: 4 channels: front-center, front-left, front-right, back-center |
original_copy | 1b | 固定为0 |
home | 1b | 固定为0 |
更多资料:https://github.com/0voice