流媒体基础-RTP封装PS流

PS流格式

首条数据结构:

RTP Header + PS Header +(System Header + PSM)+ PES(Header + Payload)

非首条数据结构:

RTP Header + PS Header + PES(Header + Payload)

接下来记录侧重于找到 H.264 数据,所以会跳过一些暂时不关心的内容。

PS流有一个结束码 MPEG_program_end_code:占位32bit,其值为0x000001B9。

PS流总是以0x000001BA开始,以0x000001B9结束,对于一个PS文件,有且只有一个结束码0x000001B9;不过对于直播的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头、系统头、PSM、PES。

所以一个IDR NALU PS 包由外到内顺序是:PS header > PS system header > PSM > PES。对于其它非关键帧的PS包,就简单多了,直接加上PS头和PES 头就可以了,顺序为:PS header > PES。

以上是对只有视频 video 的情况,如果要把音频Audio也打包进PS封装,只需将数据加上PES header 放到视频PES 后就可以了。顺序如下:PS包 = PS头 > PES(v) > PES(a)。

PS Header(PS头)

起始位:0x000001BA

  • 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;规定了此字段之后填充字段的长度;
LOOP
  • stuffing_byte:填充字段,固定为0xFF;长度由前一字段确定;
LOOP END

system_header字段:系统头部字段,只有当下一个字段的值为系统头部起始码0x000001BB时才存在,长度不定,详细请看PS System Header。

抓包实例:

RTP Payload 中承载的即为 PS 数据,起始的 0x000001BA 代表 PS 包的开始(startcode)

接下来跳过 9 个字节,暂时不关心它的内容,看第 10 个字节 fe,对应着二进制数据的 1111 1110,它的后三位为 110 为十进制的 6,即接下来的六个字节是扩展内容。

#define PS_HDR_LEN  14
static int gb28181_make_ps_header(char *pData, unsigned long long s64Scr)
{
    unsigned long long lScrExt = (s64Scr) % 100;    
    s64Scr = s64Scr / 100;
    bits_buffer_s      bitsBuffer;
    bitsBuffer.i_size = PS_HDR_LEN;    
    bitsBuffer.i_data = 0;
    bitsBuffer.i_mask = 0x80;
    bitsBuffer.p_data = (unsigned char *)(pData);
    memset(bitsBuffer.p_data, 0, PS_HDR_LEN);
    bits_write(&bitsBuffer, 32, 0x000001BA); /*start codes 起始码*/
    bits_write(&bitsBuffer, 2, 1); /*marker bits '01b'*/
    bits_write(&bitsBuffer, 3, (s64Scr>>30)&0x07); /*System clock [32..30]*/
    bits_write(&bitsBuffer, 1, 1); /*marker bit*/
    bits_write(&bitsBuffer, 15, (s64Scr>>15)&0x7FFF); /*System clock [29..15]*/
    bits_write(&bitsBuffer, 1, 1); /*marker bit*/
    bits_write(&bitsBuffer, 15, s64Scr & 0x7fff); /*System clock [14..0]*/
    bits_write(&bitsBuffer, 1, 1); /*marker bit*/
    bits_write(&bitsBuffer, 9, 0); /*SCR extension*/
    bits_write(&bitsBuffer, 1, 1); /*marker bit*/
    bits_write(&bitsBuffer, 22, (255)&0x3fffff); /*bit rate(n units of 50 bytes per second.)*/
    bits_write(&bitsBuffer, 2, 3); /*marker bits '11'*/
    bits_write(&bitsBuffer, 5, 0x1f); /*reserved(reserved for future use)*/
    bits_write(&bitsBuffer, 3, 0); /*stuffing length*/
    return 0;
}

PS 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
  • 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 开始,当且仅当数据包为第一个数据包时才存在。

这四个字节代表 PS System Header 的开始,之后紧邻的2个字节的 header_length,其 0x0012表示 PS System Header 的长度,换算为十进制,即为18个字节。

