1. 先搞清楚这几件事,再动手装VTM6.0
如果你是因为看到H.266/VVC相关论文或者招聘要求才搜到这个标题,建议先别急着敲命令。VTM6.0是VVC(Versatile Video Coding)在2019年发布的第六版参考软件,对应当时JVET推进到第六版草案的状态。虽然现在VTM版本已经迭代了很多,但6.0这个版本对学习整个VVC框架仍然非常有价值,因为它的代码结构相对清晰,还没有后来大量交叉工具引入后的复杂臃肿感。
这里要先分清楚两个概念:H.266/VVC是标准本身,规定的是码流格式和解码器必须支持的语法语义;VTM是配套的参考软件,包含编码器、解码器两个可执行程序。它不是商业产品,只是一个"跑得通"的实验平台,存在的意义是验证标准提案的增益、提供性能参考锚点。所以你在VTM上看到的编码速度慢到令人发指,完全是正常现象。
我见过不少人安装VTM后第一个反应是"编码速度怎么这么慢,是不是哪里设置错了"。坦白讲,如果你用的是默认配置编码几秒钟的序列,VTM6.0在普通桌面CPU上编码一帧可能就要几十秒甚至几分钟,这还不算多线程加速前的情况。为什么这么慢?因为参考软件追求的是编码增益的验证,里面几乎所有帧内预测模式、帧间模式、变换划分方式都会做全遍历搜索,没有任何商业化编码器常有的early termination,慢是设计使然。
另外要明确版本差异。VTM6.0对应的标准草案是JVET-N系列会议之后的版本,帧内编码里已经有了Position Dependent Prediction Combination(PDPC)、Multiple Reference Line(MRL)、Intra Sub-Partitions(ISP),帧间编码里有了仿射运动估计、基于子块的时域运动矢量预测(SbTMVP)、自适应运动矢量精度(AMVR)等工具。这代版本对研究帧内编码和帧间编码的主要框架已经足够,早于这个版本的VTM3.0/4.0缺少一些关键工具,晚于VTM8.0之后的版本代码结构变化大、复杂度更高,对新手不太友好。
2. 环境准备与编译:Linux/macOS/Windows三套方案
2.1 需要准备哪些依赖
VTM6.0的编译依赖非常少,这点比很多开源项目良心多了。核心依赖只有两样:CMake(3.4以上版本即可)和C++编译器。如果你在Linux或者macOS上操作,还需要安装libxml2开发库,如果缺失,编译时不会报错,但生成的可执行文件跑起来会在读到EncAppCfg时崩溃,这是个非常容易踩的坑。
Windows平台建议用Visual Studio 2019以上。即便你平时用MinGW比较多,我也不推荐在Windows上拿MinGW编译VTM6.0,因为CMake生成的Makefile在MinGW环境下偶尔会遇到路径分隔符问题,运气不好折腾几个小时。VS对CMake项目的支持很成熟,用它最省心。
2.2 Linux/macOS编译步骤
Linux和macOS的编译流程几平完全一样,直接按下面执行:
git clone https://vcgit.hhi.fraunhofer.de/jvet/VVCSoftware_VTM.git cd VVCSoftware_VTM git checkout VTM-6.0这里要注意:master分支上的代码很新,跟VTM6.0差异很大,编译前务必切到VTM-6.0这个tag。如果不习惯git操作,也可以直接去官方仓库下载VTM-6.0的zip压缩包,效果一样。
接着创建build目录并执行编译:
mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j4这里的Release选项决定了编码性能。如果你用Debug模式编译,编码速度会再慢3到5倍,几乎没法做实验。我的习惯是用-j$(nproc)直接用满全部CPU核,VTM每个线程编码一个CTU行,并行收益接近线性,不用白不用。
编译完成后,在build目录的bin文件夹下会生成EncoderApp和DecoderApp两个可执行文件。
2.3 Windows编译步骤
Windows上用VS编译的流程是这样的:先用cmake-gui选择源码目录和build目录,点Configure,选择Visual Studio对应的版本,然后点Generate。之后打开生成的.sln文件,在解决方案管理器里找到EncoderApp和DecoderApp两个项目,右键生成解决方案。
需要留意的是,VS默认是Debug模式,需要手动切换到Release x64。x86平台也能编译通过,但内存在32位进程里受限,处理高清序列时会有麻烦。
提示:如果你用的VS版本比较新,比如VS2022,CMake生成时直接选"Visual Studio 17 2022"即可,VTM6.0的代码完全兼容。
2.4 验证编译是否成功
不要急着编码视频,先用无害的方式验证工具链是否正常:
cd bin ./EncoderApp --help如果命令行提示了一堆编码器参数,说明基本没问题。如果没有任何输出或者运行时直接segmentation fault,检查两点:是否用了Debug构建;是否缺少libxml2。定位到问题后再重新编译,不要在缺依赖的情况下硬跑,测出来的结果没有任何参考价值。
3. 编解码基础命令:从一次完整实验跑通开始
3.1 编码命令解析
VTM6.0的编码命令格式如下:
./EncoderApp -c encoder_randomaccess_vtm.cfg -c per-sequence-config.cfg -i input.yuv -wdt 1920 -hgt 1080 -fr 50 -f 65 -q 32拆开来看:-c指定配置文件,可以叠加,后面的配置覆盖前面的;-i输入YUV文件路径;-wdt和-hgt指定输入视频宽高;-fr指定帧率;-f指定编码总帧数;-q指定量化参数(QD)。核心概念就这些,其余参数要么有默认值,要么在配置文件里给定了,命令行上不必全部体现。
per-sequence-config.cfg是每个序列单独准备的配置,里面通常包含InputBitDepth、InputChromaFormat、FramesToBeEncoded等序列属性。之所以单独搞一个文件,是因为VTM跑实验时往往需要批量测试不同分辨率和帧率的序列,把序列相关参数和编码器通用参数分离,执行脚本可以复用。
3.2 一次完整编码到解码的流程
假设手头有一个BasketballDrive_1920x1080_50.yuv,这是CTC标准测试序列之一。完整流程如下:
第一步,在bin目录下新建一个seq.cfg,内容如下:
InputFile : BasketballDrive_1920x1080_50.yuv InputBitDepth : 8 InputChromaFormat : 420 FrameRate : 50 FramesToBeEncoded : 65 SourceWidth : 1920 SourceHeight : 1080第二步,执行编码:
./EncoderApp -c encoder_randomaccess_vtm.cfg -c seq.cfg -q 32注意:因为seq.cfg里已经写了输入文件、宽高、帧数等信息,命令行上的参数就可以省掉。编码结束后会生成一个.bin的码流文件,文件名默认与配置文件有关,建议用-b显式指定输出名,避免后面对不上号:
./EncoderApp -c encoder_randomaccess_vtm.cfg -c seq.cfg -q 32 -b str.bin第三步,用解码器生成重建YUV:
./DecoderApp -b str.bin -o rec.yuv解码输出的是重建视频,跟原始YUV对比可以计算PSNR。VTM编码器的日志里已经会输出每帧的PSNR和最终平均PSNR,所以很多情况下不需要额外算。
3.3 没有测试序列怎么办
完整的CTC标准测试序列在JVET公共服务器上可以下载,但体积很大。如果你只是想验证"程序跑通了",可以用FFmpeg生成一个测试序列:
ffmpeg -f lavfi -i testsrc2=size=1920x1080:rate=50 -frames 65 -pix_fmt yuv420p test.yuv这里用testsrc2而不是testsrc,因为testsrc2的颜色下变化更平滑,编码时有更多帧间相关性,跑出来的RD曲线不会太难看。实际研究时不要用这种合成序列得出结论,它只能用来验证工具链是否正常。
4. 配置文件与码率控制:理解VTM参数体系的钥匙
4.1 三种主配置的定位
VTM6.0的cfg目录下有几个核心配置文件:encoder_intra_vtm.cfg、encoder_lowdelay_vtm.cfg、encoder_lowdelay_P_vtm.cfg、encoder_randomaccess_vtm.cfg。它们分别对应全帧内配置、低延迟P帧配置、低延迟B帧配置、随机接入配置。
这几个配置的核心差别在于GOP结构和参考帧管理策略:
| 配置文件 | GOP结构 | 典型用途 |
|---|---|---|
| encoder_intra_vtm.cfg | 全部I帧 | 帧内编码性能评估 |
| encoder_lowdelay_P_vtm.cfg | 只有第一帧是I帧,后续全P帧 | 低时延场景 |
| encoder_lowdelay_vtm.cfg | I帧+P/B帧交错,无未来帧引用 | 会议通话类场景 |
| encoder_randomaccess_vtm.cfg | 分层B帧,含I帧刷新 | 广播、流媒体通用测试 |
研究帧内编码的人用intra配置,研究运动估计、光流、帧间预测的人用randomaccess和lowdelay配置。不同配置出的码流结构差异巨大,在写论文对比时一定要明确自己用的是哪种配置,否则实验结果不具备可比性。
4.2 QP和码控的关系
VTM6.0默认是固定QP模式,也就是每个CTU使用同一个量化步长,不做目标码率匹配。固定QP模式下-q 22 27 32 37四个点是CTC的标准测试条件。如果你需要输出特定码率,例如"2Mbps",那要用码控模式,VTM6.0里通过RateControl和TargetBitrate这两个参数启用:
RateControl : 1 TargetBitrate : 2000000在码控模式下,QP变成了编码器自主调整的变量,编码器按照目标码率动态调整量化步长。这个模式跑起来比较慢,因为每帧编码结束后还要做码率分配的逻辑。做标准测试时建议用固定QP,码控模式用在码率适配实验里即可。
4.3 修改配置时最容易改错的几个参数
FrameRate如果写错,码率的计算会出很大偏差,因为时间戳信息都依赖帧率。InputChromaFormat如果实际是420而写成了422,编码器不会报错,但输出的码流编码的是错误的重采样数据,画质会掉得离谱。FramesToBeEncoded要小于等于输入YUV总帧数,多写会读到文件末尾再继续读,后面的帧数据全是脏数据。
注意:修改编码配置时,宁可多花一分钟检查InputChromaFormat、SourceWidth、SourceHeight这三个参数,也不要后面浪费半天排查画质异常。这是VTM排错里最基础也最高频的错误来源。
4.4 理解VTM6.0的编码日志
编码结束后会输出一大段统计信息,不要直接跳过。重点看这几行:
Bytes OFRAME : 123456 Bits IFrame : 987654 POC 0 TId: 0 QP: 32 Bits: 12345 Y-PSNR: 35.67 U-PSNR: 40.12 V-PSNR: 41.23第一行Bytes表示总码流大小,换算成码率用:码率(bps) = 总字节数 * 8 * 帧率 / 总帧数。POC开头的那一行是逐帧统计,包含帧类型、时域层ID(TId)、QP、比特数、各分量PSNR。如果某帧的比特数突然暴涨,通常说明这帧被编码成了I帧(random access配置下每32帧插一次I帧),这是正常的。但如果无规律地暴涨,就需要怀疑是不是输入YUV出现了花屏。
5. 实操中的常见问题与排查思路
5.1 编码器崩溃在一开始的"libxml2"问题
这是在Linux上编译的用户最容易遇到的现象:编译一切正常,运行编码器时却提示类似error while loading shared libraries: libxml2.so.2或者直接段错误。
原因很简单,VTM读取配置文件时用到了libxml2的解析能力,但cmake在生成Makefile时没有正确添加链接路径。解决思路是安装libxml2的开发包:
# Ubuntu/Debian sudo apt-get install libxml2-dev # CentOS/RHEL sudo yum install libxml2-devel # macOS brew install libxml2安装后重新跑cmake生成Makefile,再make一次即可。CMakeCache.txt如果没有识别到新装的库,可以删掉build目录重建。
5.2 编码速度奇慢,怎么加速
VTM6.0在单线程下编码1080p序列大概每帧几秒到几十秒,这取决于机器和配置。如果实在等不及,可以做两件事:
第一,开多线程。配置文件的FramesToBeEncoded没有并发限制,VTM支持WPP(Wavefront Parallel Processing),在配置里设置WaveFrontSynchro为1,然后命令行加-t 4或者-threads 4指定线程数。实际加速比受限于CTU行之间依赖关系,开8线程时加速比大概在4到5倍,不会线性增长,但总比单线程强。
第二,减小测试序列。不要一上来就编码4K序列,先用BQMall_832x480_60或者BasketballPass_416x240_50这类小分辨率序列验证自己的想法,逻辑正确了再上高分辨率序列出实验数据。学术实验的标准就是多个分辨率序列都要有,但调试阶段没必要跟自己过不去。
第三,多跑intra配置。如果研究内容是帧内编码,intra配置比randomaccess配置快不少,因为不需要做运动估计的搜索。
5.3 解码出来的YUV无法用播放器打开
常见原因是把解码输出文件当成了可用播放器直接渲染的格式。VTM解码器输出的是裸YUV,不带任何容器头,播放器不知道宽高、帧率、色彩格式。用FFplay播放时需显式指定参数:
ffplay -f rawvideo -pixel_format yuv420p -video_size 1920x1080 -framerate 50 rec.yuv如果想直接肉眼检查画质,可以在编码前用-v参数把重建帧dump出来,或者用脚本把PSNR串联打印成表格。比较省力的做法是把rec.yuv转成MP4再逐个帧对比:
ffmpeg -f rawvideo -pixel_format yuv420p -video_size 1920x1080 -framerate 50 -i rec.yuv -c:v libx264 rec.mp45.4 编码器输出文件比预期大得多
如果你发现和论文里报告的码率完全对不上,检查两步。第一步,确认输入YUV是否连续可预测,纯随机噪声即使VTM也压不了多少;第二步,确认没有在命令行或者配置里误写了极大的QP范围或者GOPSize不匹配的问题。正常情况下,固定QP 32编码1080p序列,random access配置的码率大概在几Mbps到十几Mbps量级,如果出来码率是几百Mbps,大概率是输入格式不对或者帧率配置不对。
还有一种情况是编码器把文件当成10bit输入处理了,而实际YUV是8bit数据。此时色度分量会在变换量化阶段产生大量无效能量,导致码率异常高,同时画面会出现奇怪的颜色噪声。检查InputBitDepth是否正确。
5.5 码流可用但码率不是目标值
如果你用了固定QP却在期望某个具体码率,必须理解VTM不是按码率搜索QP的。固定QP模式只管量化步长,不管最终码率是多少。反过来,设置了RateControl : 1但没设TargetBitrate,VTM会用默认目标值,大概率不是你想的那个,一定要显式设置。
5.6 PSNR变成负数或者NaN
基本可以断定是YUV数据位宽或色彩格式配置错误。一个不那么常见的坑是输入YUV文件包含文件头(比如rawvideo里有的会带几百字节的头部信息),VTM不知道有头,会把文件头当像素,导致后续解码结果完全错乱。用工具切掉文件头再喂给编码器即可。
5.7 VTM6.0的Direct模式输出有误
如果你是直接在命令行里不指定任何配置文件,只靠-i input.yuv去裸编码,VTM会使用内置默认参数,但内置默认的FramesToBeEncoded是0,编码器会一直读到文件末尾,可能读到一批不完整帧。正确做法是必须指定至少一个cfg文件。
6. 从安装到研究:VTM6.0还能怎么玩
6.1 用它验证论文中的编码工具
VTM6.0的核心价值是作为实验平台验证新工具的性能。如果你想测试一个自定义的帧内预测模式,改动点通常在IntraPrediction.cpp、CodingUnit.cpp、Slice.cpp这些文件里。建议在动手改代码前,先跑通编码器并记录一份baseline结果。具体做法是:
./EncoderApp -c encoder_intra_vtm.cfg -c seq.cfg -q 22 -b baseline_q22.bin记录日志中输出的总码率和平均PSNR。之后改完代码重新编译、重新编码、再算RD增益,就能快速评价改动是好是坏。
6.2 在VTM中临时禁用某个工具
研究时有个常见的需求:验证某个工具在整体编码增益中的贡献。VTM6.0的配置文件中每个工具基本都有开关,比如ISP、MRL、PDPC、Affine、AMVR、BDOF等,把对应开关从1改成0再编码一次,对比两次的BD-Rate即可得出结论。这比改代码调试方便得多,也是VTM做得比较友好的地方之一。
6.3 配合其他工具做客观指标评估
VTM输出的PSNR是逐帧算的,但很多论文还需要SSIM、VMAF等指标。这类指标通常不需要VTM支持,可以把解码重建的YUV和原始YUV导出后用其它工具计算,例如用FFmpeg计算SSIM:
ffmpeg -f rawvideo -pixel_format yuv420p -video_size 1920x1080 -framerate 50 -i orig.yuv -f rawvideo -pixel_format yuv420p -video_size 1920x1080 -framerate 50 -i rec.yuv -lavfi ssim -f null -6.4 搭建批处理的实验脚本
做研究不是编一两个序列就够的,一般要在多个QP点、多个序列、多个配置下重复实验。我习惯用shell脚本维护批量任务,核心结构如下:
#!/bin/bash for qp in 22 27 32 37; do for seq in BasketballDrive BQTerrace Cactus; do ./EncoderApp -c encoder_randomaccess_vtm.cfg \ -c ${seq}.cfg -q ${qp} -b ${seq}_q${qp}.bin >> log_${seq}_q${qp}.txt done done跑完后用grep提取每帧的PSNR和总字节数,汇总成表格。注意:批量跑的时候每个编码实例的线程数不要设太高,避免多个编码任务抢占CPU,反而拖慢整体吞吐。我的经验是单实例设4线程,同时跑3个实例,总耗时反而比全部占满CPU更短。
6.5 一个容易被忽略的操作习惯:保存配置快照
VTM版本更新频繁,而且同一版本不同时间跑出来的结果可能因为编译器优化选项不同而有细微差异。写论文时需要精确报告实验条件,除了记录VTM版本号,也要把完整配置文件和编译参数随结果一起存档。这不是科研洁癖,是负责任的实验习惯,真到审稿人要求复现实验的时候,一份完整的配置记录能省掉大量沟通成本。
VTM6.0虽老,但作为理解H.266/VVC技术脉络的起点,它比新版本更友好。把这套安装和使用流程吃透,后面的编码工具研究、跨版本性能对比、甚至自研编码器开发,都能顺畅衔接起来。