4. RTP负载类型与封装格式:H.264/H.265的NAL单元拆分、AAC的ADTS头、opus的RTP封装
这一章,我们聊聊RTP的负载。说白了,就是你要传的视频、音频数据,怎么塞进RTP包里。
我刚开始做流媒体时,总觉得封装是个体力活。后来线上出过一次问题——H.264的NAL单元拆分不对,导致解码器疯狂报错。嗯,从那以后我再也不敢小看这一步了。
你想想看,RTP本身只是个运输工具。真正有价值的是里面的载荷。视频是H.264还是H.265?音频是AAC还是Opus?每种格式都有自己的脾气,封装方式完全不同。
4.1 H.264的NAL单元与RTP封装
H.264的码流,核心就是NAL单元。每个NAL单元都有一个头,告诉解码器「我是什么类型」。
| NAL类型 | 值 | 说明 |
|---|---|---|
| 非IDR图像 | 1 | 普通帧,依赖前面的参考帧 |
| IDR图像 | 5 | 关键帧,解码器从这里开始刷新 |
| SPS | 7 | 序列参数集,图像尺寸、编码配置 |
| PPS | 8 | 图像参数集,熵编码模式等 |
RTP封装H.264时,有三种模式。我个人习惯用单NAL单元模式,简单直接。但遇到大帧时,就得用FU-A分片了。
单NAL单元模式
一个RTP包只装一个NAL单元。适合小尺寸的帧,比如SPS、PPS。
RTP头 | NAL头(1字节) | NAL载荷
FU-A分片模式
大NAL单元拆成多个RTP包。每个分片都有FU指示器和FU头。
第一个分片: RTP头 | FU指示器(0x1C) | FU头(S=1,E=0) | 分片数据
中间分片: RTP头 | FU指示器(0x1C) | FU头(S=0,E=0) | 分片数据
最后分片: RTP头 | FU指示器(0x1C) | FU头(S=0,E=1) | 分片数据
4.2 H.265的NAL单元与RTP封装
H.265和H.264的封装思路类似,但NAL头从1字节变成了2字节。多了个层ID和TID。
H.264 NAL头: [F(1) | NRI(2) | Type(5)]
H.265 NAL头: [F(1) | Type(6) | LayerID(6) | TID(3)]
H.265的RTP封装也支持单NAL和FU分片。但要注意,H.265的FU指示器是48,不是1C。
4.3 AAC的ADTS头与RTP封装
AAC的RTP封装,核心是ADTS头。每个AAC帧前面都要加这个头,解码器才能知道采样率、声道数、帧长度。
| ADTS字段 | 位数 | 说明 |
|---|---|---|
| syncword | 12 | 固定0xFFF,同步头 |
| ID | 1 | 0=MPEG-4, 1=MPEG-2 |
| profile | 2 | 0=Main, 1=LC, 2=SSR, 3=LTP |
| sampling_frequency_index | 4 | 采样率索引,查表得到实际值 |
| channel_configuration | 3 | 声道数配置 |
| frame_length | 13 | ADTS头 + AAC数据的长度 |
RTP封装AAC时,我建议用AU Header模式。每个RTP包可以装多个AAC帧,用AU Header描述每个帧的长度。
RTP头 | AU Header(2字节) | AU Header长度(2字节) | AAC帧1 | AAC帧2 | ...
4.4 Opus的RTP封装
Opus的RTP封装相对简单。它不需要像AAC那样加ADTS头,因为Opus帧本身就有自描述能力。
Opus的RTP载荷格式:
RTP头 | Opus帧1 | Opus帧2 | ...
每个Opus帧前面有一个TOC字节,描述了编码模式、带宽、帧长。
| TOC字段 | 位数 | 说明 |
|---|---|---|
| configuration | 5 | 编码模式和带宽 |
| stereo | 1 | 0=单声道, 1=立体声 |
| frame_count | 2 | 帧数编码,00=1帧, 01=2帧, 10=3帧, 11=4帧 |
4.5 三种封装对比
| 编码 | 封装复杂度 | 头开销 | 典型应用 |
|---|---|---|---|
| H.264 | 高(分片逻辑复杂) | 1-2字节/包 | 视频会议、直播 |
| H.265 | 高(2字节NAL头) | 2字节/包 | 4K/8K视频 |
| AAC | 中(ADTS头7字节) | 7字节/帧 | 音乐、广播 |
| Opus | 低(TOC字节) | 1字节/帧 | 实时通信 |
这张图展示了三种编码的RTP封装流程。从原始数据开始,根据编码类型走不同的封装路径,最后统一成RTP载荷发送出去。
好了,这一章的内容就到这里。RTP负载封装是流媒体开发的基础,理解了这些,后面讲传输优化时你才能明白为什么有些包会丢、有些包会乱序。