一、概述
MPEG-2是MPEG(Moving Picture Experts Group,运动图像专家组)组织制定的视频和音频有损压缩标准之一,它的正式名称为“基于数字存储媒体运动图像和语音的压缩标准”。MPEG-2标准是在1994年11月为数字电视而提出来的,目前分为9个部分,统称为ISO/IEC13818国际标准。其中第一部分ISO/IEC13818-1是系统(System)部分,该部分解决了将一个或多个基本视频流、音频流以及其他数据流合并成适合存储或传输的单个或多个流的问题。如下图为ISO/IEC13818-1标准中的原图,该系统(System)部分主要描述的是图中竖线右侧的相关内容,而图中左侧视频数据、音频数据的编码压缩分别遵循ISO/IEC 13818-2和ISO/IEC 13818-3中的规范,它们属于该标准的压缩层。
在上图中左侧,经过量化、采样后的视频数据(如YUV或RGB)、音频数据(如PCM)经过编码压缩后输出音、视频基本流ES。ES是连续码流,为了方便存储与传输,需要将基本的码流ES根据需要分割成长度不等的数据包,并加上包头就形成了打包的基本数据流PES。然后为了适应不同的网络环境和应用场景,会将一路或多路节目的PES流复用、封装打包为单一的节目流PS或传输流TS。
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
PS流与TS流的主要特性区别在于:
长度:TS流的包结构是长度是固定的,而PS(Program Stream,节目流)流的包结构是可变长度的,且通常比较长;
抗干扰:TS包由于长度固定,且携带同步信息,在信道环境较为恶劣、传输误码较高时,如果误码破坏了某一TS包的同步信息时,接收机可在固定的位置检测它后面包中的同步信息,从而恢复同步,避免了信息丢失。而PS包由于长度是变化的,一旦某一PS包的同步信息丢失,接收机无法确定下一包的同步位置, 就会造成失步,导致严重的信息丢失;
用途:由于TS流的抗干扰性,使其可用于传输误码较高的网络传输,也可以用于存储,而PS流主要用于DVD;
ES(Elemental Stream,基本流)是编码视频数据流或音频数据流,每个ES都由若干个AU(Access Unit,访问单元)组成,每个视频AU或音频AU都是由头部和编码数据两部分组成,1个AU相当于编码的1幅视频图像或1个音频帧。也可以说,每个AU实际上是编码数据流的显示单元,即相当于解码的1幅视频图像或1个音频帧的取样。
二、PES结构与字段分析
PES(Packet Elemental Stream,打包的基本数据流)只是担任ES分割、打包、封装为PS/TS中间桥梁的角色,是MPEG-2数据流互换的逻辑结构。在PES层,比较关键的是在PES包头信息中加入PTS(显示时间标签)和DTS(解码时间标签)用于视频、音频同步。
Figure F-2 -- PES packet syntax diagram
如上图是标准文档中PES包结构原图,其中一些字段分行进行说明如下:
图Figure F-2第一行字段信息
Stream_id取值表
图Figure F-2第二行字段信息
图Figure F-2第三行字段信息
图Figure F-2最后一行字段信息
以下是原文档中PES包结构的代码逻辑,读者可以参考上边的字段说明信息和PES包结构图来参考阅读、理解:
/** 数据类型:
* bslbf:"Bit string, left bit first",位串,左位优先
* uimsbf:"Unsigned integer",无符号整型
*/
PES_packet() {
packet_start_code_prefix 24 uimsbf
stream_id 8 uimsbf
PES_packet_length 16 uimsbf
if( stream_id != program_stream_map
&& stream_id != padding_stream
&& stream_id != private_stream_2
&& stream_id != ECM
&& stream_id != EMM
&& stream_id != program_stream_directory
&& stream_id != DSMCC_stream
&& stream_id != ITU-T Rec. H.222.1 type E_stream) {
'10' 2 bslbf
PES_scrambling_control 2 bslbf
PES_priority 1 bslbf
data_alignment_indicator 1 bslbf
copyright 1 bslbf
original_or_copy 1 bslbf
PTS_DTS_flags 2 bslbf
ESCR_flag 1 bslbf
ES_rate_flag 1 bslbf
DSM_trick_mode_flag 1 bslbf
additional_copy_info_flag 1 bslbf
PES_CRC_flag 1 bslbf
PES_extension_flag 1 bslbf
PES_header_data_length 8 uimsbf
if (PTS_DTS_flags =='10' ) {
'0010' 4 bslbf
PTS [32..30] 3 bslbf
marker_bit 1 bslbf
PTS [29..15] 15 bslbf
marker_bit 1 bslbf
PTS [14..0] 15 bslbf
marker_bit 1 bslbf
}
if (PTS_DTS_flags ==‘11’ ) {
'0011' 4 bslbf
PTS [32..30] 3 bslbf
marker_bit 1 bslbf
PTS [29..15] 15 bslbf
marker_bit 1 bslbf
PTS [14..0] 15 bslbf
marker_bit 1 bslbf
'0001' 4 bslbf
DTS [32..30] 3 bslbf
marker_bit 1 bslbf
DTS [29..15] 15 bslbf
marker_bit 1 bslbf
DTS [14..0] 15 bslbf
marker_bit 1 bslbf
}
if (ESCR_flag=='1') {
reserved 2 bslbf
ESCR_base[32..30] 3 bslbf
marker_bit 1 bslbf
ESCR_base[29..15] 15 bslbf
marker_bit 1 bslbf
ESCR_base[14..0] 15 bslbf
marker_bit 1 bslbf
ESCR_extension 9 uimsbf
marker_bit 1 bslbf
}
if (ES_rate_flag == '1') {
marker_bit 1 bslbf
ES_rate 22 uimsbf
marker_bit 1 bslbf
}
if (DSM_trick_mode_flag == '1') {
trick_mode_control 3 uimsbf
if ( trick_mode_control == fast_forward ) {
field_id 2 bslbf
intra_slice_refresh 1 bslbf
frequency_truncation 2 bslbf
} else if ( trick_mode_control == slow_motion ) {
rep_cntrl 5 uimsbf
} else if ( trick_mode_control == freeze_frame ) {
field_id 2 uimsbf
reserved 3 bslbf
} else if ( trick_mode_control == fast_reverse ) {
field_id 2 bslbf
intra_slice_refresh 1 bslbf
frequency_truncation 2 bslbf
} else if ( trick_mode_control == slow_reverse ) {
rep_cntrl 5 uimsbf
} else {
reserved 5 bslbf
}
}
if ( additional_copy_info_flag == '1' ) {
marker_bit 1 bslbf
additional_copy_info 7 bslbf
}
if ( PES_CRC_flag == '1' ) {
previous_PES_packet_CRC 16 bslbf
}
if ( PES_extension_flag == '1' ) {
PES_private_data_flag 1 bslbf
pack_header_field_flag 1 bslbf
program_packet_sequence_counter_flag 1 bslbf
P-STD_buffer_flag 1 bslbf
reserved 3 bslbf
PES_extension_flag_2 1 bslbf
if ( PES_private_data_flag == '1' ) {
PES_private_data 128 bslbf
}
if (pack_header_field_flag == '1' ) {
pack_field_length 8 uimsbf
pack_header()
}
if(program_packet_sequence_counter_flag== '1'){
marker_bit 1 bslbf
program_packet_sequence_counter 7 uimsbf
marker_bit 1 bslbf
MPEG1_MPEG2_identifier 1 bslbf
original_stuff_length 6 uimsbf
}
if ( P-STD_buffer_flag == '1' ) {
'01' 2 bslbf
P-STD_buffer_scale 1 bslbf
P-STD_buffer_size 13 uimsbf
}
if ( PES_extension_flag_2 == '1'){
marker_bit 1 bslbf
PES_extension_field_length 7 uimsbf
for(i=0;i<PES_extension_field_length;i++) {
reserved 8 bslbf
}
}
}
for (i=0;i<N1;i++) {
stuffing_byte 8 bslbf
}
for (i=0;i<N2;i++) {
PES_packet_data_byte 8 bslbf
}
} else if ( stream_id == program_stream_map
|| stream_id == private_stream_2
|| stream_id == ECM
|| stream_id == EMM
|| stream_id == program_stream_directory
|| stream_id == DSMCC_stream)
|| stream_id == ITU-T Rec. H.222.1 type E stream {
for ( i=0;i<PES_packet_length;i++) {
PES_packet_data_byte 8 bslbf
}
} else if ( stream_id == padding_stream) {
for ( i=0;i<PES_packet_length;i++) {
padding_byte 8 bslbf
}
}
}
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
三、TS结构与字段分析 
如上图所示是标准文档中的TS(Transport Stream,传输流)包结构原图,从图中第一行可以看到传输流TS中的每个TS包固定大小188字节。一个TS包主要由头部(header)和有效荷载(payload)两部分组成,其中payload承载的数据主体是PES,而头部除了包含前4个固有字节外还可能包含自适应字段(adaptation field)。自适应字段是一个可变长的字段,它可以没有,也可以扩展到整个TS包,其存在与否由adaptation_field_control来控制。那么下面我们依然是分层看一下TS数据包头部的各个字段。第一层的代码逻辑和字段解释按顺序展示如下:
// TS包头部第一行字段代码
transport_packet(){
sync_byte 8 bslbf
transport_error_indicator 1 bslbf
payload_unit_start_indicator 1 bslbf
transport_priority 1 bslbf
PID 13 uimsbf
transport_scrambling_control 2 bslbf
adaptation_field_control 2 bslbf
continuity_counter 4 uimsbf
if(adaptation_field_control=='10' || adaptation_field_control=='11'){
adaptation_field()
}
if(adaptation_field_control=='01' || adaptation_field_control=='11') {
for (i=0;i<N;i++){
data_byte 8 bslbf
}
}
}
TS包结构Header第一层(即前四个字节)字段信息
/**
* tcimsbf:'two's complement integer, msb (sign) bit first', 有符号整数
*/
adaptation_field() {
adaptation_field_length 8 uimsbf
if(adaptation_field_length >0) {
discontinuity_indicator 1 bslbf
random_access_indicator 1 bslbf
elementary_stream_priority_indicator 1 bslbf
PCR_flag 1 bslbf
OPCR_flag 1 bslbf
splicing_point_flag 1 bslbf
transport_private_data_flag 1 bslbf
adaptation_field_extension_flag 1 bslbf
if(PCR_flag == '1') {
program_clock_reference_base 33 uimsbf
reserved 6 bslbf
program_clock_reference_extension 9 uimsbf
}
if(OPCR_flag == '1') {
original_program_clock_reference_base 33 uimsbf
reserved 6 bslbf
original_program_clock_reference_extension 9 uimsbf
}
if (splicing_point_flag == '1') {
splice_countdown 8 tcimsbf
}
if(transport_private_data_flag == '1') {
transport_private_data_length 8 uimsbf
for (i=0; i<transport_private_data_length;i++){
private_data_byte 8 bslbf
}
}
if (adaptation_field_extension_flag == '1' ) {
adaptation_field_extension_length 8 uimsbf
ltw_flag 1 bslbf
piecewise_rate_flag 1 bslbf
seamless_splice_flag 1 bslbf
reserved 5 bslbf
if (ltw_flag == '1') {
ltw_valid_flag 1 bslbf
ltw_offset 15 uimsbf
}
if (piecewise_rate_flag == '1') {
reserved 2 bslbf
piecewise_rate 22 uimsbf
}
if (seamless_splice_flag == '1'){
splice_type 4 bslbf
DTS_next_AU[32..30] 3 bslbf
marker_bit 1 bslbf
DTS_next_AU[29..15] 15 bslbf
marker_bit 1 bslbf
DTS_next_AU[14..0] 15 bslbf
marker_bit 1 bslbf
}
for ( i=0;i<N;i++) {
reserved 8 bslbf
}
}
for (i=0;i<N;i++){
stuffing_byte 8 bslbf
}
}
}
TS包结构Header第二层字段信息
TS包结构Header第三层字段信息
TS包结构Header第四层字段信息
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
四、PSI
TS流中是多路节目复用的,即多个节目的多个基本流复用后在同一个TS流上传输,那么怎么知道各个节目在传输流中的位置,并区分哪个流属于哪个节目呢?所以就还需要一些附加信息,这就是PSI(Program Specific Information,节目专用信息)。标准中规定了4个PSI,分别是节目关联表PAT、节目映射表PMT、条件访问表CAT、网络信息表NIT。这些表都由一个或多个子表组成,而子表又进一步由一个或多个section组成,在从PSI表到TS包的转换过程中,section起到了中介的作用。不同的表之间可以通过表标识(table_id)进行区分,属于同一个table_id的不同子表一般通过表的扩展标识(table_id_extension)、版本号(version_number)进行区分,对于子表还要加上其它的字段信息条件。其在这些表中的结构如下图:
PSI包含了进行多路解调和显示节目的必要的和足够的信息,每个PSI表填充一个单独的TS包,并周期性间隔插入到TS流中,它们的PID值是特定的。因为TS包中需要PSI的存在,TS流中的TS包结构可能存在以下可能:
TS流中可能存在的TS包结构
当TS包中传输的是PSI时,该TS包中Header和PSI表之间会有一个8bit的字段:pointer_field,该字段的取值表示其后面直到TS包中PSI表第一个section的第一个字节的字节数。如果当前TS包中至少有一个PSI表的中的一个section,那么TS包头部的payload_unit_start_indicator字段值应该设置为1,payload部分的第一个字节应该是这个pointer_field字段,以指示PSI表第一个section的起始位置。如果当前TS包中未传输PSI,则TS头部的payload_unit_start_indicator字段值应该设置为0,payload部分就不会有这个pointer_field字段。
4.1、PAT
PAT(Program Association Table,节目关联表)的PID值固定为'0x0000', 每个TS流中可能包含一个或多个PAT,所有的这些PAT共同组成了这个TS流中包含的节目列表。PAT列出了TS流中存在哪些节目流,指定了TS流中每个节目对应PMT所在TS包的PID。PAT的第一条数据指定了NIT所在TS包的PID,其他数据指定了PMT所在TS包的PID,一个TS流含多少个节目就含有多少PMT。节目关联表PAT的结构图、结构代码、字段信息按顺序展示如下:
Figure F-3 -- Program association section diagram
/**
* rpchof: 'remainder polynomial coefficients, highest order first',剩余的多项式系数,最高位优先
*/
program_association_section() {
table_id 8 uimsbf
section_syntax_indicator 1 bslbf
'0' 1 bslbf
reserved 2 bslbf
section_length 12 uimsbf
transport_stream_id 16 uimsbf
reserved 2 bslbf
version_number 5 uimsbf
current_next_indicator 1 bslbf
section_number 8 uimsbf
last_section_number 8 uimsbf
for (i=0; i<N;i++) {
program_number 16 uimsbf
reserved 3 bslbf
if(program_number == '0') {
network_PID 13 uimsbf
} else {
program_map_PID 13 uimsbf
}
}
CRC_32 32 rpchof
}
PAT字段信息
4.2、PMT
解析TS流的时候首先要从PID为0的包里找到节目关联表PAT,因为在PAT中指定了PMT(Program Map Table,节目映射表)所在包的PID。由于PMT中指定了一路节目中各个基本流(视频、音频等)的映射关系,即该节目视频或音频所在TS包的PID,根据指定的PID就可以找到对应的音视频流。总结来说,PMT是用来区分单个节目中的各个基本流,PAT则是区分多路复用中的各个节目。节目映射表的结构图、代码结构、字段解释按顺序解释如下:
Figure F-5 -- TS program map section diagram
TS_program_map_section() {
table_id 8 uimsbf
section_syntax_indicator 1 bslbf
'0' 1 bslbf
reserved 2 bslbf
section_length 12 uimsbf
program_number 16 uimsbf
reserved 2 bslbf
version_number 5 uimsbf
current_next_indicator 1 bslbf
section_number 8 uimsbf
last_section_number 8 uimsbf
reserved 3 bslbf
PCR_PID 13 uimsbf
reserved 4 bslbf
program_info_length 12 uimsbf
for (i=0; i<N; i++) {
descriptor()
}
for (i=0;i<N1;i++) {
stream_type 8 uimsbf
reserved 3 bslbf
elementary_PID 13 uimsnf
reserved 4 bslbf
ES_info_length 12 uimsbf
for (i=0; i<N2; i++) {
descriptor()
}
}
CRC_32 32 rpchof
}
4.3、CAT
CAT(Conditional Access Table,条件访问表)所在TS包的PID值为'0x0001',CAT中列出了条件控制信息(ECM)和条件管理信息(EMM)所在分组的PID,用于节目的加密与解密。CAT的结构图、代码结构按顺序展示如下(相关字段在上面的PAT和PMT中已经出现过了,不需要再解释,参考上面即可):
CA_section() {
table_id 8 uimsbf
section_syntax_indicator 1 bslbf
'0' 1 bslbf
reserved 2 bslbf
section_length 12 uimsbf
reserved 18 bslbf
version_number 5 uimsbf
current_next_indicator 1 bslbf
section_number 8 uimsbf
last_section_number 8 uimsbf
for (i=0; i<N;i++) {
descriptor()
}
CRC_32 32 rpchof
}
4.4、NIT
NIT(Network Information Table,网络信息表)的PID由PAT中的network_PID字段指定,但NIT的内容是私有的、由用户指定的。它提供TS流的传输信息以及网络自身特性信息,比如网络名称、频道频率、调制特征等信息。
五、同步与时钟恢复
Mpeg-2用于音视频同步以及系统时钟恢复的时间标签分别在ES、PES和TS这3个层次中。在ES层,与同步有关的主要是视频缓冲验证VBV,用于防止解码器的缓冲器出现上溢或者下溢;在PES层,主要是在PES头信息里出现的显示时间标签PTS和解码时间标签DTS;在TS层中,TS头信息包含了节目时钟参考PCR,用于恢复出与编码端一致的系统时序时钟STC。
5.1、ES层
通常的视频压缩算法都采用了可变长编码,编码生成的视频码流是可变码率的。为了能够在实际的固定码率信道或者可变码率信道上传输,需要引入缓冲区缓存视频码流数据。因此,视频编码算法必须提供一个有效的缓冲区管理策略,确保缓冲区不会发生上溢和下溢。编码器通过码率控制算法,调整生成的视频码流满足既定的缓冲区管理策略;同时在码率控制算法中使用自适应量化方法,确保压缩视频的质量。缓冲区管理策略通常都是建立在一个假想的解码器模型上,该解码器模型直接和编码器的输出相连接,缓冲区管理策略通过控制编码视频数据流移入和移出解码器缓冲区的时间以保证解码器模型的数据缓冲区不上溢也不下溢。在Mpeg-2标准中,该解码器模型称为VBV(Video Buffer Verifier,视频缓冲验证),VBV在ES层中定义。
5.2、PES层
在PES层打包的时候,在包的头部插入了DTS(Decoding Time Stamp,解码时间戳)和PTS(Presentation Time Stamp,显示时间戳)两个标签。对于视频来说,由于B帧PTS和DTS相等,所以无须在B帧多插入 DTS,而对于I帧和P帧,由于经过复用后数据包的顺序会发生变化,显示前一定要存储在视频解码器的重新排序缓存器中,经过重新排序后再显示,所以一定要同时插入PTS和DTS作为重新排序的依据。而对于音频流来说,音频没有双向预测,它的解码时间就是显示时间,即DTS和PTS是相同的,因此只用一个即可,又因为标准中规定不能单单只出现DTS而没有PTS(见上面的表格《图Figure F-2第二行字段信息》),所以音频只采用PTS即可。
DTS和PTS为系统目标解码器分别指定了基本流的预期解码时间和显示时间,它们都是对系统时钟的300分频的时钟计数值,且应该和PCR有相同的起点。本机PCR起到的是连续计数的功能,而DTS和PTS则是指定的一个时间点,可以起到时间定时的作用,相当于闹钟。在PCR值连续的情况下,DTS和PTS等待自身指定的时间点的到来,当PCR的值等于DTS或PTS时,代表着解码器的解码序列或显示序列已经准备好了,可以开始按照指定的时间进行解码或显示动作了。
如上所示的两个公式分别是DTS、PTS值的计算公式,其中和分别表示基本流n的显示单元k在系统目标解码器的解码时间和显示时间,DTS和PTS的值是以系统时钟频率除以300(即90khz)的周期为单位。其中的系统时钟频率(SystemClockFrequency)单位是赫兹(Hz),其应该满足如下约束(原始数据编码和实际应用程序中该约束会有更高的精度,高精度的约束会通过系统时钟描述符descriptor传递给解码器):
5.3、TS层
在Mpeg-2编码器中有一个共同的系统时钟,此时钟用来产生指示音频和视频的正确解码和显示时序的时间标签(DTS、PTS),同时可用来产生在抽样瞬间系统时序时钟的瞬时值(PCR)。标准中规定的系统时钟频率为27MHz,传输流中的PCR,PTS/DTS等均为对该共同系统时钟的采样值。解码端捕获PCR,恢复出本地的STC(System Time Clock,系统时序时钟),将其作为音视频同步控制的基准,并依据PTS和DTS时间标签来安排解码和显示时间表,使音视频分别同步于STC,以实现音视频之间的同步。
PCR(Program Clock Reference,节目时钟参考)由33bit的PCR_base和9bit的PCR_ext共同组成,表示包含PCR_base最后一位的字节到达T-STD(Transport Stream System Target Decoder,系统目标解码器)输入处的预期时间。PCR的取值是对编码器系统时钟脉冲触发的计数器状态抽样而来,其插入到TS包头的自适应区(Adaptation Field)中进行传输,用于解码端的时钟同步。标准规定在原始音频和视频流中,PTS的间隔不能超过0.7s(700ms),而出现在TS包头的PCR间隔不能超过0.1s(100ms)。
PCR_base是由27MHz脉冲经300分频后的90kHz脉冲触发计数器,再对计数器状态进行取样得到的,其作用是在解码器切换节目时,提供对解码器PCR计数器的初始值,让该PCR值与PTS、DTS最大可能地达到相同的时间起点;而PCR_ext是由27MHz脉冲直接触发计数器,再对计数器状态进行取样得到的,其作用是通过解码器端的锁相环电路修正解码器的系统时钟,使其达到和编码器一致的27MHz。如下面所示的三行算式是PCR的计算公式,其中i表示PCR_base字段的最后一个字节,t(i)表示该字节到达系统目标解码器输入处的时间:
5.4、时钟恢复
当新节目的PCR到达解码器时,需要更新时间基准,此时解码端系统时钟STC被置位。通常第一个从解复用器中解出的PCR被直接装入到如上图所示的左侧作为输入,然后进入右侧的PLL(phase-locked loop,锁相环)闭环操作部分。每当一个新节目的PCR到达解码器时,此值被认为是锁相环的参考频率,用来与STC的当前值比较,减法器(Subtractor)产生的差值e经过脉宽调制后被输入低通滤波器并经放大,输出控制信号f,用做电压控制振荡器(VCO)的瞬时频率,VCO输出的频率是在27MHz左右振荡的信号,作为解码器的系统时钟。27MHz时钟经过波形整理后输入到计数器(Counter)中,产生当前的STC值,其33bits的90kHz部分用于和PTS/DTS比较,产生解码和显示的同步信号。
如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