在 System Header 中,可以获取到 PS 中的码流种类(stream_id)在 header_length 后共有 6 个字节的数据,之后就是 stream_id 字段。第一个 stream_id 值为 0xE0,对照可知本 PS 流为视频流:

0xE0 视频流
0xC0 音频流
0xBD 海康私有流

它是 3 个字节一循环,第二个 stream_id 值为 0xC0,可知为音频流,之后又出现了第三个 stream_id 值为 0xBD,这是一个海康私有流。

这个 0xBD 看一篇文章有提及:遇到 0x000001BD 的,这个是海康私有流的标识,如果丢弃,之后就看不到原视频里移动侦测时闪烁的红框。 
#define SYS_HDR_LEN 18
static int gb28181_make_sys_header(char *pData)
{    
    bits_buffer_s bitsBuffer;
    bitsBuffer.i_size = SYS_HDR_LEN;
    bitsBuffer.i_data = 0;
    bitsBuffer.i_mask = 0x80;
    bitsBuffer.p_data = (unsigned char *)(pData);
    memset(bitsBuffer.p_data, 0, SYS_HDR_LEN);
    /*system header*/
    bits_write( &bitsBuffer, 32, 0x000001BB); /*start code*/
    bits_write( &bitsBuffer, 16, SYS_HDR_LEN-6); /* 减6,是因为start code加上length这两位,占了6个字节*/
    bits_write( &bitsBuffer, 1, 1); /*marker_bit*/
    bits_write( &bitsBuffer, 22, 50000); /*rate_bound*/
    bits_write( &bitsBuffer, 1, 1); /*marker_bit*/
    bits_write( &bitsBuffer, 6, 1); /*audio_bound*/
    bits_write( &bitsBuffer, 1, 0); /*fixed_flag */
    bits_write( &bitsBuffer, 1, 1); /*CSPS_flag */
    bits_write( &bitsBuffer, 1, 1); /*system_audio_lock_flag*/
    bits_write( &bitsBuffer, 1, 1); /*system_video_lock_flag*/
    bits_write( &bitsBuffer, 1, 1); /*marker_bit*/
    bits_write( &bitsBuffer, 5, 1); /*video_bound*/
    bits_write( &bitsBuffer, 1, 0); /*dif from mpeg1*/
    bits_write( &bitsBuffer, 7, 0x7F); /*reserver*/
    /*audio stream bound*/
    bits_write( &bitsBuffer, 8, 0xC0); /*stream_id 音频的流id*/
    bits_write( &bitsBuffer, 2, 3); /*marker_bit */
    bits_write( &bitsBuffer, 1, 0); /*PSTD_buffer_bound_scale*/
    bits_write( &bitsBuffer, 13, 512); /*PSTD_buffer_size_bound*/
    /*video stream bound*/
    bits_write( &bitsBuffer, 8, 0xE0); /*stream_id 视频的流id*/
    bits_write( &bitsBuffer, 2, 3); /*marker_bit */
    bits_write( &bitsBuffer, 1, 1); /*PSTD_buffer_bound_scale*/
    bits_write( &bitsBuffer, 13, 2048); /*PSTD_buffer_size_bound*/
    return 0;
}

PSM(节目映射流)

起始位:0x000001BC

PSM只有关键帧的时候才会存在,PSM用来提供es流的描述信息,以及各es流之间的关系。

  • 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字段的长度;
LOOP
  • descriptor字段:program Stream信息描述字段,长度由前个字段确定;
LOOP END
  • 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(C0~DF)指音频,0x(E0~EF)为视频;
  • elementary_stream_info_length字段:长度字段,占位16bit;表示此字段之后的,ES流描述信息的长度;
  • descriptor:描述信息,长度由前个字段确定;表示此类型的ES流的描述信息,这个描述信息的长度是不包含在elementary_stream_map_length字段里面的;
LOOP
  • CRC_32:CRC字段,占位32bit,CRC校验值;

