
视频编解码器这个词做视频相关开发的人天天挂在嘴边但真正能把它讲清楚的人并不多。前阵子我帮一个创业团队优化视频存储方案他们一个月的原始素材量能把两块4TB硬盘塞满最后我做的第一件事不是调服务器而是坐下来跟他们聊了一个小时编码器选型。因为他们压根没意识到同样的画面用不同的编码器压一遍体积能差出十倍以上。这件事之后我就想写一篇把编解码器原理彻底讲透的文章从底层机制讲到实际项目里的选型和排错让看完的人能真正搞懂自己手头视频文件背后到底发生了什么。视频编解码器简单说就是把原始视频画面压缩成更小数据量的工具播放时再把压缩后的数据还原成画面。它解决的核心矛盾是存储带宽和画面质量不可兼得的问题。这篇文章适合视频开发者、转码服务运维、后期制作人员以及任何被超大视频文件折磨过的人。我会从原始视频为什么那么大讲起然后拆解编码器内部的每一个关键环节再看解码端怎么重建画面最后聊编码标准怎么选、实战参数怎么配、遇到播放卡顿该怎么查。1. 算一笔账不压缩的视频到底能有多大很多人知道原始视频大但大到一个什么程度其实没有概念。我们先算个细账。以常见的1080p、30帧、8bit色深、4:2:0色度采样的视频为例。1080p的意思是1920乘1080个像素点一帧画面大概是207万个像素。4:2:0采样下每个像素对应1个亮度分量和0.5个色度分量平均下来每像素1.5个字节。所以一帧画面的裸数据量是1920 × 1080 × 1.5 3,110,400 字节约2.97MB。再乘上30帧每秒一秒的原始数据量就是89MB换算成码率是712Mbps。一分钟的视频是5.3GB。一部90分钟的电影原始数据接近480GB。这个数字放到任何存储方案面前都是灾难。所以视频编解码器存在的第一理由就是我们必须把数据量降下来否则视频根本无法存储和传输。那压缩空间从哪来我习惯把视频里的冗余分成三类。第一类是空间冗余。一帧画面里蓝天、白墙、纯色背景这种大面积相似区域相邻像素的颜色和亮度差别极小完全没必要一个像素一个像素地记录。第二类是时间冗余。视频每秒30帧相邻两帧之间往往只有极小的变化可能只有画面边缘的物体移动了几个像素背景几乎完全没动。连续记录每一帧的全部信息等于反复记录同一份几乎没变的数据。第三类是视觉冗余。人眼对亮度的敏感度远高于对色彩的敏感度对画面高频细节的感知能力也有限有些信息即使丢掉人眼也不一定看得出来。好的编码器本质上就是把这三种冗余一层层剥掉。空间冗余靠帧内预测和变换编码处理时间冗余靠帧间预测和运动补偿处理视觉冗余靠量化和色度采样处理。下面我按编码器实际工作的顺序把这条流水线完整拆开。2. 从原始画面到压缩码流编码器内部的一整套动作2.1 分块一切压缩操作的基本单元编码器不是对整帧画面做全局运算那样计算量太大而且画面不同区域的内容特性完全不同。它的做法是把一帧画面切成一个个小块逐个处理。在H.264标准里基本处理单元叫宏块大小是16×16像素。到了HEVCH.265这个单元变成了编码树单元CTU默认尺寸是64×64并且允许用四叉树的方式不断往下细分最小可以到8×8甚至4×4。AV1里面还有更灵活的块划分方式。为什么块越小越好因为画面内容不是均匀的。天空这种平坦区域一个大块就能覆盖预测也容易做准但一片树叶的纹理区域像素之间差异很大块太大了预测不准残差就大压缩效率反而差。所以编码器会做率失真优化——对每个区域尝试不同的划分方式计算压缩后的码率和画面失真程度选一个综合代价最小的方案。这个过程非常非常耗计算资源。你在ffmpeg里调preset参数从ultrafast到placebo本质上就是在调节这种搜索的精细程度。preset越慢划分方式试得越多找的组合越优同样码率下画质越好但编码时间成倍增加。2.2 帧内预测利用空间冗余每一个块被划分好之后编码器要做的事情是预测这个块的内容。帧内预测的思路是一帧画面里相邻像素之间存在强烈的相关性可以用周围已经编码好的像素来推断当前块的内容。比如一个块的上方和左方都是某个颜色的像素那这个块大概率也是相近的颜色。H.264的帧内预测提供了9种预测模式包括8个方向性预测和1个DC平面预测。HEVC把它扩展到了35种模式增加了33个不同角度的方向预测加上Planar模式和DC模式。AV1的帧内预测模式数量更多还引入了基于楔形分割的预测方式。这么说可能不太好理解我举个实际例子。假设你在拍一段蓝天白云的视频画面里有一块区域是天空。编码器处理到这个块时发现它的上方、左方像素都是接近的蓝色那么用DC模式取平均值或者用Planar模式做一个平滑渐变就能以非常少的比特数描述这个块的内容。编码器把预测值和真实值的差值——也就是残差——算出来残差往往接近零需要编码的数据量就大幅降低了。帧内预测的应用场景主要是关键帧I帧以及画面内变化剧烈的区域。它不依赖其他帧所以随机访问必须靠它。2.3 帧间预测与运动补偿时间冗余的杀手锏如果说帧内预测是对单帧画面的压缩帧间预测就是跨帧的压缩它处理的是视频序列里最庞大的那一部分冗余。想象一个摄像头固定拍摄街道一辆车从画面中穿过。相邻的两帧之间静止的路面、建筑完全没变变的只是车的位置。如果我们能在前一帧里找到当前块对应的内容只需要告诉解码器这个块的内容就是上一帧里往右移了20个像素的那个块就完全不需要重新编码这个块的像素数据了。这个过程就是运动估计和运动补偿。编码器在参考帧中搜索与当前块最匹配的区域记录一个运动矢量MV这个矢量告诉解码器应该从参考帧的哪个位置取数据然后只需要编码运动矢量和预测残差。H.264的运动估计精度可以达到1/4像素也就是说即使物体移动了半个像素、四分之一个像素编码器也能通过像素插值技术找到匹配位置进一步压缩残差能量。按照帧的依赖关系编码标准定义了三种主要帧类型I帧关键帧完整编码的帧不依赖任何其他帧是随机访问的入口。IDR帧更特殊它之后的帧不允许参考它之前的帧。P帧参考前面的I帧或P帧做预测压缩效率比I帧高很多但遇到画面剧烈变化时预测不准。B帧同时参考前向和后向的帧预测更准、压缩率更高但编码顺序和显示顺序会不一致解码器需要缓冲重排。一组I帧到下一个I帧之间的所有帧组成一个GOPGroup of Pictures。GOP越长压缩率越高但随机访问的代价也越大——你从中间开始播放时必须等下一个I帧才能解码。这也是为什么进度条一拖有的视频要转好几圈才能出来。具体我会在后面的实战排查里再展开。2.4 变换和量化把数据往人眼不敏感的方向赶预测做完之后剩下的是残差数据。对于大部分自然画面残差块里还残留着大量能量分布不均匀的信息直接编码效率也不高。所以编码器需要对残差做一次变换。这里用的是离散余弦变换DCT。它的作用是先把空域信息转换成频域信息。你可以把DCT理解为把一块像素数据分解成一系列不同频率的分量的叠加。低频分量代表画面中缓慢变化的部分比如渐变背景高频分量代表剧烈变化的部分比如锐利边缘和纹理。自然图像的残差经过DCT之后能量会集中到左上角的低频区域高频分量往往接近零。这个特性和傅里叶变换压缩音频的思路一脉相承。H.264使用4×4和8×8整数DCTHEVC支持4×4到32×32的DCT变换对于帧内预测残差还会特殊使用DST。但真正看不见的损耗发生在量化这一步。量化就是用QP量化参数去除变换后的系数。QP越大步长越大更多小系数被置零码率越低画质损失也越大。HEVC和H.264的QP每增加6量化步长近似增大一倍码率大约减半。量化对应的正是前面说的视觉冗余人眼对画面细节的分辨能力是有限的高频细节丢掉一部分人眼几乎察觉不到。所有有损视频编码的压缩率主要就是靠量化堆出来的。2.5 熵编码最后的极限压缩经过预测、变换、量化之后数据变成了一系列系数、运动向量、模式信息。这些数据仍然带有统计规律零出现得特别多小数值出现的概率比大数值高得多。熵编码就是把这种统计规律榨干最后一步。H.264支持两种熵编码方式CAVLC和CABAC。CAVLC是上下文自适应的可变长编码实现简单。CABAC是上下文自适应的二进制算术编码它能根据前面已经编码的内容动态调整概率模型对每一个比特都分配接近理论极限的码长。说白了CABAC能把包含大量零和小系数的数据流压到几乎每个符号不足一个比特比CAVLC再节省10%到20%的码率。这也是为什么H.264的Baseline profile不支持CABAC只有Main profile以上才支持的原因之一算术编码的计算复杂度和内存开销比哈夫曼编码大不少。但为了压缩率这些代价是值得的。到这一步编码器输出的码流已经可以写入容器文件MP4、MKV、TS等了。整个编码流程走完一个700Mbps的原始视频可以被压缩到5Mbps甚至更低压缩比超过100比1而且人眼在正常观看距离上很难看出明显损失。3. 解码端不是简单逆运算它有一个完整的重建回路3.1 为什么编码器内部藏着一个解码器很多人以为解码就是编码的逆过程熵解码、反量化、逆变换、加上预测值就得到画面了。这个说法方向没错但没有说到最关键的一点——整个链路里解码器用的参考帧必须和编码器完全一致。你想想编码器做帧间预测的时候参考的是原始图像还是重建后的图像如果编码器用原始图像做参考而解码器手里只有重建后带误差的图像误差就会像滚雪球一样一帧一帧地累积、漂移最后画面花掉。所以编码器内部必须内置一个本地解码环路对量化后的系数做反量化和逆变换再加上预测值重建出解码端会看到的画面然后把这个重建帧放进参考帧缓冲区。后续P帧、B帧的预测全部基于这个重建帧。这就是为什么视频编码器比单纯做压缩算法的工具复杂得多。它不仅要完成前向的编码计算还得实时模拟整个解码过程。在硬件实现里这部分电路就是编码器和解码器共用的那一块。3.2 环路滤波画质的最后一道防线由于量化是有损的而且操作单位是块所以重建出来的画面会出现很明显的块效应——块与块之间的边界处像素值出现肉眼可见的跳变。如果不去处理低码率下视频会有严重的马赛克感。解决这个问题的是环路滤波器。H.264引入了解块滤波Deblocking Filter它检测块边界的像素梯度如果梯度明显但不是真实的图像边缘就对边界两侧像素做平滑处理。HEVC在解块滤波之后又加上了SAO样点自适应补偿对像素值做进一步的偏移补偿。VVC和AV1则进一步引入了更复杂的ALF自适应环路滤波技术根据画面内容训练滤波器系数。这里有个细节值得注意环路滤波器处理后的帧也会放回参考帧缓冲区作为后续帧的预测参考。所以它不仅仅改善了当前帧的主观画质还直接提升了对后续所有帧的预测精度。这也是环路二字的含义——它处在编码、解码的主循环里而不只是后处理。解码器在运行时做的事其实比编码器单一它只需要按照码流里的指令执行推断、重建不需要自己去搜索最优模式。编码器大部分时间花在试错上——尝试几十种预测模式、搜索最佳运动矢量、比较不同的量化策略解码器则不需要做任何选择判断。这也是为什么硬件编码器实现难度远大于硬件解码器而便宜的手机芯片也能轻松硬解4K视频但要硬编码4K H.265就吃力得多。4. 编码标准与格式选型H.264、HEVC、AV1到底该怎么挑4.1 几代标准的压缩率提升和背后的代价视频编码标准基本十年一换代。H.264/AVC发布于2003年是迄今为止兼容性最好的标准几乎所有设备、浏览器、播放器都支持它。HEVC/H.265发布于2013年目标是在相同画质下码率减半它对高分辨率、10bit色深的支持也更完善。AV1由开放媒体联盟在2018年推出目标是免收传统专利授权费压缩率比HEVC再提升约20%到30%。最新的VVC/H.266在2020年定稿面向8K时代压缩率比HEVC再提升约30%到50%但编码复杂度极高目前实际落地还比较有限。这里把几个主流编码格式的特性放在一起看更直观编码标准发布年份同等画质相对前代码率主要优势主要问题H.264/AVC2003基准兼容性无与伦比从低端设备到高端服务器全支持压缩率在今天已经不够用HEVC/H.2652013约为H.264的一半压缩率大幅提升对4K/8K和10bit支持好专利授权复杂部分浏览器支持有限AV12018比HEVC再少约20-30%开放免传统专利费压缩率优秀流媒体平台力推编码速度慢硬件普及仍在进行VVC/H.2662020比HEVC再少约30-50%面向8K、高动态范围场景的极限压缩复杂度高生态还在早期4.2 实际项目里怎么选标准参数表看起来漂亮但工程上选编码器我先看的是三个维度用户终端能不能放、编码成本扛不扛得住、存储和带宽能省多少。如果你的视频要兼容网页播放H.264配合AAC音频封装成MP4是压倒性的安全选择。Web浏览器天然支持H.264连老旧设备都能硬解。HEVC在浏览器端的支持参差不齐尤其是Safari默认走硬件解码Windows平台要看显卡和浏览器版本出问题的概率不低。AV1在Chrome和Firefox里的支持不错但老版本浏览器无能为力。如果是给自有播放器App做内容分发那HEVC是个好选择。码率减半意味着同样带宽下用户能看到更高分辨率的内容或者同样画质下省一半流量费。我做过一次实际对比同一个1080p30的片子H.264压到4Mbps画质尚可HEVC在2Mbps就达到接近的水平高端点的显示器上差距更明显。AV1目前在短视频平台和头部流媒体里用得很多因为巨头们自己有海量带宽成本而且有强大算力做超慢速离线编码。中小团队要谨慎软件编码AV1慢得让人怀疑人生硬件编码器虽然已经出现但同等码率下的画质和成熟度还需要时间追赶。另外提一下10bit色深。很多人以为高色深只是后期调色才用得上其实10bit编码在相同码率下的压缩效率比8bit更高因为它们减少了色带效应也就是渐变区域的色阶断层。如果你在压制动画或天空渐变场景多的视频用10bit编码能明显改善画面哪怕你的源素材是8bit的重压为10bit在主观画质上也常有收益。5. ffprobe、ffmpeg和播放卡顿实战里的参数与排查链路5.1 拿到一个视频先别急着转码我处理视频项目的第一件事永远是用ffprobe看文件底细ffprobe -v error -show_streams -select_streams v:0 input.mp4重点看codec_name编码器类型、profile、level、width/height、avg_frame_rate、bit_rate和pix_fmt。有一次同事拿着一个MOV文件过来说这视频网页上放不了我一看GCRA——Apple ProRes编码浏览器当然不放不了这是后期剪辑中间格式发布前必须转成H.264。另一个常见情况是视频明明标称4K但profile写的是High不是High 10说明这只是8bit的普通H.264不要对画质抱太高期待。5.2 转码参数怎么定用ffmpeg做软件编码我最常用的压制定式是ffmpeg -i input.mov -c:v libx264 -crf 18 -preset slow -profile:v high -pix_fmt yuv420p -c:a aac -b:a 128k output.mp4-crf 18到23是质量导向编码常用的区间。CRF越小画质越好文件越大。23左右是看不出明显损失但又不会太大的平衡点如果你的视频要在线分发我通常给18到20宁可文件大一点也不想接到用户画质投诉。preset我一般用slow追求极致压缩再往上加时编码时间增长曲线非常陡峭压缩率收益却很小性价比不高。但CRF有个问题它只保证画质恒定不保证最终码率是多少。如果你需要精确控制目标码率比如直播推流或固定存储预算就要用目标码率模式。ffmpeg里可以用-vcodec libx264 -b:v 3000k加-maxrate和-bufsize来限制码率波动。更稳妥的做法是两遍编码第一遍分析视频复杂度第二遍分配码率相同目标码率下画质通常比单遍好一截代价是编码时间翻倍。GOP参数也要提。ffmpeg里-g 48指的是每48帧放一个关键帧对30fps视频来说就是每1.6秒一个I帧。直播场景需要低延迟关键帧间隔不能太长点播场景则可以放宽到2到4秒提升压缩率。x264还有一个scenecut逻辑即使没到I帧间隔如果画面切换剧烈也会自动提前插入关键帧。这个特性默认开着能让剪辑切换多的视频保持更好的可压缩性我一般不关它。5.3 播放不流畅的完整排查链路视频播放卡顿的根因排查比表面看起来要复杂得多。我按自己的排错顺序分享一条实用的排查链路。第一步先用ffprobe拿到完整的流信息确认三件事编码格式是什么、码率有多高、帧率是多少。有一个很常见的坑视频的码率是VBR的平均码率看着不高但峰值码率能冲到很高。如果你的播放器没有做足够的缓冲就会在码率高的段落卡一下。我遇到过一个客户反馈视频播放到第12秒必卡后来看码率曲线第12秒到第14秒码率飙到平均值的四倍而播放器的缓冲区只有2秒不卡就怪了。解决办法是重压时用-maxrate限制峰值码率。第二步判断是软件解码还是硬件解码。浏览器里播放4K HEVC视频如果终端设备不支持硬解CPU软解扛不住画面就会掉帧。你可以打开系统的任务管理器或活动监视器看播放视频时CPU占用率。如果CPU占满而GPU几乎没动多半是软解在硬撑。这类问题要么换客户端播放器走系统硬解要么在服务端转成H.264或降低规格。第三步检查B帧设置。B帧虽然是压缩利器但会引入解码延迟因为它需要后面的帧做参考解码器必须缓冲等待。直播或视频通话场景下B帧要尽量少有的低延迟编码器甚至直接禁用B帧。在ffmpeg里可以用-x264-params bframes0禁掉。点播场景里B帧不是问题因为播放器有充足的缓冲时间。第四步检查封装容器。有些播放器对MKV容器的支持不如MP4和TS完整虽然MKV能装几乎所有编码流但遇到不标准的封装依然会出幺蛾子。规则很简单网络分发优先MP4直播优先FLV或TS存档和高兼容性场景用MKV。还有一个很容易被忽视的坑编码器和解码器的兼容性。比如HEVC封装进MP4时如果不对标签做处理Safari认不出码流。ffmpeg里用-c:v libx265 -tag:v hvc1可以解决这个问题。这类坑我踩过太多次了很多播放不了的反馈最后都是标签、profile、level不兼容这些小细节引起的。最后再说一个我自己做转码服务时的经验压完片之后不要只看文件体积和人眼观感要用ffprobe或者专业测试图卡做客观验证。有一回我压一个体育视频噪点多、运动剧烈默认CRF压完后体积大了不少但画质还是没有达到预期。后来把preset从medium提到slow同码率下画面的细节保留好了很多。运动越多、纹理越复杂的视频越需要花更多编码时间来搜索运动矢量和划分块。在这种场景下编码时间多花一些是完全值得的。