代码如下
const int RTP_HEADLEN = 12;
bool UnpackRTPH264(void *bufIn, int len, void **pBufOut, int *pOutLen)
{
*pOutLen = 0;
if (len < RTP_HEADLEN)
{
return false;
}
unsigned char *src = (unsigned char *)bufIn + RTP_HEADLEN;
unsigned char head1 = *src; // 获取第一个字节
unsigned char head2 = *(src + 1); // 获取第二个字节
unsigned char nal = head1 & 0x1f; // 获取FU indicator的类型域,
unsigned char flag = head2 & 0xe0; // 获取FU header的前三位,判断当前是分包的开始、中间或结束
unsigned char nal_fua = (head1 & 0xe0) | (head2 & 0x1f); // FU_A nal,恢复包第一个字节
bool bFinishFrame = false;
if (nal == 0x1c) // 判断NAL的类型为0x1c=28,说明是FU-A分片
{ // fu-a
if (flag == 0x80) // 开始
{
*pBufOut = src - 3;
*((int *)(*pBufOut)) = 0x01000000; // 大模式会有问题
*((char *)(*pBufOut) + 4) = nal_fua;
*pOutLen = len - RTP_HEADLEN + 3;
}
else if (flag == 0x40) // 结束
{
*pBufOut = src + 2;
*pOutLen = len - RTP_HEADLEN - 2;
}
else // 中间
{
*pBufOut = src + 2;
*pOutLen = len - RTP_HEADLEN - 2;
}
}
else // 单包数据
{
*pBufOut = src - 4;
*((int *)(*pBufOut)) = 0x01000000; // 大模式会有问题
*pOutLen = len - RTP_HEADLEN + 4;
}
unsigned char *bufTmp = (unsigned char *)bufIn;
if (bufTmp[1] & 0x80)
{
bFinishFrame = true; // rtp mark
}
else
{
bFinishFrame = false;
}
return bFinishFrame;
}
1.单个NAL包单元
12字节的RTP头后面的就是音视频数据,比较简单。一个封装单个NAL单元包到RTP的NAL单元流的RTP序号必须符合NAL单元的解码顺序。
对于 NALU 的长度小于 MTU 大小的包, 一般采用单一 NAL 单元模式.
对于一个原始的 H.264 NALU 单元常由[Start Code] [NALU Header] [NALU Payload]三部分组成, 其中 Start Code 用于标示这是一个
NALU 单元的开始, 必须是 "00 00 00 01" 或 "00 00 01", NALU 头仅一个字节, 其后都是 NALU 单元内容.
打包时去除 "00 00 01" 或 "00 00 00 01" 的开始码, 把其他数据封包的 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| type | |
+-+-+-+-+-+-+-+-+ |
| |
| Bytes 2..n of a Single NAL unit |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
如有一个 H.264 的 NALU 是这样的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]
这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 内容.
封装成 RTP 包将如下:
[ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F ]
即只要去掉 4 个字节的开始码就可以了.
2、组合封包模式
当 NALU 的长度特别小时, 可以把几个 NALU 单元封在一个 RTP 包中.
3、FU-A的分片格式
数据比较大的H264视频包,被RTP分片发送。12字节的RTP头后面跟随的就是FU-A分片:
而当 NALU 的长度超过 MTU 时, 就必须对 NALU 单元进行分片封包. 也称为 Fragmentation Units (FUs).
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 14. RTP payload format for FU-A
Figure 14. RTP payload format for FU-A
FU indicator有以下格式:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
FU指示字节的类型域Type=28表示FU-A。。NRI域的值必须根据分片NAL单元的NRI域的值设置。
FU header的格式如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S: 1 bit 当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。
E: 1 bit 当设置成1, 结束位指示分片NAL单元的结束,即, 荷载的最后字节也是分片NAL单元的最后一个字节。
当跟随的FU荷载不是分片NAL单元的最后分片,结束位设置为0。
R: 1 bit
保留位必须设置为0,接收者必须忽略该位。
Type: 5 bits
NAL单元荷载类型
要注意地方:
拆包和解包
拆包:当编码器在编码时需要将原有一个NAL按照FU-A进行分片,原有的NAL的单元头与分片后的FU-A的单元头有如下关系:
原始的NAL头的前三位为FU indicator的前三位,原始的NAL头的后五位为FU header的后五位,
FU indicator与FU header的剩余位数根据实际情况决定。
解包:当接收端收到FU-A的分片数据,需要将所有的分片包组合还原成原始的NAl包时,FU-A的单元头与还原后的NAL的关系如下:
还原后的NAL头的八位是由FU indicator的前三位加FU header的后五位组成,即:
nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f)