如果要解析原始流编码类型,则需要解析PSM,我们需要找到 0x000001BC 的位串,然后根据PSM的结构进行解析;如果不需要解析,则在找到PSM的开始码之后,找到此字段的长度字段,跳过此长度即可,直接解析后面的PES包。

再之后开始解析,又碰到了 0x000001,这三个字符连在一起可以作为数据端的分隔符。

现在还不知道这块数据是什么类型,读取下一个字节,也就是 0xBC(PSM),二进制的 1011 1100。

对照 stream_id 表,第一行的就是本段数据的类型 program_stream_map,即 PSM。

同理,再之后的两个字段为 header 的长度,0x005E 十进制的 94。

接下来的 94 个字节也是属于 PSM 段的,从 PSM 中可以获取到视频流的具体编码类型。

结合上边的 PSM 结构,自 0x005E 后,跳过两个字段的固定内容,就到了两个字节的 program_stream_info_length,其值为 0x0024,说明其后跟着的 descriptor 共占 36 字节。

跳过 36 字节后,接下来的值 0x0030 为 element_stream_map_length(基本流映射长度),也就是 48 字节,它表示接下来的 48 字节都是用来描述原始流信息的。

接下来进入了原始流描述的第一次循环,第一个字节 stream_type 值为 0x1B,根据 GB28181 的定义可知为 H.264 编码,之后的 0xE0 表示其为视频流。

再之后的 0x001C 代表接下来描述占用 28 字节。

同理,接着循环下去,接下来的字段 0x90 和 0xC0,代表着其为 G.711 编码的音频流。

循环两次后,他们占用的字节数已经等于 48,跟 element_stream_map_length 值相等。

所以循环停止,接下来的内容就是 CRC_32,四个字节。

static int gb28181_make_psm_header(char *pData)
{
    bits_buffer_s bitsBuffer;
    bitsBuffer.i_size = PSM_HDR_LEN; 
    bitsBuffer.i_data = 0;
    bitsBuffer.i_mask = 0x80;
    bitsBuffer.p_data = (unsigned char *)(pData);
    memset(bitsBuffer.p_data, 0, PSM_HDR_LEN);//24Bytes
    bits_write(&bitsBuffer, 24,0x000001); /*start code*/
    bits_write(&bitsBuffer, 8, 0xBC); /*map stream id*/
    bits_write(&bitsBuffer, 16,18); /*program stream map length*/ 
    bits_write(&bitsBuffer, 1, 1); /*current next indicator */
    bits_write(&bitsBuffer, 2, 3); /*reserved*/
    bits_write(&bitsBuffer, 5, 0); /*program stream map version*/
    bits_write(&bitsBuffer, 7, 0x7F); /*reserved */
    bits_write(&bitsBuffer, 1, 1); /*marker bit */
    bits_write(&bitsBuffer, 16,0); /*programe stream info length*/
    bits_write(&bitsBuffer, 16, 8); /*elementary stream map length is*/
    /*audio*/
    bits_write(&bitsBuffer, 8, 0x90); /*stream_type 音频编码格式G711*/
    bits_write(&bitsBuffer, 8, 0xC0); /*elementary_stream_id*/
    bits_write(&bitsBuffer, 16, 0); /*elementary_stream_info_length is*/
    /*video*/
    bits_write(&bitsBuffer, 8, 0x1B); /*stream_type 视频编码格式H.264*/
    bits_write(&bitsBuffer, 8, 0xE0); /*elementary_stream_id*/
    bits_write(&bitsBuffer, 16, 0); /*elementary_stream_info_length */
    /*crc (2e b9 0f 3d)*/
    bits_write(&bitsBuffer, 8, 0x45); /*crc (24~31) bits*/
    bits_write(&bitsBuffer, 8, 0xBD); /*crc (16~23) bits*/
    bits_write(&bitsBuffer, 8, 0xDC); /*crc (8~15) bits*/
    bits_write(&bitsBuffer, 8, 0xF4); /*crc (0~7) bits*/
    return 0;
}

PES(打包流)

