3、视频解码基础:H.264/H.265编码原理、YUV色彩空间、帧率与码率概念
各位同学,欢迎来到第三章。
做多媒体开发,尤其是硬解码,有几个基础概念你必须吃透。说白了,不懂YUV、不懂帧率码率、不懂H.264和H.265的区别,你连解码器报的错都看不懂。今天我就把这些核心概念掰开了讲清楚。
3.1 为什么视频要编码?
先问个问题:一段1080p、30fps、时长1分钟的视频,原始数据有多大?
我们来算一下:
- 分辨率:1920 x 1080 = 2,073,600 像素
- 每个像素:如果是RGB24,需要3字节
- 一帧数据:2,073,600 x 3 ≈ 6.22 MB
- 30帧/秒:6.22 MB x 30 ≈ 186.6 MB/s
- 1分钟:186.6 MB x 60 ≈ 11.2 GB
11.2GB!这还只是1分钟。你想想看,一部90分钟的电影,原始数据超过1TB。这谁受得了?
所以,视频编码的核心目的就一个:压缩。把冗余信息去掉,只保留关键数据。
3.2 H.264 / AVC 编码原理
H.264,也叫AVC,是目前最普及的视频编码标准。我在MTK平台上处理过的视频流,90%以上都是H.264。
它的核心思想是:帧内预测 + 帧间预测 + 变换量化 + 熵编码。
3.2.1 帧内预测
说白了,就是利用同一帧内相邻像素的相似性来压缩。比如一块蓝色的天空,我只需要存一个颜色值,然后告诉解码器“从这里到那里都是这个颜色”。
H.264支持多种帧内预测模式,比如4x4块有9种方向预测模式。我刚开始做优化时,发现某些场景下帧内预测模式选错了,画面边缘会出现明显的块状效应。嗯,这里要注意,帧内预测只用于I帧。
3.2.2 帧间预测
这才是H.264的精髓。它利用的是时间冗余。
你想啊,视频里相邻两帧的画面变化其实很小。比如一个人说话,背景基本没变。那我何必每帧都存完整的背景?我只存“变化的部分”就行了。
具体做法是:
- 把当前帧分成16x16的宏块
- 在参考帧(前一帧或后一帧)中找最匹配的块
- 记录下这个块的偏移量,也就是运动矢量
- 只存运动矢量和残差(当前块和匹配块的差值)
我在MT8676上调试时,遇到过一个问题:运动估计的搜索范围设得太小,导致快速运动的画面出现“拖影”。后来我把搜索范围从32像素扩大到64像素,问题就解决了。
3.2.3 变换与量化
这一步是把残差数据从空间域转换到频率域。H.264用的是4x4整数DCT变换。
为什么要做变换?因为人眼对高频细节不敏感。量化就是故意丢掉一些高频信息,这是有损压缩的主要来源。
量化参数QP值越大,丢掉的信息越多,压缩率越高,但画质越差。我个人的习惯是,在MTK平台上做视频通话时,QP值控制在20-28之间,画质和码率比较平衡。
3.2.4 熵编码
最后一步,把量化后的数据用更紧凑的方式表示。H.264支持CAVLC和CABAC两种方式。CABAC压缩率更高,但计算量也更大。在MT8676的硬解码模块里,CABAC是硬件实现的,所以不用担心性能问题。
3.3 H.265 / HEVC 编码原理
H.265是H.264的升级版,目标是在同等画质下,码率降低50%。
它和H.264的核心思想一样,但做了很多改进:
| 特性 | H.264 | H.265 |
|---|---|---|
| 宏块大小 | 固定16x16 | 可变,最大64x64(CTU) |
| 帧内预测模式 | 9种(4x4块) | 35种 |
| 运动补偿精度 | 1/4像素 | 1/4像素(亮度)/ 1/8像素(色度) |
| 变换块大小 | 4x4 / 8x8 | 4x4 到 32x32 |
| 熵编码 | CAVLC / CABAC | CABAC(改进版) |
H.265最大的变化是引入了四叉树划分结构。一个64x64的CTU可以递归地划分成更小的块,这样在平坦区域用大块编码(节省码率),在细节区域用小块编码(保留细节)。
我曾经在MT8676上测试过一段4K视频,H.265编码的码率只有H.264的55%左右,但主观画质几乎看不出区别。这就是为什么现在4K/8K视频都强制用H.265。
重要提示:MT8676的硬解码模块同时支持H.264和H.265。但要注意,H.265的解码复杂度是H.264的2-3倍。如果你的系统资源紧张,优先考虑H.264。
3.4 YUV色彩空间
做视频开发,你打交道最多的不是RGB,而是YUV。
YUV和RGB的区别是什么?
- RGB:用红、绿、蓝三个分量表示颜色。每个分量8位,共24位。
- YUV:用亮度(Y)和色度(U、V)表示颜色。人眼对亮度敏感,对色度不敏感。
YUV的优势在于:可以降低色度的采样率,而人眼几乎察觉不到。
3.4.1 常见的YUV采样格式
| 格式 | 描述 | 数据量(相对于RGB) |
|---|---|---|
| YUV444 | 每个像素都有完整的Y、U、V | 100% |
| YUV422 | 每2个像素共享一组U、V | 66.7% |
| YUV420 | 每4个像素共享一组U、V | 50% |
在MTK平台上,硬解码输出的默认格式通常是NV12(一种YUV420的变体)。它的内存布局是:先存所有Y数据,然后交错存储U和V。
避坑指南:我曾经在对接显示驱动时,把NV12的UV顺序搞反了,结果画面偏绿。后来查了MTK的文档才发现,NV12的UV顺序是U在前、V在后,而有些平台是V在前、U在后。一定要确认你平台的字节序!
3.4.2 YUV与RGB的转换
虽然硬解码输出的是YUV,但最终显示到屏幕上,通常需要转成RGB。MT8676的显示模块内部有硬件色彩转换器,不需要你手动算。
但如果你要在CPU上做转换,公式如下:
// BT.601标准,YUV转RGB
R = Y + 1.402 * (V - 128)
G = Y - 0.344 * (U - 128) - 0.714 * (V - 128)
B = Y + 1.772 * (U - 128)
注意:Y、U、V的取值范围是[0, 255],计算出的R、G、B需要做clamp操作,确保在[0, 255]范围内。
3.5 帧率与码率
这两个概念,我见过太多人搞混了。
3.5.1 帧率(FPS)
帧率就是每秒显示多少帧画面。单位是fps(frames per second)。
- 电影:24fps
- 电视:25fps(PAL)/ 30fps(NTSC)
- 游戏:60fps或更高
- 视频通话:15-30fps
帧率越高,画面越流畅,但数据量也越大。在MT8676上做硬解码时,帧率是由解码器自动处理的。你只需要保证解码器的输出帧率不低于显示帧率,否则会出现卡顿。
3.5.2 码率(Bitrate)
码率是每秒传输的比特数。单位是bps(bits per second),常用kbps或Mbps。
码率决定了视频的质量和文件大小:
- 码率越高,画质越好,文件越大
- 码率越低,画质越差,文件越小
常见的码率参考:
| 分辨率 | H.264推荐码率 | H.265推荐码率 |
|---|---|---|
| 480p | 1-2 Mbps | 0.5-1 Mbps |
| 720p | 3-5 Mbps | 1.5-2.5 Mbps |
| 1080p | 8-12 Mbps | 4-6 Mbps |
| 4K | 30-50 Mbps | 15-25 Mbps |
注意:码率不是越高越好。在MTK平台上,如果码率超过解码器的处理能力,会出现丢帧或花屏。MT8676的硬解码器最高支持4K@60fps,码率上限约100Mbps。超过这个值,解码器会报错。
3.5.3 帧率与码率的关系
很多人以为帧率翻倍,码率也要翻倍。其实不是。
因为帧间预测的存在,相邻帧之间的冗余信息很多。帧率从30fps提高到60fps,码率通常只增加30%-50%,而不是100%。
我做过一个测试:一段1080p视频,30fps时码率是10Mbps。改成60fps后,码率只增加到14Mbps。因为多出来的30帧,大部分信息都可以从前面帧预测出来。
3.6 本章小结
这一章的内容,是后面所有章节的基础。你理解了:
- H.264/H.265的编码原理(帧内预测、帧间预测、变换量化)
- YUV色彩空间(为什么用YUV、NV12格式)
- 帧率和码率的概念(以及它们之间的关系)
下一章,我们就要进入实战了——在MT8676上搭建硬解码环境。到时候你会发现,这些基础概念会反复用到。
好,今天就到这里。有问题随时交流。