PS封装H264码流分析
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PS封装H264码流分析相关的知识,希望对你有一定的参考价值。
参考技术A 一个完整的ps包封装:
PSheader + PS system header + PS system Map + PES header + h264 data
因为一般视频数据都是采用rtp打包发送,所以这里我就把ps封装和rtp封装放在一起讲
当我们获取到一帧h264的关键帧数据时,先进行PS封装,在视频数据前加上 PS header + PS system header + PS system Map header ,当前面的头添加完毕后还需要再加一个 PES header,但是由于PES头的负载长度类型是short,最大为65536,所以每65536字节的视频数据后都得加一个PES头,如下:
| PS header | PS system header | PS system Map |PES | data | PES | data | PES | data|
这样一个关键帧的PS封装就完成了剩下的就是把封装好的数据分包打RTP包了,每1300字节的数据前加一个RTP包头,然后发送出去
pack_start_code : (32b) 起始码字段 默认0x000001BA 标志一个包的开始
marker_bit :(2b) 标记位字段2位字段,取值’01’。
system_clock_reference_base[32..30] :(3b)系统时钟参考字段
marker_bit : (1b) 标记位字段取值’1’
system_clock_reference_base[29..15] : (15b) 系统时钟参考字段
marker_bit : (1b) 标记位字段取值’1’
system_clock_reference_base[14..0] : (15b) 系统时钟参考字段
marker_bit : (1b) 标记位字段取值’1’
system_clock_reference_extension : (9b) 系统时钟参考字段
marker_bit : (1b) 标记位字段取值’1’
program_mux_rate : (22b) 节目复合速率字段
marker_bit : (1b) 标记位字段取值’1’
marker_bit : (1b) 标记位字段取值’1’
reserved : (5b) 填充字段
pack_stuffing_length : (3b) 包填充长度字段
节目复合速率字段 program_mux_rate (没查到相关资料)
一个22位整数,规定P-STD在包含该字段的包期间接收节目流的速率。其值以50字节/秒为单位。不允许取0值。该字段所表示的值用于在2.5.2中定义P-STD输入端的字节到达时间。该字段值在本标准中的节目多路复合流的不同包中取值可能不同。
/12字节RTP头/
80 60 00 00 00 00 00 00 0d 25 5a a5
/PS 头/
00 00 01 ba 44 00 05 5f 94 01 00 60 1b f8
00 00 01 ba 开始码
44 00 44 f5 84 01 系统时钟参考字段,二进制如下
01 000 1 000000000000000 1 010101111110010 1 000000000 1
SCR = 90000/8 = 00000000 00000000 00101011 11110010 (SRC值是累加的,这个是第一帧数据的SRC值)
上面红色的1都是marker_bit 标记位
000 这三个位段的值是由SCR值的第30-32位填充,参考上述SRC值,故填充3位0
000000000000000 这15位字段是由SRC值的第15-29位填充,故填充000000000000000
010101111110010 这15位字段是由SRC值的第0-14位填充,故填充 0101011 11110010
00 60 1b f8 二进制如下:
00000000001100000000110 11 11111 000
00000000001100000000110 没看懂用途,尝试随便取值不影响视频和音频,不能取0,我这里用的是6150(1100000000110)
11111 5位填充字段 全部填1
00 3位扩展长度填充字段 填0
system_header_start_code : (32b) 开始码 0x000001BB
header_length : (16) 该字段后的系统标题的字节长度
marker_bit : (1b) 标记位字段取值’1’
rate_bound : (22b) 速率界限字段
marker_bit : (1b) 标记位字段取值’1’
audio_bound : (6b) 音频界限字段
fixed_flag : (1b) 固定标志字段
CSPS_flag : (1b) CSPS标志字段
system_audio_lock_flag : (1b) 系统音频锁定标志字段
system_video_lock_flag : (1b) 系统视频锁定标志字段
marker_bit : (1b) 标记位字段取值’1’
vedio_bound : (5b) 视频界限字段
packet_rate_restriction_flag: (1b) 分组速率限制标志字段
reserved_bits : (7b) 保留位字段
stream_id : (8b) 流标识字段
marker_bit : (2b) 取值’11’
P-STD_buffer_bound_scale : (1b) P-STD缓冲区界限比例字段
P-STD_buffer_size_bound : (13) P-STD缓冲区大小界限字段
00 00 01 bb 00 0c 80 1e ff fe e1 7f e0 e0 d8 c0 c0 20
00 00 01 bb : 四字节开始码
00 0c : 当前字段后该头的长度 12
80 1e ff 转成二进制如下:
1 0000000000111101111111 1
111101111111 :rate_bound 该字段可被解码器用于估计是否有能力对整个流解码(没查到如何填值)
fe e1 7f转成二进制如下:
111111 1 0 1 1 1 00001 0 1111111
111111: 音频界限字段 audio_bound 6位字段,取值是在从0到32的闭区间中的整数,且不小于节目流中解码过程同时活动的GB/T XXXX.3和GB/T AAAA.3音频流的最大数目。在本小节中,若STD缓冲区非空或展现单元正在P-STD模型中展现,则GB/T XXXX.3和GB/T AAAA.3音频流的解码过程是活动的。
1 : 固定标志字段 fixed_flag 1位标志位。置’1’时表示比特率恒定的操作;置’0’时,表示操作的比特率可变。
0 : CSPS标志字段 CSPS_flag 1位字段。
1: 系统音频锁定标志字段 system_audio_lock_flag 置 ‘1’
1: 系统视频锁定标志字段 system_video_lock_flag 置 ‘1’
00001: 视频界限字段 video_bound
0: 分组速率限制标志字段 packet_rate_restriction_flag 1位标志位。若CSPS标识为’1’,则该字段表示2.7.9中规定的哪个限制适用于分组速率。若CSPS标识为’0’,则该字段的含义未定义。
1111111 : 7位字段。被保留供ISO/IEC将来使用。它的值应为’111 1111’,除非ISO/IEC对它作出其它规定。
e0 e0 d8 c0 c0 20转成二进制如下:
11100000 11 1 0000011011000 11000000 11 0 0000000100000
11100000 : 流标识字段 stream_id E0在gb28181中定义是视频
11: 固定值
1: 1位字段。表示用于解释后续P-STD_buffer_size_bound字段的比例系数。若前面的stream_id表示一个音频流,则该字段值为’0’。若表示一个视频流,则该字段值为’1’。对于所有其它的流类型,该字段值可以为’0’也可以为’1’。
0000011011000 : 音频缓存区大小 216 单位是1024字节
11000000: 流标识字段 stream_id C0在gb28181中定义是音频
11: 固定值
0:
0000000100000: 视频缓存区大小 32 单位 128字节
packet_start_code_prefix : (24b) 开始码 0x000001
map_stream_id : (8) 映射流标识字段 值为0xBC
program_stream_map_length: (16) 节目流映射长度字段
current_next_indicator : (1) 当前下一个指示符字段
reserved : (2) 填充字段
program_stream_map_version: (5) 节目流映射版本字段
reserved : (7)
marker_bit : (1)
program_stream_info_length : (16) 节目流信息长度字段
elementary_stream_map_length: (16) 基本流映射长度字段
stream_type : (8) 流类型字段
elementary_stream_id : (8) 基本流标识字段
elementary_stream_info_length : (16) 基本流信息长度字段
CRC_32 : (32) CRC 32字段
00 00 01 bc 00 18 e1 ff 00 00 00 08 1b e0 00 00 90 c0 00 00 23 b9 0f 3d
00 00 01 bc 开始码加固定id
00 18 头的长度
e1 ff 00 00 00 08二进制如下:
11 00001 1111111 1 0000000000000000 0000000000001000
1: 当前下一个指示符字段 current_next_indicator 1位字段。置’1’时表示传送的节目流映射当前是可用的。置’0’时表示传送的节目流映射还不可用,但它将是下一个生效的表。
11: 填充字段 ‘11’
00001: 节目流映射版本字段 program_stream_map_version 5位字段,表示整个节目流映射的版本号。一旦节目流映射的定义发生变化,该字段将递增1,并对32取模。在current_next_indicator为’1’时,该字段应该是当前适用的节目流映射的版本号;在current_next_indicator为’0’时,该字段应该是下一个适用的节目流映射的版本号。
1111111: 填充字段
0000000000000000: 节目流信息长度字段 program_stream_info_length 16位字段,指出紧跟在该字段后的描述符的总长度
0000000000001000: 基本流映射长度字段 elementary_stream_map_length 16位字段,指出在该节目流映射中的所有基本流信息的字节长度。它只包括stream_type、elementary_stream_id和elementary_stream_info_length字段。(这里注意一下,这里的基本流映射长度,他只包括他后面的指定的那几个定义字段的总和,即从从这个长度,我们可以知道后面他根了几种类型的流定义,因为一种流的这个定义字段:stream_type(1BYTE)、elementary_stream_id(1byte)和elementary_stream_info_length(2byte)字段总和为4个字节,所以用elementary_stream_map_length/4可以得到后面定义了几个流类型信息。)
1b e0 00 00: 1b是H264视频流 e0 :指视频
00 00指后面跟着0个字节的视频描述字节
90 c0 00 00: 90是G.711 音频流:0x90 ,c0指音频
00 00:0个字节描述符
23 b9 0f 3d : 32位字段,它包含CRC值以在处理完整个节目流映射后在附录A中定义的解码器寄存器产生0输出值。/crc (23 b9 0f 3d)/
packet_start_code_prefix : (24b) 分组起始码前缀字段 packet_start_code_prefix 0x000001
stream_id : (8) 流标识字段 stream_id 这个字段的定义,其中0x(C0 DF)指音频,0x(E0 EF)为视频
PES_packet_length : (16) PES分组长度字段 PES_packet_length
‘10’ : (2)
PES_scrambling_control : (2) PES加扰控制字段 PES_scrambling_control
PES_priority : (1) PES优先级字段 PES_priority
data_alignment_indicator : (1) 数据对齐指示符字段 data_alignment_indicator
copyright: (1) 版权字段 copyright
original_or_copy : (1) 原始或拷贝字段 original_or_copy
PTS_DTS_flags : (2) PTS DTS标志字段 PTS_DTS_flags
ESCR_flag : (1) ESCR标志字段 ESCR_flag
ES_rate_flag : (1) ES速率标志字段 ES_rate_flag
DSM_trick_mode_flag : (1) DSM特技方式标志字段 DSM_trick_mode_flag
additional_copy_info_flag : (1) 附加版权信息标志字段 additional_copy_info_flag
PES_CRC_flag : (1) PES CRC标志字段 PES_CRC_flag
PES_extension_flag: (1) PES扩展标志字段 PES_extension_flag
PES_header_data_length: (8) PES标题数据长度字段 PES_header_data_length
‘0011’ : (4)
PTS[32..30] : (3) 展现时间戳字段 PTS
marker_bit: (1)
PTS[29..15] : (15)
marker_bit : (1)
PTS[14..0] : (15)
marker_bit : (1)
00 00 01 e0 49 e6 88 80 05 31 00 01 57 e5
00 00 01: 开始码
e0 : 视频
49 e6: 视频数据长度
88 80 05 :二进制数据如下
10 00 1 0 0 0 10 0 0 0 0 0 0 00000101
10 : 固定值
00: PES加扰控制字段 PES_scrambling_control
1: PES优先级字段 PES_priority ‘1’表示PES分组中有效负载的优先级高于该字段为’0’的PES分组有效负载
0: 数据对齐指示符字段 data_alignment_indicator 当值为’0’时,没有定义是否有任何此种的对齐。
0: 版权字段 copyright当值为’0’时,没有定义该材料是否受到版权保护
0: 原始或拷贝字段 original_or_copy 1位字段。置’1’时表示相关PES分组有效负载的内容是原始的;值为’0’表示相关PES分组有效负载的内容是一份拷贝
10: PTS DTS标志字段 PTS_DTS_flags
2位字段。当值为’10’时,PTS字段应出现在PES分组标题中;当值为’11’时,PTS字段和DTS字段都应出现在PES分组标题中;当值为’00’时,PTS字段和DTS字段都不出现在PES分组标题中。值’01’是不允许的。
0 0 0 0 0 0 六个扩展标志位 置0
00000101 : PES标题数据长度字段 5 标明后续还有五个字节
31 00 01 57 e5:二进制如下
11 000 1 000000000000000 1 010101111110010 1
0011: 固定值
PTS = 90000/8 = 11250 二进制:
10101111110010
000: PTS第30 -32位填充
000000000000000: PTS第15 -29位填充
010101111110010: PTS第0 -14位填充
关于对H264码流的PS的封装的相关代码实现
转自:http://www.cnblogs.com/lidabo/p/6604988.html
1、写在开始之前:
最近因为新工作要维护别人留下的GB模块代码,先熟悉了流程,然后也试着封装了下ps流,结果也能通过测试正常预览了,当然,其中开发读文档的头疼,预览花屏,卡帧的事情都有遇到,当时慢慢的看文档,整理逻辑,也就都顺利解决了,下面把大致的一些流程代码贴出来分享下。既然是对接国标,自然少不了通读它的标准文档和相关的RFC文档了!具体的我就不说了,可以用百度google下的。
注意:因为是GB要求ps封装后再加上rtp头的格式来的, 所以下面代码中我也加上了rtp头,如果不需要的话,直接屏蔽代码中的rtp即可。
2、封装的重点
当我们从读缓冲区中取得一帧音视频数据的时候,封装时其实每一帧数据有且只有一个ps头和psm头,如果是I帧的话,就还多一个system头,一个或者多个pes头和rtp头,
像如果帧数据过长的话,就得进行分片,每片都会包含一个pes头,rtp负载最好长度1460,所以会进行再分包操作!所以每一个包数据至少一个rtp+databuf,每一片数据,至少有个rtp+pes+databuf,每一帧数据至少有rtp+ps+psm+pes+databuf(关键帧的话:多一个system头)
3、具体的各个封装的代码实现
首先给去一个整体的封装rtp->ps->sys->psm->pes(如果只要ps的话,则为ps->sys->psm->pes)的大致流程,
然后再一一罗列出各个部件的封装接口
- /***
- *@remark: 音视频数据的打包成ps流,并封装成rtp
- *@param : pData [in] 需要发送的音视频数据
- * nFrameLen [in] 发送数据的长度
- * pPacker [in] 数据包的一些信息,包括时间戳,rtp数据buff,发送的socket相关信息
- * stream_type[in] 数据类型 0 视频 1 音频
- *@return: 0 success others failed
- */
- int gb28181_streampackageForH264(char *pData, int nFrameLen, Data_Info_s* pPacker, int stream_type)
- {
- char szTempPacketHead[256];
- int nSizePos = 0;
- int nSize = 0;
- char *pBuff = NULL;
- memset(szTempPacketHead, 0, 256);
- // 1 package for ps header
- gb28181_make_ps_header(szTempPacketHead + nSizePos, pPacker->s64CurPts);
- nSizePos += PS_HDR_LEN;
- //2 system header
- if( pPacker->IFrame == 1 )
- {
- // 如果是I帧的话,则添加系统头
- gb28181_make_sys_header(szTempPacketHead + nSizePos);
- nSizePos += SYS_HDR_LEN;
- //这个地方我是不管是I帧还是p帧都加上了map的,貌似只是I帧加也没有问题
- // gb28181_make_psm_header(szTempPacketHead + nSizePos);
- // nSizePos += PSM_HDR_LEN;
- }
- // psm头 (也是map)
- gb28181_make_psm_header(szTempPacketHead + nSizePos);
- nSizePos += PSM_HDR_LEN;
- //加上rtp发送出去,这样的话,后面的数据就只要分片分包就只有加上pes头和rtp头了
- if(gb28181_send_rtp_pack(szTempPacketHead, nSizePos, 0, pPacker) != 0 )
- return -1;
- // 这里向后移动是为了方便拷贝pes头
- //这里是为了减少后面音视频裸数据的大量拷贝浪费空间,所以这里就向后移动,在实际处理的时候,要注意地址是否越界以及覆盖等问题
- pBuff = pData - PES_HDR_LEN;
- while(nFrameLen > 0)
- {
- //每次帧的长度不要超过short类型,过了就得分片进循环行发送
- nSize = (nFrameLen > PS_PES_PAYLOAD_SIZE) ? PS_PES_PAYLOAD_SIZE : nFrameLen;
- // 添加pes头
- gb28181_make_pes_header(pBuff, stream_type ? 0xC0:0xE0, nSize, (pPacker->s64CurPts / 100), (pPacker->s64CurPts/300));
- //最后在添加rtp头并发送数据
- if( gb28181_send_rtp_pack(pBuff, nSize + PES_HDR_LEN, ((nSize == nFrameLen)?1:0), pPacker) != 0 )
- {
- printf("gb28181_send_pack failed!\\n");
- return -1;
- }
- //分片后每次发送的数据移动指针操作
- nFrameLen -= nSize;
- //这里也只移动nSize,因为在while向后移动的pes头长度,正好重新填充pes头数据
- pBuff += nSize;
- }
- return 0;
- }
上面列出来了整个打包发包的过程,接下来一个一个接口的看
- /***
- *@remark: ps头的封装,里面的具体数据的填写已经占位,可以参考标准
- *@param : pData [in] 填充ps头数据的地址
- * s64Src [in] 时间戳
- *@return: 0 success, others failed
- */
- int gb28181_make_ps_header(char *pData, unsigned long long s64Scr)
- {
- unsigned long long lScrExt = (s64Scr) % 100;
- s64Scr = s64Scr / 100;
- // 这里除以100是由于sdp协议返回的video的频率是90000,帧率是25帧/s,所以每次递增的量是3600,
- // 所以实际你应该根据你自己编码里的时间戳来处理以保证时间戳的增量为3600即可,
- //如果这里不对的话,就可能导致卡顿现象了
- bits_buffer_s bitsBuffer;
- bitsBuffer.i_size = PS_HDR_LEN;
- bitsBuffer.i_data = 0;
- bitsBuffer.i_mask = 0x80; // 二进制:10000000 这里是为了后面对一个字节的每一位进行操作,避免大小端夸字节字序错乱
- 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 [29..15]*/
- bits_write(&bitsBuffer, 1, 1); /*marker bit*/
- bits_write(&bitsBuffer, 9, lScrExt&0x01ff); /*System clock [14..0]*/
- 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;
- }
- /***
- *@remark: sys头的封装,里面的具体数据的填写已经占位,可以参考标准
- *@param : pData [in] 填充ps头数据的地址
- *@return: 0 success, others failed
- */
- 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);/*header_length 表示次字节后面的长度,后面的相关头也是次意思*/
- 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*/
- 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*/
- 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;
- }
- /***
- *@remark: psm头的封装,里面的具体数据的填写已经占位,可以参考标准
- *@param : pData [in] 填充ps头数据的地址
- *@return: 0 success, others failed
- */
- 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, PS_SYS_MAP_SIZE);
- 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*/
- 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*/
- 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;
- }
- /***
- *@remark: pes头的封装,里面的具体数据的填写已经占位,可以参考标准
- *@param : pData [in] 填充ps头数据的地址
- * stream_id [in] 码流类型
- * paylaod_len[in] 负载长度
- * pts [in] 时间戳
- * dts [in]
- *@return: 0 success, others failed
- */
- 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分组中数据长度和该字节后的长度和
- bits_write( &bitsBuffer, 2, 2 ); /*‘10‘*/
- bits_write( &bitsBuffer, 2, 0 ); /*scrambling_control*/
- bits_write( &bitsBuffer, 1, 0 ); /*priority*/
- bits_write( &bitsBuffer, 1, 0 ); /*data_alignment_indicator*/
- bits_write( &bitsBuffer, 1, 0 ); /*copyright*/
- bits_write( &bitsBuffer, 1, 0 ); /*original_or_copy*/
- bits_write( &bitsBuffer, 1, 1 ); /*PTS_flag*/
- bits_write( &bitsBuffer, 1, 1 ); /*DTS_flag*/
- 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*/
- // 指出包含在 PES 分组标题中的可选字段和任何填充字节所占用的总字节数。该字段之前
- //的字节指出了有无可选字段。
- /*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;
- }
- /***
- *@remark: rtp头的打包,并循环发送数据
- *@param : pData [in] 发送的数据地址
- * nDatalen [in] 发送数据的长度
- * mark_flag [in] mark标志位
- * curpts [in] 时间戳
- * pPacker [in] 数据包的基本信息
- *@return: 0 success, others failed
- */
- int gb28181_send_rtp_pack(char *databuff, int nDataLen, int mark_flag, Data_Info_s* pPacker)
- {
- int nRes = 0;
- int nPlayLoadLen = 0;
- int nSendSize = 0;
- char szRtpHdr[RTP_HDR_LEN];
- memset(szRtpHdr, 0, RTP_HDR_LEN);
- if(nDataLen + RTP_HDR_LEN <= RTP_MAX_PACKET_BUFF)// 1460 pPacker指针本来有一个1460大小的buffer数据缓存
- {
- // 一帧数据发送完后,给mark标志位置1
- gb28181_make_rtp_header(szRtpHdr, ((mark_flag == 1 )? 1 : 0 ), ++pPacker->u16CSeq, (pPacker->s64CurPts /300), pPacker->u32Ssrc);
- memcpy(pPacker->szBuff, szRtpHdr, RTP_HDR_LEN);
- memcpy(pPacker->szBuff + RTP_HDR_LEN, databuff, nDataLen);
- nRet = SendDataBuff(databuff, RTP_HDR_LEN + nSendSize, pPacker);
- if (nRes != (RTP_HDR_LEN + nDataLen))
- {
- printf(" udp send error !\\n");
- return -1;
- }
- }
- else
- {
- nPlayLoadLen = RTP_MAX_PACKET_BUFF - RTP_HDR_LEN; // 每次只能发送的数据长度 除去rtp头
- gb28181_make_rtp_header(pPacker->szBuff, 0, ++pPacker->u16CSeq, (pPacker->s64CurPts/100), pPacker->u32Ssrc);
- memcpy(pPacker->szBuff + RTP_HDR_LEN, databuff, nPlayLoadLen);
- nRet = SendDataBuff(databuff, RTP_HDR_LEN + nSendSize, pPacker);
- if (nRes != (RTP_HDR_LEN + nPlayLoadLen))
- {
- printf(" udp send error !\\n");
- return -1;
- }
- nDataLen -= nPlayLoadLen;
- // databuff += (nPlayLoadLen - RTP_HDR_LEN);
- databuff += nPlayLoadLen; // 表明前面到数据已经发送出去
- databuff -= RTP_HDR_LEN; // 用来存放rtp头
- while(nDataLen > 0)
- {
- if(nDataLen <= nPlayLoadLen)
- {
- //一帧数据发送完,置mark标志位
- gb28181_make_rtp_header(databuff, mark_flag, ++pPacker->u16CSeq, (pPacker->s64CurPts/100), pPacker->u32Ssrc);
- nSendSize = nDataLen;
- }
- else
- {
- gb28181_make_rtp_header(databuff, 0, ++pPacker->u16CSeq, (pPacker->s64CurPts/100), pPacker->u32Ssrc);
- nSendSize = nPlayLoadLen;
- }
- nRet = SendDataBuff(databuff, RTP_HDR_LEN + nSendSize, pPacker);
- if (nRes != (RTP_HDR_LEN + nSendSize))
- {
- printf(" udp send error !\\n");
- return -1;
- }
- nDataLen -= nSendSize;
- databuff += nSendSize;
- //因为buffer指针已经向后移动一次rtp头长度后,
- //所以每次循环发送rtp包时,只要向前移动裸数据到长度即可,这是buffer指针实际指向到位置是
- //databuff向后重复的rtp长度的裸数据到位置上
- }
- }
- return 0;
- }
还有一个很重要的宏定义,之所以把它定义成宏,是因为会频繁调用,其功能是循环将一个字节的8位按位一个一个的压入数据,防止出现夸字节的导致字序出错问题具体实现如下,其实是挪用了vlc源码中的实现过来的,这也是读源码的一个好处,能很好的利用里面比较高级而又方便的功能代码模块
- #define PS_HDR_LEN 14
- #define SYS_HDR_LEN 18
- #define PSM_HDR_LEN 24
- #define PES_HDR_LEN 19
- #define RTP_HDR_LEN 12
- /***
- *@remark: 讲传入的数据按地位一个一个的压入数据
- *@param : buffer [in] 压入数据的buffer
- * count [in] 需要压入数据占的位数
- * bits [in] 压入的数值
- */
- #define bits_write(buffer, count, bits)\\
- {\\
- bits_buffer_s *p_buffer = (buffer);\\
- int i_count = (count);\\
- uint64_t i_bits = (bits);\\
- while( i_count > 0 )\\
- {\\
- i_count--;\\
- if( ( i_bits >> i_count )&0x01 )\\
- {\\
- p_buffer->p_data[p_buffer->i_data] |= p_buffer->i_mask;\\
- }\\
- else\\
- {\\
- p_buffer->p_data[p_buffer->i_data] &= ~p_buffer->i_mask;\\
- }\\
- p_buffer->i_mask >>= 1; /*操作完一个字节第一位后,操作第二位*/\\
- if( p_buffer->i_mask == 0 ) /*循环完一个字节的8位后,重新开始下一位*/\\
- {\\
- p_buffer->i_data++;\\
- p_buffer->i_mask = 0x80;\\
- }\\
- }\\
- }
上面忘记贴出rtp封装头了,这次补充,如果在实际不需要rtp的话,可以直接在gb28181_send_rtp_pack函数接口中屏蔽gb28181_make_rtp_header函数接口即可,当然需要注意一点问题,就是对应的buffer指针的位置就不需要移动rtp头的长度了!
- int gb28181_make_rtp_header(char *pData, int marker_flag, unsigned short cseq, long long curpts, unsigned int ssrc)
- {
- bits_buffer_s bitsBuffer;
- if (pData == NULL)
- return -1;
- bitsBuffer.i_size = RTP_HDR_LEN;
- bitsBuffer.i_data = 0;
- bitsBuffer.i_mask = 0x80;
- bitsBuffer.p_data = (unsigned char *)(pData);
- memset(bitsBuffer.p_data, 0, RTP_HDR_SIZE);
- bits_write(&bitsBuffer, 2, RTP_VERSION); /* rtp version */
- bits_write(&bitsBuffer, 1, 0); /* rtp padding */
- bits_write(&bitsBuffer, 1, 0); /* rtp extension */
- bits_write(&bitsBuffer, 4, 0); /* rtp CSRC count */
- bits_write(&bitsBuffer, 1, (marker_flag)); /* rtp marker */
- bits_write(&bitsBuffer, 7, 96); /* rtp payload type*/
- bits_write(&bitsBuffer, 16, (cseq)); /* rtp sequence */
- bits_write(&bitsBuffer, 32, (curpts)); /* rtp timestamp */
- bits_write(&bitsBuffer, 32, (ssrc)); /* rtp SSRC */
- return 0;
- }
4、封装相关心得
博主已经分步验证过rtp或者ps又或者rtp+ps封装都能正常预览。其实这个封装真心没什么理论知识说的,看看标准都知道了,只要仔细看标准一步一步的向下走,分析各个封装的各个字段,就没有什么问题了,当然在实际中也有很多小问题苦恼我很久,但是无不是因为标准没注意或者理解有误。现在我也只是简单的把自己实现的代码大致贴出来分享了,希望相关开发的少走一些弯路而已。有问题或者有更好的方法,大家可以相互交流。
以上是关于PS封装H264码流分析的主要内容,如果未能解决你的问题,请参考以下文章