PSM 之后跟着的就是 PES,它分为两个部分,一部分是 Header,一部分是 Payload,Header 用于存储一些描述信息,而 Payload 部分为其存储的原始数据,PES 可能有多个。

我们来看看I帧的PS流格式,这里需要注意的是SPS、PPS之前要加上PES头部。如下图所示,其中绿色部分就是我们拿到的H.264裸流数据,须将它拆分成三段并在前面加上PES头部。这一点在GB28181标准中没有细说,需要通过分析海康IPC流才能看出。

PSH:0x000001BA
SYS:0x000001BB
PSM:0x000001BC
PES Header:0x000001E0
SPS NALU:0x0000000167
PPS NALU:0x0000000168
IDR NALU:0x0000000165

很显然,PS流的裸流数据,是AnnexB格式的。

一般情况下IDR帧很大,超过了RTP的负载长度限制(1400字节),所以上面这一个I帧要拆分成若干包RTP分多次发送。第一包的结构如上图所示,第二包以后RTP的结构就简单多了,没有PS头,是这样的:

上面提到的是I帧的情况,相比它,P/B帧的帧格式真是太简单了,因为它既没有SYS、PSM,也没有SPS、PPS:

P/B帧大小一般不超过1400字节,如果超过1400字节,也需分成多包RTP数据进行传输,超出1400部分的第二包RTP结构:

PES Header

PES头部,它记录了帧的时间戳,DTS可以不填。

数据开始以 0x000001 进行分隔,接下来的一个字段决定着数据段的类型,例如其值为 0xE0,对应的二进制是 1110 0000,由之前的表格可知,它是一个视频流。

再之后的 0x002A 表示长度,值为42,即其后的42字节为数据内容长度。

之后参考 PES packet 结构来解析数据。

由文档可知,当 PES 包的 stream_id 不是几种类型的情况,那么进行解析。开始时一个固定的两位二进制数 10,跟图片上的 8c(二进制:1000 1100),加上之后的几个数据位,共 8 位一个字节,跳过后,来到了 80(二进制:1000 000),也就是 PTS_DTS_flags = 10。

当值为'10'时,PTS 字段应出现在PES 分组标题中;当值为'11'时,PTS 字段和DTS 字段都应出现在PES 分组标题中;当值为'00'时,PTS 字段和 DTS 字段都不出现在 PES 分组标题中。值'01'是不允许的。

也就是 PES Header 中含有 PTS 段,不含有 DTS 段,PES 头主要是通过 PTS 和 DTS 来提供音视频同步的信息,防止解码器输入缓存上溢或下溢的关键。每一个 I帧、P帧、B帧 的包头都有一个PTS和DTS。

下边是 PES Packet 结构的下边部分:

另外注意的一个字段是 PES_header_data_length,它的位置是PES包长度跳过两个字节后的位置。

它的值为 0x09,也就是其后字节的长度,通过这个可以分割PES头部内容和数据段,其它字段暂不关心。

上边3个截图,它们都是以 0x000001E0 起始的 PES 包,PES 是可以存在多个的,有点不同的是最后一个截图,它的包很大,0xFFC6 有 65478 个字节,根据长度位置后边的第二个字节0x00 判断可知,这个 PES 包的PTS_DTS_flags值为0,这个PES包不包含PTS和DTS信息。

再之后的字节 0x05 为PES_header_data_length,代表它的 PES Header 头部数据长度为5。

