Hacker News Best

H.264 Is Magic (2016)

6.9内容质量

TL;DR · AI 摘要

H.264 is magic: a technical walkthrough of a remarkable technology. H.264 is Magic Sid Bala 02 Nov 2016 • 10 min read Sh...

核心要点

  • 主题聚焦:H.264 Is Magic 2016
  • 来源:Hacker News Best,建议结合原文判断细节。
  • AI 分析暂不可用,本条为保底评分与摘要。
#AI#编程#安全
打开原文

H.264的魔力:一项非凡技术的技术解析

H.264的魔力

#### Sid Bala

2016年11月2日

•

10分钟阅读

分享

kg-card-begin: markdown

H.264是一种视频压缩编解码标准。它无处不在 - 互联网视频、蓝光光盘、手机、安防摄像头、无人机,所有设备都在使用H.264。

H.264是一项非凡的技术成就。它是30多年研发的成果,目标只有一个:减少传输动态视频所需的带宽。

从技术角度看,它非常有趣。本文将从宏观层面解析其中的一些细节 - 希望不会用过多的技术细节让你感到枯燥。请注意,这里解释的许多概念不仅适用于H.264,也适用于视频压缩技术本身。

为什么要进行压缩?

一个未压缩的视频文件包含一个二维缓冲区数组,每个帧都包含像素数据。因此它是一个三维数组(两个空间维度和一个时间维度)的字节集合。每个像素需要3个字节存储 - 红、绿、蓝三种主色各一个字节。

1080p @ 60 Hz = 1920x1080x60x3 => 每秒约370MB的原始数据。

这几乎无法处理。50GB的蓝光光盘只能存储约2分钟的视频。你无法快速传输这些数据。即使使用SSD,从内存直接写入磁盘也会遇到困难[^1]。

没错,我们需要压缩。

为什么要使用H.264压缩?

是的,我会回答这个问题。但首先让我给你看个例子。这是苹果官网:

我截取了这个首页的屏幕并生成了两个文件:

  • 苹果官网的PNG截图 1015KB
  • 相同苹果官网的5秒60fps H.264视频 175KB

哦?这两个文件大小看起来反过来了?

没错。这个H.264视频有300帧,大小是175KB。而该视频的单帧PNG图像大小是1015KB。

看起来我们存储了视频中300倍的数据量,但文件大小却只有五分之一。因此H.264似乎比PNG高效1500倍。

这怎么可能?好吧,有什么诀窍吗?

有很多诀窍!H.264使用了你能想到的所有技巧(以及很多你想不到的技巧)。让我们看看其中重要的几个。

###### 减重

想象你正在制造一辆街头赛车的汽车。你需要更快。你首先会做什么?减重。你的车重3000磅。你扔掉不需要的东西。那些后座?呵,扔掉。那个低音炮?没了。你不能听音乐。空调?是的,扔掉。变速器?呃...不。等等!我们还是需要这个。

你只保留关键部件。

这种通过丢弃不需要的比特来节省空间的概念称为有损压缩。H.264是有损编解码器 - 它只丢弃不重要的比特,保留重要的比特。

PNG是无损编解码器。这意味着没有任何东西被丢弃。从PNG编码的图像中可以完全恢复原始图像的每个比特。

重要比特?算法如何知道我帧中的哪些比特是重要的?

有一些显而易见的方法可以裁剪图像。也许右上象限永远没有用。也许我们可以将这些像素设为零并丢弃该象限。这样我们只需要四分之三的空间。现在约2200磅。或者也许我们可以裁剪掉画面边缘的厚边框,因为重要内容在中间。是的,你可以这样做。但H.264不会这样做。

H.264实际上做了什么?

H.264 与其它有损图像算法一样,会丢弃细节信息。以下是原始图像与丢弃细节后的图像对比特写。

你注意到压缩后的图像没有显示 MacBook Pro 扬声器网格上的孔洞了吗?如果不放大查看,甚至很难察觉差异。右边的图像体积仅为原图的 7%——而且我们甚至还没有以传统方式压缩图像!想象一下你的汽车仅重 200 磅!

7% 哇!你是如何丢弃这些细节信息的?

为此我们需要快速上一节数学课。

###### 信息熵

现在我们进入核心内容了!哈,双关语!如果你上过信息论课程,可能还记得信息熵这个概念。信息熵表示表示某些信息所需的比特数。请注意,它并不等同于数据集的大小,而是表示必须用于表示数据集中所有信息的最小比特数。

举个例子,如果数据集是单次抛硬币的结果,你需要 1 比特的熵。如果你记录两次抛硬币结果,需要 2 比特。这个逻辑合理吗?

