4. 编解码器入门
H.264,也叫AVC,是现在流媒体领域最通用的编码格式。没有之一。
我做了这么多年流媒体,见过各种编码格式起起落落,但H.264始终是那个“万金油”。RTSP推流里,90%以上的场景都在用它。为什么?兼容性好,压缩率高,而且专利池虽然复杂,但终端支持度极高。
这一章,我们就把H.264的核心掰开揉碎了讲。不扯玄乎的理论,只讲你在RTSP推流中真正用得上的东西。
4.1 H.264编码标准概览
H.264是ITU-T和ISO/IEC联合制定的视频编码标准。它的核心思路很简单:用更少的比特,传更好的画质。
相比上一代MPEG-4 Part 2,H.264在同等画质下能节省30%-50%的码率。这个提升是革命性的。我记得2010年左右,很多监控系统还在用MPEG-4,码率动不动就8Mbps,换成H.264后直接降到3Mbps,画质还更好。
H.264的核心技术亮点:
- 帧内预测:利用空间冗余,减少单帧内的数据量
- 帧间预测:利用时间冗余,通过运动估计压缩连续帧
- 变换编码:4x4整数DCT变换,减少块效应
- 熵编码:CAVLC和CABAC两种模式,CABAC压缩率更高
- 去块效应滤波器:平滑块边界,提升主观画质
说白了,H.264就是一套“找规律”的算法。它把视频拆成一个个小块,然后找出这些块之间的相似性,只记录差异部分。你想想看,一段静止的监控画面,背景几乎不变,H.264只需要传一次背景,后面只传变化的部分——码率自然就下来了。
4.2 NAL单元结构
NAL,全称Network Abstraction Layer,网络抽象层。这是H.264设计里最巧妙的部分之一。
为什么要有NAL?因为H.264的设计者从一开始就考虑到了网络传输。他们把编码后的数据打包成一个个NAL单元,每个单元都可以独立在网络中传输。RTSP推流时,RTP包里面装的就是这些NAL单元。
4.2.1 NAL单元头部
每个NAL单元都有一个1字节的头部,结构如下:
+---------------+---------------+
|0|1|2|3|4|5|6|7|
+---+-----------+
|F| NRI | Type |
+---+-----------+
- F (forbidden_zero_bit):1位,必须为0。如果为1,表示这个NAL单元有错误。
- NRI (nal_ref_idc):2位,表示这个NAL单元的优先级。00表示不重要,11表示最重要。
- Type (nal_unit_type):5位,表示NAL单元的类型。
我的经验:在RTSP推流中,如果网络状况不好,我会根据NRI值做丢包策略。NRI为00的SEI或非参考帧的Slice,可以优先丢弃。NRI为11的SPS、PPS和IDR帧,必须保证送达。这个策略我在一个4G弱网环境下用过,效果很明显。
4.2.2 常见的NAL单元类型
| Type值 | NAL类型 | 说明 | 重要性 |
|---|---|---|---|
| 1 | 非IDR的Slice | 普通视频帧数据 | 中 |
| 5 | IDR的Slice | 关键帧,解码器从这里开始刷新 | 高 |
| 6 | SEI | 补充增强信息,如时间戳、字幕 | 低 |
| 7 | SPS | 序列参数集,包含分辨率、帧率等 | 最高 |
| 8 | PPS | 图像参数集,包含编码模式、熵编码类型 | 最高 |
这里我要强调一下SPS和PPS。这两个NAL单元是解码器的“说明书”。没有它们,解码器根本不知道该怎么解码。在RTSP推流中,SPS和PPS通常通过SDP描述信息传输,或者在RTP流的开头先发送。
我曾经踩过的坑:有一次做RTSP推流,客户端一直黑屏,查了半天发现是SPS和PPS只在SDP里传了一次,但客户端在连接过程中重新协商了参数,导致解码器丢失了SPS/PPS。后来我改成在RTP流中定期插入SPS和PPS,问题就解决了。建议你在推流时,每隔几秒就重复发送一次SPS和PPS,确保新加入的客户端能正常解码。
4.3 Profile与Level选择
Profile和Level是H.264的两个重要参数。它们决定了编码器的能力和复杂度。
4.3.1 Profile(档次)
Profile定义了编码器支持的工具集。档次越高,工具越多,压缩率也越高,但解码器的负担也越重。
| Profile | 特点 | 适用场景 |
|---|---|---|
| Baseline | 支持I/P帧,仅CAVLC,无B帧 | 视频会议、移动设备、低延迟场景 |
| Main | 支持I/P/B帧,CAVLC和CABAC | 标准广播、存储 |
| High | 支持8x8变换、自定义量化矩阵 | 高清视频、蓝光、流媒体 |
我个人习惯在RTSP推流中使用High Profile。为什么?因为压缩率更高。在同样的码率下,High Profile的画质明显优于Baseline。但要注意,一些老旧的嵌入式设备可能不支持High Profile解码。
4.3.2 Level(级别)
Level定义了编码器的性能上限,包括分辨率、帧率、码率等。
| Level | 最大分辨率 | 最大帧率 | 最大码率 |
|---|---|---|---|
| 3.0 | 720x576 | 30fps | 10Mbps |
| 3.1 | 1280x720 | 30fps | 14Mbps |
| 4.0 | 1920x1080 | 30fps | 20Mbps |
| 4.1 | 1920x1080 | 60fps | 50Mbps |
| 5.0 | 3840x2160 | 30fps | 135Mbps |
我的建议:选择Level时,不要只看分辨率。还要考虑帧率和码率。比如你推1080p 60fps的视频,Level至少要到4.1。如果码率超过20Mbps,也要相应提高Level。我一般会留20%的余量,避免编码器在极限情况下出现异常。
4.4 H.264在RTSP中的应用
RTSP推流中,H.264的传输主要涉及两个层面:SDP描述和RTP封装。
4.4.1 SDP中的H.264描述
在RTSP的DESCRIBE响应中,SDP会描述H.264的编码参数。一个典型的SDP片段如下:
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=64001E;sprop-parameter-sets=Z2QAHqw2AoAB9wEAAAMA,KOIMgA==
- packetization-mode:1表示使用非交错模式,这是RTSP推流最常用的模式。
- profile-level-id:16进制编码的Profile和Level。64001E表示High Profile,Level 3.0。
- sprop-parameter-sets:Base64编码的SPS和PPS,用逗号分隔。
4.4.2 RTP封装
H.264的RTP封装遵循RFC 3984标准。主要有三种模式:
- 单一NAL单元模式:一个RTP包只装一个NAL单元。适用于小尺寸的NAL单元。
- 聚合模式:多个小NAL单元合并到一个RTP包中。适用于SEI、SPS、PPS等小数据。
- 分片模式:一个大NAL单元拆成多个RTP包。适用于IDR帧等大数据。
在实际的RTSP推流中,我几乎只用单一NAL单元模式和分片模式。聚合模式用得少,因为SPS和PPS我都是单独发送的。
RTSP推流中H.264的关键配置:
- Profile:推荐High Profile,兼容性好的场景用Baseline
- Level:根据分辨率和帧率选择,留20%余量
- GOP大小:建议2-5秒一个IDR帧,太大会导致seek延迟
- B帧数量:RTSP推流建议不超过2个B帧,太多会增加延迟
- 码率控制:推荐CBR(恒定码率),避免网络抖动
4.5 H.264编码流程与NAL单元生成
为了让你更直观地理解H.264在RTSP中的工作流程,我画了一张图。
这张图展示了从原始视频帧到RTSP推流的完整流程。你可以看到,SPS和PPS是独立于视频帧生成的,它们在整个推流过程中只需要发送一次(但建议定期重复)。
4.6 实战建议
最后,我总结几条在RTSP推流中使用H.264的实战建议:
- Profile选择:优先用High Profile。如果遇到解码兼容性问题,降级到Baseline。
- GOP设置:2-5秒一个IDR帧。太短会浪费码率,太长会导致seek延迟。
- B帧控制:RTSP推流建议用0-2个B帧。B帧越多,延迟越高。
- 码率控制:用CBR模式。VBR虽然画质好,但码率波动大,容易导致网络拥塞。
- SPS/PPS重复:每隔3-5秒重复发送一次SPS和PPS,确保新客户端能正常解码。
- NAL单元分片:IDR帧如果太大,一定要用分片模式传输,避免RTP包超过MTU。
我曾经踩过的坑:有一次在弱网环境下做RTSP推流,我用了VBR码率控制。结果画面剧烈变化时,码率瞬间飙升到30Mbps,直接把网络打爆了。后来我改成CBR,虽然画质略有下降,但推流稳定多了。在RTSP推流中,稳定性永远比画质重要。