RTP载荷H264视频流
- H264 RTP包解析
-
- 预备
视频:
由一副副连续的图像构成,由于数据量比较大,因此为了节省带宽以及存储,就需要进行必要的压缩与解压缩,也就是编解码。
h264裸码流:
对一个图像或者一个视频序列进行压缩,即产生码流,采用H264编码后形成的码流就是h264裸码流。
码流传输:
发送端将H264裸码流打包后进行网络传输,接收端接收后进行组包还原裸码流,然后可以再进行存储,转发,或者播放等等相关的处理。
存储转发可以直接使用裸码流,播放则需要进行解码和显示处理
解码显示:
一般会解成YUV数据,然后交给显示相关模块做处理,如使用openGL或者D3D等进行渲染(采用3d的方式来显示2d的图像称为渲染)
解码又分软件解码和硬件解码,
软解一般ffmpeg
硬解则各不同,由各硬件厂商开放sdk来处理,如:hisi,intel media sdk,nvida gpu,apple ios videotoolbox等。
-
- h264码流传输
类似发送网页文本数据有http一样,视频数据在网络上传输也有专门的网络协议来支持,如:rtsp,rtmp等
由于码流是一帧一帧的图片数据,所以传输的时候也是一帧帧来传输的,因此这里就会涉及到各种类型的帧处理了
I 帧: 参考帧或者关键帧,可以理解为是一帧完整画面,解码时只需要本帧数据就解码成一幅完整的图片,数据量比较大。
P帧: 差别帧,只有与前面的帧或I帧的差别数据,需要依赖前面已编码图象作为参考图象,才能解码成一幅完整的图片,数据量小。
B帧: 双向差别帧,也就是B帧记录的是本帧与前后帧的差别,需要依赖前后帧和I帧才能解码出一幅完整的图片,数据量小。
由于i帧比较大,已经超出mtu最大1500,所以需要拆包分片传输,这里说的拆包发送不是指发送超过1500的数据包时tcp的分段传输或者upd的ip分片传输,而是指rtp协议本身对264的拆包。
rtp打包后将数据交给tcp或者upd进行传输的时候就已经控制在1500以内,这样可以提高传输效率,避免一个I帧万一丢失就会造成花屏或者重传造成延时卡顿等等问题。
(顺便提一句,rtmp打包就比较简单,由于是基于tcp的协议,大包直接交给tcp去做分段传输,rtmp通过设置合适的trunk size去发送一帧帧数据)
既然要进行拆包发送与接收,就少不了需要相关的包结构以及打包组包了,继续。。。。。。
-
- H264在网络传输的单元:NALU
NALU结构:NALU header(1byte) + NALU payload
header 部分可以判断类型等信息,从右往左5个bit位
SPS: 0x67 header & 0x1F = 7 |
I Frame: 0x65 header & 0x1F = 5 |
PPS: 0x68 header & 0x1F = 8 |
P Frame: 0x41 header & 0x1F = 1 |
SEI: 0x66 header & 0x1F = 6 |
|
payload 部分可以简单理解为 编码后的264帧数据
详细可以去查阅 h264 NALU 语法结构
-
- h264的RTP打包
下面是 RFC 3550 中规定的 RTP 头的结构.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| contributing source (CSRC) identifiers |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
负载类型 Payload type (PT): 7 bits
*** Sequence number (SN): 16 bits
时间戳 Timestamp: 32 bits
rtp打包h264,包含了三种类型的包:
一个rtp包携带了一帧数据(single)
多个rtp包携带了一帧数据(FU-A)
一个rtp包携带了多帧数据(STAP-A)
在实际应用中绝大部分采用的是前两种方式,对方式1常见的是对nalu的sps,pps进行打包,因为sps和pps数据量很小,一个rtp包足以携带,一般采用 sps,pps分别由一个rtp包携带的方式。对IDR数据及其他类型数据通常是采用方式2,因为视频帧数据通常比较大,一个rtp包不足以携带,分成多个rtp包携带,分包携带后对最后一个rtp包的mark字段是要设置为true的。包格式定义详细见 rfc3984
H.264 Payload 格式定义了三种不同的基本的负载(Payload)结构. 接收端可通过 RTP Payload 的第一个字节来识别它们. 这一个字节类似 NALU 头的格式, 而这个头结构的 NAL 单元类型字段则指出了代表的是哪一种结构。这个字节的结构如下, 可以看出它和 H.264 的 NALU 头结构是一样的:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
字段 Type: 这个 RTP payload 中 NAL 单元的类型.
这个字段和 H.264 中类型字段的区别是, 当 type 的值为 24 ~ 31 表示这是一个特别格式的 NAL 单元, 而 H.264 中, 只取 1~23 是有效的值.
一句话: h264仅用1-23,24以后的用在RTP H264负载类型头中
不同类型的NALU的重要性指示如下表所示:
24 STAP-A 单一时间的组合包
25 STAP-B 单一时间的组合包
26 MTAP16 多个时间的组合包
27 MTAP24 多个时间的组合包
28 FU-A 分片的单元
29 FU-B 分片的单元
30-31 没有定义
可能的结构类型分别有:
1. 单一 NAL 单元模式
即一个 RTP 包仅由一个完整的 NALU 组成. 这种情况下 RTP NAL 头类型字段和原始的 H.264的 NALU 头类型字段是一样的.
2. 组合封包模式
即可能是由多个 NAL 单元组成一个 RTP 包. 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24.
那么这里的类型值分别是 24, 25, 26 以及 27.
3. 分片封包模式
用于把一个 NALU 单元封装成多个 RTP 包. 存在两种类型 FU-A 和 FU-B. 类型值分别是 28 和 29.
1. 单NALU: P帧或者B帧比较小的包,直接将NALU打包成RTP包进行传输 RTP header(12bytes) + NALU header (1byte) + NALU payload
2. 多NALU: 特别小的包几个NALU放在一个RTP包中
3. FUs(Fragment Units): I帧长度超过MTU的,就必须要拆包组成RTP包了,有FU-A,FU-B
RTP header (12bytes)+ FU Indicator (1byte) + FU header(1 byte) + NALU payload
看到这里会不禁思考,
1. NALU头不见了,如何判断类型?实际上NALU头被分散填充到FU indicator和FU header里面了
bit位按照从左到右编号0-7来算,nalu头中0-2前三个bit放在FU indicator的0-2前三个bit中,后3-7五个bit放入FU header的后3-7五个中
//NALU header = (FU indicator & 0xe0) | (FU header & 0x1F) 取FU indicator前三和FU header后五
headerStart[1] = (headerStart[0]&0xE0)|(headerStart[1]&0x1F);
因此查看I帧p帧类型,遇到FU分片的,直接看第二个字节,即Fu header后五位,这个跟直接看NALU头并无差异,一般有:0x85,0x05,0x45等等
2. 多个RTP包如何还原组合成回一个完整的I帧? 在FU header中有标记为判断
照旧从左到右,Fu header前两个bit表示start和end标记,start为1表示一个i帧分片开始,end为1表示一个i帧分片结束
3. 如何查看是一个I帧分片开始?
看第一个字节FU Indicator,照旧从左到右,Fu Indicator前三个bit是NALU头的前三个bit,后五位为类型FU-A:28(11100),FU-B:29(11101)
RTP抓包看下来整个字节是0x7c开头
如果不是分片,第一字节就是NALU头,如:0x67,0x68,0x41等
-
- 抓包分析如下图
--->RTP包中接收的264包是不含有0x00,0x00,0x00,0x01头的,这部分是rtp接收以后,另外再加上去的,解码的时候再做判断的。
1. SPS
2. I帧分片开始,第一个字节FU Indicator,0x7c, 后五位11100,28,FU-A
第二个字节FU Header,0x85,前两个bit start位1,end位0 表示 分片开始,后五个bit值5,I帧
I帧分片,0x7c开头,第二个字节0x05, FU Header start和end位 0,后五个bit值5,I帧
I帧分片结束,7c开头,第二个字节0x45,FU Headr,start 0,end1,后五个bit值5,I帧, Mark标记一帧结束
3. p帧,第一个字节:0x41
4. P帧分片开始:第一个字节FU Indicator,0x7c, 后五位11100,28,FU-A
第二个字节FU Header,0x81, 前两个bit start位1,end位0 表示 分片开始,后五个bit 值是1,p帧
P帧分片结束:0x5c开头,第二个字节0x41,FU Headr,start 0,end1,后五个bit值1,P帧, Mark标记一帧结束
-
- SDP 参数
下面描述了如何在 SDP 中表示一个 H.264 流:
. "m=" 行中的媒体名必须是 "video"
. "a=rtpmap" 行中的编码名称必须是 "H264".
. "a=rtpmap" 行中的时钟频率必须是 90000.
. 其他参数都包括在 "a=fmtp" 行中.
如:
m=video 49170 RTP/AVP 98
a=rtpmap:98 H264/90000
a=fmtp:98 profile-level-id=42A01E; sprop-parameter-sets=Z0IACpZTBYmI,aMljiA==
下面介绍一些常用的参数.
表示支持的封包模式.
当 packetization-mode 的值为 0 时或不存在时, 必须使用单一 NALU 单元模式.
当 packetization-mode 的值为 1 时必须使用非交错(non-interleaved)封包模式.
当 packetization-mode 的值为 2 时必须使用交错(interleaved)封包模式.
这个参数不可以取其他的值.
这个参数可以用于传输 H.264 的序列参数集和图像参数 NAL 单元. 这个参数的值采用 Base64 进行编码. 不同的参数集间用","号隔开.
这个参数用于指示 H.264 流的 profile 类型和级别. 由 Base16(十六进制) 表示的 3 个字节. 第一个字节表示 H.264 的 Profile 类型, 第
三个字节表示 H.264 的 Profile 级别:
这个参数的值是一个整型, 指出了每一秒最大的宏块处理速度.
参考:
https://www.cnblogs.com/leehm/p/11009504.html
https://blog.****.net/mo4776/article/details/78391969