假设你有一枚奇怪的硬币——你抛了它 10 次,每次都出现正面。你会如何向别人描述这个信息?你不会说 "HHHHHHHHH",而是会说 "10 次抛掷,全部正面"——搞定!你刚刚就完成了数据压缩!太简单了。我为你节省了数小时的脑力折磨课程。显然这是一个过度简化,但你已经将数据转换为相同信息的更短表示形式。你减少了数据冗余。这个数据集的信息熵并没有改变——你只是在不同表示形式之间进行了转换。这种编码器被称为熵编码器——这是一种通用的无损编码器,适用于任何类型的数据。

###### 频域

现在你理解了信息熵,让我们继续讨论数据转换。你可以用一些基本单位来表示数据。如果你使用二进制,有 0 和 1。如果你使用十六进制,有 16 个字符。你可以在这两种系统之间轻松转换。它们本质上是等价的。到目前为止都明白了吗?好的!

现在,发挥一下想象力!想象你可以将任何随空间(或时间)变化的数据集——比如图像的亮度值——转换到不同的坐标空间。现在我们不再使用 x-y 坐标,而是使用频率坐标。freqX 和 freqY 成为新的坐标轴。这被称为频域表示。还有一个令人困惑的数学定理[^2] 表明,只要你选择足够高的 freqX 和 freqY,就可以对任何数据进行完美无损的转换。

好吧,那 freqX 和 freqY 到底是什么?

freqX 和 freqY 是另一组基础单位。就像我们从二进制切换到十六进制时,基础单位发生了变化一样,我们现在从熟悉的 x-y 坐标切换到 freqX 和 freqY。十六进制的 'A' 看起来和二进制的 '1010' 不同,但它们表示的是相同的信息。因此,我们的图像在频域中看起来是这样的:

MacBook Pro 上精细的网格在图像的高频成分中包含大量信息。精细变化的内容 = 高频成分。颜色和亮度的任何渐变变化——比如渐变——都是图像的低频成分。介于两者之间的内容自然也处于中间位置。所以精细细节 = 高频,柔和渐变 = 低频。这个逻辑合理吗?

在频域表示中,低频分量位于图像中心附近,而高频分量则分布在图像边缘区域。

好吧,听起来还算合理。但为什么要这么做呢?

现在,你可以对频域图像进行处理,将边缘部分遮盖掉——丢弃包含高频信息的数据。当你将图像转换回常规的x-y坐标系时,会发现处理后的图像与原图相似,但丢失了一些细节。不过,此时图像所占用的空间仅是原图的一小部分。通过控制遮罩的大小,你可以精确地调节输出图像的细节程度。

这是首页中笔记本电脑特写画面的再次展示。不同之处在于,这次应用了一个圆形边框遮罩。

这些数字表示图像信息熵相对于原始图像的占比。即使在2%的压缩率下,除非你放大到这个级别,否则几乎无法察觉差异。2%!你的汽车现在仅重60磅!

这就是如何实现减重的方法。这种在有损压缩中通过量化处理信息的过程被称为量化 [^3]。

好吧,确实令人印象深刻。还有其他方法吗?

###### 色度抽样(Chroma Subsampling)

人眼视觉系统对颜色的细微差别分辨能力较弱,却能轻松察觉亮度的微小变化。因此,必然存在一种方法可以丢弃部分颜色信息以进一步减重。

在电视信号中,R+G+B颜色数据会被转换为Y+Cb+Cr。其中Y代表亮度(即黑白灰度),Cb和Cr代表色度(颜色)分量。从信息熵的角度来看,RGB和YCbCr是等价的。

为什么要如此复杂?RGB不是已经足够了吗?

在彩色电视出现之前,我们只有Y信号。当彩色电视开始普及时,工程师们需要找到一种方法,将RGB颜色信息与Y信号一并传输。他们没有选择使用两条独立的数据流,而是明智地将颜色信息编码到Cb和Cr中,并与Y信息一同传输。这样,黑白电视只需查看Y分量,而彩色电视则会额外查看色度分量并将其内部转换为RGB。

但这里有个巧妙之处:Y分量以完整分辨率编码,而C分量仅以四分之一分辨率编码。由于人眼对颜色变化的感知能力较差,这种做法是可行的。通过这种方式,可以将总带宽减少一半,而视觉差异却非常小。一半!你的汽车现在仅重30磅!

这种丢弃部分颜色信息的过程被称为色度抽样 [^4]。虽然这种方法并非H.264独有,且已有数十年历史,但如今几乎被所有编码标准采用。

