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上搭建硬解码环境。到时候你会发现,这些基础概念会反复用到。

好,今天就到这里。有问题随时交流。