ONVIF之RTP学习
作者:Jesse
时间:2020/12/18
一. RTP数据帧格式及解析
1.1 RTP数据帧
wireshark截图说明:
代码说明
for (offset_sync = 0; offset_sync < ret - 5; offset_sync++) { if ((data[offset_sync] == 0x24) // $ = 0x24 && ((data[offset_sync + 1] == gs_client[index]->rtp_ipc_video_interleaved_channel) || (data[offset_sync + 1] == gs_client[index]->rtcp_ipc_video_interleaved_channel) || (data[offset_sync + 1] == gs_client[index]->rtp_ipc_audio_interleaved_channel) || (data[offset_sync + 1] == gs_client[index]->rtcp_ipc_audio_interleaved_channel)) && ((data[offset_sync + 4]) & 0x80) == 0x80) { //RTP Head 第一个字节 //find 0x24 sync head find_sync_flag = 1; break; } }
RTSP Interleaved Frame
0x24 (魔法数) |
---|
0x00 (通道0) |
0x00 0x26 (RTSP Interleaved Frame包含的数据的的大小) |
80 60 43 72 26 9f 23 94 89 34 23 ba (RTP头) |
67 64 00 1f ac 2c 6a 81 40 16 e9 b8 08 08 0a 00 00 03 00 02 00 00 03 00 65 08 (H264数据) |
0x24 0x00 0x05 0xac 0x80 0x60 0x6a 0xe0 0x00 0x00 0x86 0xc8 0x5f 0xff 0x6a 0xa8 0x7c 0x05 0x38 0xaa 0x4b 0x8f 0xa7 0x85 0x32 0x26 0xb8 0xf4 0xc7 0xcd 0x1d 0x7a 0x9a 0x8c 0x3c 0x04 0x39 0x0f 0x8c 0xa8 0x59 0xd1 0x0b 0x8b 0xf3 0x6d 0x7d 0xa6 0xfe 0xa8 0x48 0x89 0x75 0x1d 0x51 0xa9 0x79 0x2d 0xf0 0xc8 0xb1 0x83 0xd5 0xf4 0x19 0x85 0x6d 0x5c 0xf4 0x04 0x9a 0xc5 0x35 0xb4 0x19 0x8b 0x91 0x6b 0xc6 0x28
1.2 RTP Header解析
格式说明
V:RTP协议的版本号,占2位,当前协议版本号为2 P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。 X:扩展标志,占1位,如果X=1,则在RTP报头后跟有一个扩展报头 CC:CSRC计数器,占4位,指示CSRC 标识符的个数 M: 标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。 PT: 有效荷载类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等,在流媒体中大部分是用来区分音频流和视频流的,这样便于客户端进行解析。 序列号:占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。这个字段当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包。同时出现网络抖动的情况可以用来对数据进行重新排序,序列号的初始值是随机的,同时音频包和视频包的sequence是分别记数的。 时戳(Timestamp):占32位,必须使用90 kHz 时钟频率。时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。 同步信源(SSRC)标识符:占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。 特约信源(CSRC)标识符:每个CSRC标识符占32位,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中的所有特约信源。 注:基本的RTP说明并不定义任何头扩展本身,如果遇到X=1,需要特殊处理
二. H265 RTP数据格式分析
-
组合封包例子
前2个字节是PayloadHdr,内容是:60 01,其格式如下:
所以要获取1-6(从0开始数)位上的内容,结果是48,说明是组合封包模式
依次类推,第二个NALU的长度是00 22,也就是34,然后后面的34个字节是第二个NALU的内容
-
分片封包例子
下面是分片封包的例子
前2个字节是62 01,根据格式获取到type的值是49,所以可知是分片封包类型
接下来的1个字节是FU header,值是93
1001 0011
其中后6位是NALU type,可以知道NALU type是19,属于IDR类型。而第一位是1,所以它是第一个分片
看一下最后一个分片的格式:
第3字节是53,二进制格式为:0101 0011, NALU type是19, 第二位是1,所以是最后一个分片
-
s
-
鹿死谁手所所
三. RTP相关专业描述
3.1 为什么使用 RTP 协议?
TCP 传输流媒体数据由于其可靠性,会造成很大的网络延时和卡顿。 UDP 传输由于其不可靠性,会导致丢帧,如果是关键帧,则会花屏一个序列的时长。 RTP 使用了 RTP 和 RTCP 两个子协议来完成。 RTP 使用 UDP 完成流媒体数据传输,保证其时效性。 RTCP 也是使用 UDP,但只传输控制信息,占带宽很小,此时上层根据 RTCP 的信息,按照其重要性决定是否对丢失的流媒体数据进行重传。 RTP 在 1025-65535 之间选择一个未使用的偶数端口号作为其端口,RTCP 则使用下一个奇数端口号作为其端口,进而组成一个 UDP 端口对,端口号 5004 和 5005 作为 RTP 和 RTCP 的默认端口号。 RTP-Header 的流媒体特性,提供 帧边界、编码格式、序列号、时间戳等字段。 RTP 支持网络多播,而 TCP 不支持。
3.2 H264 的封装
拆解:H264 --> 序列(SPS.PPS.IPBBP…) --> Frame(帧) --> slice(切片) --> 宏块 --> 子宏块。 序列:一段 H264 序列是指从一个 I 帧开始到下一个 I 帧前的所有帧的集合。 kk NALU:H264 被封装在一个个 NALU(Network Abstraction Layer Unit)中进行传输。 NALU 以 [00 00 00 01] 为开始码,之后是 NaluHeader,再之后是 NaluPayload。 eg: [00 00 00 01 67 2F A4 1E 23 59 1E 42 … ]. 常见的帧头数据: 00 00 00 01 67(SPS) 00 00 00 01 68(PPS) 00 00 00 01 65(IDR 帧) 00 00 00 01 61(P帧)
3.3 RTP 解包概念解析
RTP 封包时会将 [00 00 00 01] 的开始码去除。(注:在收到 RTP 包时需要在每个 NALU 头的位置拼接此开始码) eg:[RTP-Header] + [ 67 2F A4 1E 23 59 1E 42 … ]. NALU 封包策略 如果 NALU 长度比较小,则可以将其完整地装在一个 RTP 包中。 此时,RTP 的结构为 RtpHeader + RtpPayload(NaluHeader + NaluPayload). 如果 NALU 长度超过 MTU(最大传输单元) 时,则需要对 NALU 进行分片封包。 此时,RTP 的结构为 RtpHeader + RtpPayload(FuIndicator + FuHeader + NaluPayload). 会比完整包多一个字节的头信息,下文会详细解释其含义。
3.4 H264参数集
3.4.1 H.264的参数集:序列参数集(Sequence parameter set)
序列参数集:包括一个图像序列的所有信息,即两个IDR图像间的所有图像信息;
3.4.2 H.264的参数集:图像参数集(Picture parameter set)
图像参数集:包括一个图像的所有分片的所有相关信息,包括图像类型、序列号等,解码时某些序列号的丢失可用来检验信息包的丢失与否
3.5 H264的NALU的结构
每个NAL单元包括一个原始字节序列负荷(RBSP, Raw Byte Sequence Payload)、一组对应于视频编码的NAL头信息 H264在网络传输的NALU的结构是:NAL头+RBSP,实际传输中的数据流如图所示:(其中NAL头占一个字节,其低5个bit位表示NAL type,见3.5.2表) SODB:(String of Data Bits)最原始的编码数据,无任何附加数据。 RBSP:在SODB的基础上增加了rbsp_stop_ont_bit(bit值为1)并用0按字节补位对齐。 EBSP:(Encapsulation Byte Sequence Packets)在RBSP的基础上增加了防止伪起始码字节(0x03)。 RBSP的基本结构是在原始编码数据的后面添加了结尾比特。一个bit"1",若干比特"0",以便字节对齐。
3.5.1 NAL头
+---------------+ |0|1|2|3|4|5|6|7| +-+-+-+-+-+-+-+-+ |F|NRI| Type | +---------------+
3.5.2 RBSP结构
3.5.3 H.264中的NAL单元类型 nal_unit_type
nal_unit_type | NAL 单元和 RBSP 语法结构的内容 |
---|---|
0 | 未指定 |
1 | 一个非IDR图像的编码条带 slice_layer_without_partitioning_rbsp( ) |
2 | 编码条带数据分割块A slice_data_partition_a_layer_rbsp( ) |
3 | 编码条带数据分割块B slice_data_partition_b_layer_rbsp( ) |
4 | 编码条带数据分割块C slice_data_partition_c_layer_rbsp( ) |
5 | IDR图像的编码条带 slice_layer_without_partitioning_rbsp( ) |
6 | 辅助增强信息 (SEI) sei_rbsp( ) |
7 | 序列参数集 seq_parameter_set_rbsp( ) |
8 | 图像参数集 pic_parameter_set_rbsp( ) |
9 | 访问单元分隔符 access_unit_delimiter_rbsp( ) |
10 | 序列结尾 end_of_seq_rbsp( ) |
11 | 流结尾 end_of_stream_rbsp( ) |
12 | 填充数据 filler_data_rbsp( ) |
13 | 序列参数集扩展 seq_parameter_set_extension_rbsp( ) |
14...18 | 保留 |
19 | 未分割的辅助编码图像的编码条带 slice_layer_without_partitioning_rbsp( ) |
20...23 | 保留 |
24...31 | 未指定 |
3.6 H264帧类型判断
在实际的H264数据帧中,往往帧NAL type前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧,然后是P帧… 在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。因此我们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧…… 如下图:
3.6.1 SPS部分
NALU起始符为00 00 00 01。 紧跟着起始符的是是Start code,如 00 00 00 01 0x67(start coe)
3.6.2 Sequence parameter set RBSP syntax
pic_parameter_set_rbsp( ) { | C | Descriptor |
---|---|---|
profile_idc | 0 | u(8) |
constraint_set0_flag | 0 | u(1) |
constraint_set1_flag | 0 | u(1) |
constraint_set2_flag | 0 | u(1) |
constraint_set3_flag | 0 | u(1) |
reserved_zero_4bits /* equal to 0 */ | 0 | u(4) |
level_idc | 0 | u(8) |
seq_parameter_set_id | 0 | ue(v) |
if( profile_idc = = 100 | | profile_idc = = 110 | | profile_idc = = 122 | | profile_idc = = 144 ) { | ||
chroma_format_idc | 0 | ue(v) |
if( chroma_format_idc = = 3 ) | ||
residual_colour_transform_flag | 0 | u(1) |
bit_depth_luma_minus8 | 0 | ue(v) |
bit_depth_chroma_minus8 | 0 | ue(v) |
qpprime_y_zero_transform_bypass_flag | 0 | u(1) |
seq_scaling_matrix_present_flag | 0 | u(1) |
if( seq_scaling_matrix_present_flag ) | ||
for( i = 0; i < 8; i++ ) { | ||
seq_scaling_list_present_flag[ i] | 0 | u(1) |
if( seq_scaling_list_present_flag[ i ] ) | ||
if( i < 6 ) | ||
scaling_list( ScalingList4x4[ i ], 16, UseDefaultScalingMatrix4x4Flag[ i ]) | 0 | |
else | ||
scaling_list( ScalingList8x8[ i – 6 ], 64, UseDefaultScalingMatrix8x8Flag[ i – 6 ] ) | 0 | |
} | ||
} | ||
log2_max_frame_num_minus4 | 0 | ue(v) |
pic_order_cnt_type | 0 | ue(v) |
if( pic_order_cnt_type = = 0 ) | ||
log2_max_pic_order_cnt_lsb_minus4 | 0 | ue(v) |
else if( pic_order_cnt_type = = 1 ) { | ||
delta_pic_order_always_zero_flag | 0 | u(1) |
offset_for_non_ref_pic | 0 | se(v) |
offset_for_top_to_bottom_field | 0 | se(v) |
num_ref_frames_in_pic_order_cnt_cycle | 0 | ue(v) |
for( i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ ) | ||
offset_for_ref_frame[ i ] | 0 | se(v) |
} | ||
num_ref_frames | 0 | ue(v) |
gaps_in_frame_num_value_allowed_flag | 0 | u(1) |
pic_width_in_mbs_minus1 | 0 | ue(v) |
pic_height_in_map_units_minus1 | 0 | ue(v) |
frame_mbs_only_flag | 0 | u(1) |
if( !frame_mbs_only_flag ) | ||
mb_adaptive_frame_field_flag | 0 | u(1) |
direct_8x8_inference_flag | 0 | u(1) |
frame_cropping_flag | 0 | u(1) |
if( frame_cropping_flag ) { | ||
frame_crop_left_offset | 0 | ue(v) |
frame_crop_right_offset | 0 | ue(v) |
frame_crop_top_offset | 0 | ue(v) |
frame_crop_bottom_offset | 0 | ue(v) |
} | ||
vui_parameters_present_flag | 0 | u(1) |
if( vui_parameters_present_flag ) | ||
vui_parameters( ) | 0 | |
rbsp_trailing_bits( ) | 0 | |
} |
0x67的二进制是0110 0111, 以0x67为例分析如下(u: 0110 0111): 1. forbidden_zero_bit 是禁止位,应该是第一位即u(1) = 0,1为语法有错误 2. nal_ref_idc是参考级别,代表被其它帧参考情况,u(2) = 11 = 3(Nal_ref_idc表示NAL的优先级,0-3, 取值越大,表示当前NAL越重要,如果当前NAL是属于参考帧的片,或是序列参数集,或是图像参数集这些重要单位时,取值必须>0) 3. nal_unit_type是该帧的类型,为剩下的5位,u(5) = 0 0111 = 7 目前类型有: H264定义的类型 values for nal_unit_type typedef enum { NALU_TYPE_SLICE = 1, NALU_TYPE_DPA = 2, NALU_TYPE_DPB = 3, NALU_TYPE_DPC = 4, NALU_TYPE_IDR = 5, NALU_TYPE_SEI = 6, NALU_TYPE_SPS = 7, NALU_TYPE_PPS = 8, NALU_TYPE_AUD = 9, NALU_TYPE_EOSEQ = 10, NALU_TYPE_EOSTREAM = 11, NALU_TYPE_FILL = 12, (#)if (MVC_EXTENSION_ENABLE) NALU_TYPE_PREFIX = 14, NALU_TYPE_SUB_SPS = 15, NALU_TYPE_SLC_EXT = 20, NALU_TYPE_VDRD = 24 // View and Dependency Representation Delimiter NAL Unit (#)endif } NaluType; 可以看出7是NALU_TYPE_SPS 即sequence parameter sets
start code后面紧跟着的是profile_idc, 如00 00 00 01 0x67 0x64(profile_idc)
0x64转化为十进制则是100, H264定义如下: 66 Baseline 77 Main 88 Extended 100 High (FRExt) 110 High 10 (FRExt) 122 High 4:2:2 (FRExt) 144 High 4:4:4 (FRExt) 所以0x64是100 表示high_profile, High (FRExt)
参考: 00 00 00 01 67 42 00 28 E9 00 A0 0B 77 FE 00 02 00 03 C4 80 00 00 03 00 80 00 00 1A 4D 88 10 94 00 00 00 01 00 00 00 01为NALu头,其余码流由十六进制转为二进制 67 0110 0111 42 0100 0010 00 0000 0000 28 0010 1000 E9 1110 1001 00 0000 0000 A0 1010 0000 0B 0000 1011 77 0111 01/11 …… 94 1001 01//00 说明: “/”后的码流要对照标准中AnnexE的句法表,是VUI(VideoUsabilityInformation?)的内容, 不懂,不写了,只写SPS部分先。 “//”后面两个0是补齐用的。 NAL层句法:码,值 forbidden_zero_bit(f(1)):0,0 nal_ref_idc(u(2)):11, 3 nal_unit_type(u(5)): 0 0111, 7, SPS SPS序列参数集的句法:码,值 profile_idc(u(8)) = 0100 0010,66 , baseline profile基础档次 constraint_set0_flag(u(1)):0,0 constraint_set1_flag(u(1)):0,0 constraint_set2_flag(u(1)):0,0 constraint_set3_flag(u(1)):0,0 reserved_zero_4bits(u(4)):0000,0 level_idc(u(8)) :00101000,40 ,级别 seq_parameter_set_id(ue(v)): 1, 0 log2_max_frame_num_minus4(ue(v): 1, 0 MaxFrameNum = 2^(0+4) = 16 pic_order_cnt_type(ue(v)):1, 0 log2_max_pic_order_cnt_lsb_minus4(ue(v)):010 ,1 MaxPicOrderCntLsb = 2^(1+4) = 32 num_ref_frames(ue(v)):010, 1 gaps_in_frame_num_value_allowed_flag(u(1)):0,0 pic_width_in_mbs_minus1(ue(v)): 0000001010000, 2^6-1+16 = 79 PicWidthInMbs = pic_width_in_mbs_minus1 + 1 = 80 pic_height_in_map_units_minus1(ue(v)): 00000101101 ,2^5-1+13 = 44 PicHeightInMapUnits = pic_height_in_map_units_minus1 + 1 =45 frame_mbs_only_flag(u(1)):1,1 direct_8x8_inference_flag(u(1)): 1,1 frame_cropping_flag(u(1)):0,0 vui_parameters_present_flag(u(1)):1 ,1 这个参数为1,说明下面的句法存在 vui_parameters( ) aspect_ratio_info_present_flag(u(1)):1 其中: pic_width_in_mbs_minus1 : 79 pic_height_in_map_units_minus1 : 44 (79+1)x16=1280 (44+1)x16=720
3.6.2 如何判断帧类型(是图像参考帧还是I、P帧等)
最上面图的码流对应的数据来层层分析,以00 00 00 01分割之后的下一个字节就是NALU类型,将其转为二进制数据后,解读顺序为从左往右算,如下: (1)第1位禁止位,值为1表示语法出错 (2)第2~3位为参考级别 (3)第4~8为是nal单元类型
例如上面00000001后有67,68以及65
其中0x67的二进制码为: 0110 0111 4-8为00111,转为十进制7,参考第二幅图:7对应序列参数集SPS
其中0x68的二进制码为: 0110 1000 4-8为01000,转为十进制8,参考第二幅图:8对应图像参数集PPS
其中0x65的二进制码为: 0110 0101 4-8为00101,转为十进制5,参考第二幅图:5对应IDR图像中的片(I帧)
3.7 SPS、PPS和SE特殊说明
SPS和PPS是用来初始化解码器的,没这些数据,视频数据是无法解析出来的。另外如果我们分析单独的H264文件,可以发现有的文件每个IDR帧前面都有PPS和SPS,有的只是开头才有。针对SPS和PPS,一般来说: 1)、如果是在直播的话,每个IDR帧前面都应该加上SPS和PPS,因为有的观众会中途进来观看。 2)、如果是本地稳定文件,可以在开头加上SPS和PPS,或者都加上,这个根据具体需要来。 另外说明下SEI,有的H264文件有SEI,有的则没有,这说明SEI对文件的播放并无太大影响。 SEI(Supplemental Enhancement Information):辅助增强信息。这里面可以存放一些影片简介,版权信息或者作者自己添加的一些信息。