RTP 协议解包为 H264裸流
一. 为什么使用 RTP 协议?
- TCP 传输流媒体数据由于其可靠性,会造成很大的网络延时和卡顿。
- UDP 传输由于其不可靠性,会导致丢帧,如果是关键帧,则会花屏一个序列的时长。
- RTP 使用了 RTP 和 RTCP 两个子协议来完成。
- RTP 使用 UDP 完成流媒体数据传输,保证其时效性。
- RTCP 也是使用 UDP,但只传输控制信息,占带宽很小,此时上层根据 RTCP 的信息,按照其重要性决定是否对丢失的流媒体数据进行重传。
- RTP 在 1025-65535 之间选择一个未使用的偶数端口号作为其端口,RTCP 则使用下一个奇数端口号作为其端口,进而组成一个 UDP 端口对,端口号 5004 和 5005 作为 RTP 和 RTCP 的默认端口号。
- RTP-Header 的流媒体特性,提供 帧边界、编码格式、序列号、时间戳等字段。
- RTP 支持网络多播,而 TCP 不支持。
二. H264 的封装
-
拆解:H264 --> 序列(SPS.PPS.IPBBP…) --> Frame(帧) --> slice(切片) --> 宏块 --> 子宏块。
序列:一段 H264 序列是指从一个 I 帧开始到下一个 I 帧前的所有帧的集合。 -
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帧)
三. 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).
会比完整包多一个字节的头信息,下文会详细解释其含义。
-
什么是 RtpHeader, NaluHeader, FuIndicator 和 FuHeader?
RtpHeader
-
结构体
/** * RtpHeader,普遍占用12个字节 * * 由于 IP 协议采用大端序,这里需要转成小端序 (Java-Byte 是大端序,java 代码中可以不用转), * 所以这里每一个字节内的各个属性跟标准 rtp 协议头刚好相反, * 并且在使用 "大于1bit" 的属性时需要将网络序转成字节序. */ typedef struct rtp_header_t { // 1byte (0) unsigned int cc : 4; /* CSRC count */ unsigned int x : 1; /* header extension flag */ unsigned int p : 1; /* padding flag */ unsigned int version : 2; /* protocol version */ // 1byte (1) unsigned int pt : 7; /* payload type */ unsigned int m : 1; /* marker bit */ // 2bytes (2,3) unsigned int seq : 16; /* sequence number */ // 4bytes (4-7) uint32_t ts