PS头封装格式
PS流是对PES的进一步封装,是将具有共同时间基准的一个或多个PES包组合而成的单一的数据流;其基本单位是PS包,PS流由很多个PS包组成,PS包主要由固定包头,系统头,和PES包组成,其具体组成如下图所示:
下图更为清晰:
PS流有一个结束码MPEG_program_end_code
:占位32bit,其值为0x000001B9
,PS流总是以0x000001BA
开始,以0x000001B9
结束,对于一个PS文件,有且只有一个结束码0x000001B9
,不过对于网传的PS流,则应该是没有结束码的;
具体PS包头字段组成顺序如下:
-
pack_start_code
字段:起始码,占位32bit,标识PS包的开始,固定为0x000001BA
; -
‘01’
字段:占位2bit; -
SCR
字段:占位46bit,其中包含42bit的SCR值和4个bit的marker_bit值;其中SCR值由system_clock_reference_base
和system_clock_reference_extension
两部分组成;字节顺序依次是: -
system_clock_reference_base [32..30]
:占位3bit; -
marker_bit
:占位1bit; -
system_clock_reference_base [29..15]
:占位15bit; -
marker_bit
:占位1bit; -
system_clock_reference_base [14..0]
:占位15bit; -
marker_bit
:占位1bit; -
system_clock_reference_extension
:占位9bit; -
marker_bit
:占位1bit; -
program_mux_rate
字段:速率值字段,占位22bit,正整数,表示P-STD接收此字段所在包的PS流的速率;这个值以每秒50字节作为单位;禁止0值; -
Marker_bit
:标记字段,占位1bit,固定为’1’; -
Marker_bit
:标记字段,占位1bit,固定为’1’; -
Reserved
字段:保留字段,占位5bit; -
pack_stuffing_length
字段:长度字段,占位3bit;规定了此字段之后填充字段的长度; -
stuffing_byte
:填充字段,固定为0xFF;长度由前一字段确定; system_header
字段:系统头部字段,只有当下一个字段的值为系统头部起始码0x000001BB
时才存在,长度不定,其组成字段顺序如下所示:-
system_header_start_code
字段:系统头部起始码,占位32bit,值固定为0x000001BB,标志系统首部的开始; -
header_length
字段:头部长度字段,占位16bit,表示此字段之后的系统首部字节长度; -
Marker_bit
字段:占位1bit,固定值为1; -
rate_bound
字段:整数值,占位22bit,为一个大于或等于PS流所有PS包中的最大program_mux_rate值的整数;可以被解码器用来判断是否可以对整个流进行解码; -
Marker_bit
字段:占位1bit,固定值为1; -
audio_bound
字段:占位6bit;取值范围0到32间整数;大于或等于同时进行解码处理的PS流中的音频流的最大数目; -
fixed_flag
字段:标志位,占位1bit;置位1表示固定比特率操作,置位0则为可变比特率操作; -
CSPS_flag
字段:CSPS标志位,占位1bit;置位1表示此PS流满足标准的限制; -
system_audio_lock_flag
字段:标志位,占位1bit,表示音频采样率和STD的system_clock_frequency之间有一特定常数比例关系; -
system_video_lock_flag
字段:标志位,占位1bit,表示在系统目标解码器system_clock_frequency和视频帧速率之间存在一特定常数比例关系; -
Marker_bit
字段:占位1bit,固定值为1; -
video_bound
字段:整数,占位5bit,取值范围0到16;大于或等于同时进行解码处理的PS流中的视频流的最大数目; -
packet_rate_restriction_flag
字段:分组速率限制标志字段,占位1bit,若CSPS_flag == 1,则此字段表示哪种限制适用于分组速率;若CSPS_flag == 0,则此字段无意义; -
reserved_bits
字段:保留字段,占位7bit,固定为’1111111’; LOOP
:当下一个bit为1时进入
-
stream_id
字段:占位8bit,表示其后的P-STD_buffer_bound_scale
和P-STD_buffer_size_bound
字段所涉及的流的编码和基本流的号码;若stream_id ==’1011 1000’
,则其后的P-STD_buffer_bound_scale
和P-STD_buffer_size_bound
字段对应PS流中的所有音频流;若stream_id ==’1011 1001’
,则其后的P-STD_buffer_bound_scale
和P-STD_buffer_size_bound
字段对应PS流中的所有视频流;若取其他值,则应大于’1011 1100’,且按照标准对应Stream id(详见附录1);PS流中的每个原始流都应在每个系统首部中通过这种机制精确地规定一次它的P-STD_buffer_bound_scale
和P-STD_buffer_size_bound
; -
‘11’
字段:占位2bit; -
P-STD_buffer_bound_scale
字段:占位1bit,表示用来解释后面P-STD_buffer_size_bound字段的比例因子;如果之前的stream_id表示音频流,则此值应为0,若之前的stream_id表示视频流,则此值应为1,对于其他stream类型,此值可以0或1;+ P-STD_buffer_size_bound
字段:占位13bit,无符号整数;大于或等于所有PS流分组的P-STD输入缓冲区大小BSn的最大值;若P-STD_buffer_bound_scale == 0,则P-STD_buffer_size_bound以128字节为单位;若P-STD_buffer_bound_scale == 1,则P-STD_buffer_size_bound以1024字节为单位;
-
LOOP End
;
目前的系统头部好像是没有用到的,所以对于系统头部的解析,我们一般只要先首先判断是否存在系统头(根据系统头的起始码0x000001BB),然后我们读取系统头的头部长度,即header_length部分,然后根据系统头部的长度,跳过PS系统头,进入下一个部分,即PS的payload,PES包;在固定包头和系统头之后,就是PS包的payload,即PES包;若PSM存在,则第一个PES包即为PSM。
PSM格式介绍
PSM提供了对PS流中的原始流和他们之间的相互关系的描述信息;PSM是作为一个PES分组出现,当stream_id == 0xBC时,说明此PES包是一个PSM;PSM是紧跟在系统头部后面的;PSM是作为PS包的payload存在的;
PSM由很多字段组成,其字节顺序如下所示:
-
Packet start code prefix
字段:包头起始码,固定为0x000001,占位24bit;与后面的字段map_stream_id一起组成分组开始码,标志着分组的开始; -
map_stream_id
字段:类型字段,标志此分组是什么类型,占位8bit;如果此值为0xBC,则说明此PES包为PSM; -
program_stream_map_length
字段:长度字段,占位16bit;表示此字段之后PSM的总长度,最大值为1018(0x3FA); -
current_next_indicator
字段:标识符,占位1bit;置位1表示当前PSM是可用的,置位0则表示当前PSM不可以,下一个可用; -
Reserved
:保留字段,占位2bit; -
program_stream_map_version
字段:版本字段,占位5bit;表示PSM的版本号,取值范围1到32,随着PSM定义的改变循环累加;若current_next_indicator == 1,表示当前PSM的版本号,若current_next_indicator == 0,表示下一个PSM的版本号; -
Reserved
:保留字段,占位7bit; -
marker_bit
:标记字段,占位1bit,固定为1; -
program_stream_info_length
字段:长度字段,占位16bit;表示此字段后面的descriptor字段的长度; -
Descriptor
字段:program Stream信息描述字段,长度由前个字段确定; -
elementary_stream_map_length
字段:长度字段,占位16bit;表示在这个PSM中所有ES流信息的总长度;包括stream_type, elementary_stream_id, elementary_stream_info_length
的长度,即N*32bit;是不包括具体ES流描述信息descriptor的长度的; LOOP
:
-
stream_type
字段:类型字段,占位8bit;表示原始流ES的类型;这个类型只能标志包含在PES包中的ES流类型;值0x05是被禁止的;常见取值类型有MPEG-4 视频流:0x10;H.264 视频流:0x1B;G.711 音频流:0x90;因为PSM只有在关键帧打包的时候,才会存在,所以如果要判断PS打包的流编码类型,就根据这个字段来判断; -
elementary_stream_id
字段:流ID字段,占位8bit;表示此ES流所在PES分组包头中的stream_id字段的值;其中0x(C0DF)指音频,0x(E0EF)为视频; -
elementary_stream_info_length
字段:长度字段,占位16bit;表示此字段之后的,ES流描述信息的长度; -
Descriptor
:描述信息,长度由前个字段确定;表示此类型的ES流的描述信息,这个描述信息的长度是不包含在elementary_stream_map_length字段里面的;
-
LOOP End
; -
CRC_32
:CRC字段,占位32bit,CRC校验值;
如果要解析原始流编码类型,则需要解析PSM,我们需要找到0x000001BC的位串,然后根据PSM的结构进行解析;如果不需要解析,则在找到PSM的开始码之后,找到此字段的长度字段,跳过此长度即可,直接解析后面的PES包;
PS总结:
解析PS包,要先找到PS包的的起始码0x000001BA位串,然后解析出系统头部字段,之后进入PS包的负载,判断是否有PSM,根据PSM确定payload的PES包中所负载的ES流类型;然后再根据ES流类型和ID从PES包中解析出具体的ES流;解包过程则相反;若要从PS流中找出来帧类型,必须将PS包解析成ES并组成完整的帧,然后在帧数据开始根据NAL头来进行帧的类型的判断;
PSM只有在关键帧打包的时候,才会存在;IDR包含了SPS,PPS和I帧;每个IDR NALU前一般都会包含SPS、PPS等NALU,因此将SPS、PPS、IDR的NALU 封装为一个PS 包,包括PS头,PS system header,PSM,PES;所以一个IDR NALU PS 包由外到内顺序是:PS header| PS system header | PSM| PES。对于其它非关键帧的PS包,就简单多了,直接加上PS头和PES 头就可以了。顺序为:PS header | PES header | h264raw data。以上是对只有视频video 的情况,如果要把音频Audio也打包进PS 封装,只需将数据加上PES header 放到视频PES 后就可以了。顺序如下:PS 包=PS头|PES(video)|PES(audio);
附录1:stream_id类型定义
stream_id | Note | stream coding |
1011 1100 | 1 | program_stream_map |
1011 1101 | 2 | private_stream_1 |
1011 1110 | padding_stream | |
1011 1111 | 3 | private_stream_2 |
110x xxxx | ISO/IEC 13818-3 or ISO/IEC 11172-3 audio | |
1110 xxxx | ITU-T Rec. H.262† | |
1111 0000 | 3 | ECM_stream |
1111 0001 | 3 | EMM_stream |
1111 0010 | 5 | ITU-T Rec. H.222.0† |
1111 0011 | 2 | ISO/IEC_13522_stream |
1111 0100 | 6 | ITU-T Rec. H.222.1 type A |
1111 0101 | 6 | ITU-T Rec. H.222.1 type B |
1111 0110 | 6 | ITU-T Rec. H.222.1 type C |
1111 0111 | 6 | ITU-T Rec. H.222.1 type D |
1111 1000 | 6 | ITU-T Rec. H.222.1 type E |
1111 1001 | 7 | ancillary_stream |
1111 1010 Ö 1111 1110 | reserved data stream | |
1111 1111 | 4 | program_stream_directory |