这些就是有损压缩中主要的减重手段。现在,我们的图像帧变得非常小——因为我们已经丢弃了大部分细节信息和一半的颜色信息。

等等,就这些吗?我们还能做些什么?

当然。减重只是第一步。到目前为止,我们仅关注单帧图像中的空间域信息。现在是时候探索时间域压缩——通过分析多个帧之间的关系来实现进一步减重。

###### 运动补偿

H.264是一种采用运动补偿压缩的标准。

运动补偿?这是什么新东西?

Imagine you're watching a tennis match. The camera is fixed at a certain angle. The only thing moving is the ball back and forth. How would you encode this information? You do what you always do, right? You have a 3D array of pixels, two dimensions in space and one in time. Right?

不,为什么要这么做?毕竟大部分画面都是静态的,比如球场、球网和观众。唯一真正变化的就是球的运动。如果能用一张静态背景图,再加上一张只记录球运动的动态图,是不是能节省很多空间?你明白我的意思了吧?懂了吗?看到我想要表达的重点了吗?这就是运动估计!

抛开这些无聊的玩笑,这正是H.264编码器的工作方式。H.264将画面分割成宏块(macro-blocks)——通常是16x16像素的块,用于运动估计。它会先编码一张静态图像,通常称为I帧(Intra frame)。这是一张完整的帧,包含了构建该帧所需的所有数据。随后的帧则为P帧(Predicted)或B帧(Bi-directionally Predicted)。P帧会为每个宏块从上一帧计算运动矢量。因此P帧必须由解码器基于之前的帧重新构建。解码器从视频流中最近的I帧开始,依次遍历后续帧,累加运动矢量的变化值,直到到达当前帧。

B帧则更有趣,因为它可以同时从过去帧和未来帧进行双向预测。现在你应该明白为什么苹果首页的视频能被压缩得如此高效了。因为本质上它只包含三个I帧,而宏块只是在这些I帧之间进行平移。

假设你在YouTube上观看视频,错过了最后几秒的对话,于是倒回了几秒钟。你有没有注意到视频不会立即从你选择的时间码开始播放?它会暂停几秒钟,然后才开始播放。既然这些帧已经从网络缓冲过,为什么还要暂停?

喂,这真的让我很烦。为什么非得这样?

因为你要求解码器跳转到某个任意帧,解码器必须重新计算所有数据——从最近的I帧开始,累加到目标帧的运动矢量变化值。这个过程计算量很大,所以才会出现短暂的暂停。现在你了解真相后,应该不会那么烦躁了,毕竟它其实在做繁重的计算,而不是故意找你麻烦。

由于你只编码了运动矢量的变化值,这种技术对任何包含运动的视频都非常节省空间,代价是需要额外的计算资源。

至此我们已经涵盖了空间压缩和时间压缩!到目前为止,量化过程已经节省了大量空间。色度抽样进一步将所需空间减少了一半。再加上运动补偿技术,只需要存储3个实际帧,就能替代视频中原本约300个帧。

看起来效果还不错。那接下来呢?

现在我们收尾并完成最后一步。使用传统的无损熵编码器。为什么不呢?为了彻底,我们干脆加上它好了。

###### 熵编码器

I帧在经过有损处理后仍包含冗余信息。在P帧和B帧中,每个宏块的运动矢量存在大量重复值——在我们的测试视频中,当画面平移时,多个宏块会以相同幅度移动,因此会出现整组相同值的运动矢量。

熵编码器会处理这种冗余信息。由于它是一个通用的无损编码器,我们无需关心它做出的取舍。所有输入数据都可以被完整恢复。

到此为止!本质上,这就是H.264等视频压缩编码器的工作原理。这些就是它的"魔法"。

好吧,很棒!不过我很好奇现在我们的车有多重了。

原始视频以1232x1154这个奇怪的分辨率拍摄。如果套用这里的公式计算: 5秒@60fps = 1232x1154x60x3x5 => 1.2GB 压缩视频 => 175KB

如果将相同压缩比应用到我们的3000磅汽车上,最终重量是0.4磅,也就是6.5盎司!

是的,这就是魔法!

显然,我在这里对几十年来该领域深入研究的成果做了极大简化。如果想了解更多,维基百科页面有很详细的说明。

有评论吗?我哪里说错了?不喜欢这些无聊的笑话?对脏话感到不适?欢迎在HackerNews或Reddit发表意见!

或者想聊天的话,可以来推特或领英找我。

[^1] SSD基准测试

[^2] 奈奎斯特-香农采样定理

[^3]量化

[^4] 色度子采样