#define PES_HDR_LEN 19
static int gb28181_make_pes_header(char *pData, int stream_id, int payload_len, unsigned long long pts, unsigned long long dts)
{
    bits_buffer_s bitsBuffer;
    bitsBuffer.i_size = PES_HDR_LEN;
    bitsBuffer.i_data = 0;
    bitsBuffer.i_mask = 0x80;
    bitsBuffer.p_data = (unsigned char *)(pData);
    memset(bitsBuffer.p_data, 0, PES_HDR_LEN);
    
    /*System Header*/
    bits_write( &bitsBuffer, 24,0x000001); /*start code*/
    bits_write( &bitsBuffer, 8, (stream_id)); /*streamID*/
    bits_write( &bitsBuffer, 16,(payload_len)+13); /*packet_len pes剩余头部以及后面的es长度之和,比如SPS长度+13*/
    bits_write( &bitsBuffer, 2, 2 ); /*'10'*/
    bits_write( &bitsBuffer, 2, 0 ); /*scrambling_control*/
    bits_write( &bitsBuffer, 1, 1 ); /*priority*/
    bits_write( &bitsBuffer, 1, 1 ); /*data_alignment_indicator*/
    bits_write( &bitsBuffer, 1, 0 ); /*copyright*/
    bits_write( &bitsBuffer, 1, 0 ); /*original_or_copy*/
    bits_write( &bitsBuffer, 1, 1 ); /*PTS_flag 是否有PTS*/
    bits_write( &bitsBuffer, 1, 1 ); /*DTS_flag 是否有DTS信息*/
    bits_write( &bitsBuffer, 1, 0 ); /*ESCR_flag*/
    bits_write( &bitsBuffer, 1, 0 ); /*ES_rate_flag*/
    bits_write( &bitsBuffer, 1, 0 ); /*DSM_trick_mode_flag*/
    bits_write( &bitsBuffer, 1, 0 ); /*additional_copy_info_flag*/
    bits_write( &bitsBuffer, 1, 0 ); /*PES_CRC_flag*/
    bits_write( &bitsBuffer, 1, 0 ); /*PES_extension_flag*/
    bits_write( &bitsBuffer, 8, 10); /*header_data_length*/ 
    
    /*PTS,DTS*/    
    bits_write( &bitsBuffer, 4, 3 ); /*'0011'*/
    bits_write( &bitsBuffer, 3, ((pts)>>30)&0x07 ); /*PTS[32..30]*/
    bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,((pts)>>15)&0x7FFF); /*PTS[29..15]*/
    bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,(pts)&0x7FFF); /*PTS[14..0]*/
    bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 4, 1 ); /*'0001'*/
    bits_write( &bitsBuffer, 3, ((dts)>>30)&0x07 ); /*DTS[32..30]*/
    bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,((dts)>>15)&0x7FFF); /*DTS[29..15]*/
    bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,(dts)&0x7FFF); /*DTS[14..0]*/
    bits_write( &bitsBuffer, 1, 1 );
    return 0;
}

PES Body(H264)

封装H.264的PS流是annexB格式,具备起始头,如果是ES流的话,就是AVCC格式。

PES Header 其后就是 Payload 数据,0x00000001 表示着数据段的开始:

Startcode + NALU Header + NALU Payload

翻看上边 PES 截图,本例中共有四个 PES 包,查看一下它们的数据部分。0x00000001 其后分别跟着 0x67、0x65、0x68、0x06。

0x06:0000110(SEI)
0x65:01100101(IDR)
0x67:01100111(SPS)
0x68:01101000(PPS)

根据 H264 封装规则,0x67代表这一帧为SPS,0x68为PPS,0x06为SEI Message,65 为 I 帧。

我从海康收到的第一个PS包,到最后解析到 I 帧的时候,至数据末尾肯定不到 1400 个字节,而最后一个 PES 包的长度字段标志为 65478 字节,可以确定的是因为 I 帧过大,进行了分包。

简单总结

以上,通过对海康网络摄像头输出的 PS 流结构进行查看,已经清晰的知道了它的结构,虽未详尽了解各个参数,但包含主要的节点,能为后续解码提供先行知识,正如 GB28181 文档所写的,RTP 返回的数据承载着 PS 包,PS 包有 PS Header、系统头、PSM、还有N个PES(国标中用 PESV 表示视频,PESA 表示音频),知道了 PS 包的结构,就可以用处理程序解析数据,从而把原始流ES(H264)从 PES 包中解析出来。

猜你喜欢

转载自blog.csdn.net/davidsguo008/article/details/128537756