halfsize / HDR
HDR 视频压完,为什么发灰?
在相册里明明很鲜艳。压了一遍之后,天空白成一片,人脸发灰,整段片子像蒙了一层纱。 这不是码率的问题,把码率调高也救不回来。
iPhone 到底录了什么
从 iPhone 12 开始,默认相机录的是 HLG HDR:10bit 像素、BT.2020 色域、
传递函数标记为 arib-std-b67。安卓旗舰大多录 HLG 或
PQ(smpte2084,也就是 HDR10 的曲线)。文件里存的不是「场景颜色」,
而是需要配合已知显示设备才能解释的 HDR 码值。
压缩工具会犯的两种错
- 保留 HDR 标记。编码降到 8bit
yuv420p,但 HDR 元数据还留着。 不支持 HDR 的播放器会把原始码值直接显示出来,看着又平又灰。 - 丢掉标记。像素转成了 8bit,元数据也删了,但传递函数从头到尾没转换过。 颜色偏移、对比度塌陷,而且不同播放器的显示结果还各不相同。
两种情况下文件都是合法的、能播的,只是看起来不对。而压缩的人通常会以为是码率给少了。
一个判断方法:如果调高码率颜色也回不来,那它从来就不是码率问题。
解法是一次色彩转换,不是一个勾选框
要把 HDR 放进普通的 8bit SDR 文件,必须先进线性光、压缩动态范围,再把结果重新标记成
BT.709。用 FFmpeg 的 zscale 和 tonemap 滤镜,这是一条滤镜链:
zscale=tin=arib-std-b67:min=bt2020nc:pin=bt2020:t=linear:npl=100,
format=gbrpf32le,
zscale=p=bt709,
tonemap=tonemap=hable:desat=0,
zscale=t=bt709:m=bt709:r=tv,
format=yuv420p
npl 是标称峰值亮度。HLG 是相对的,100 就够;PQ 是对着真实峰值做的,通常是
1000 尼特。所以 PQ 源片第一行要换成 tin=smpte2084:...:npl=1000。
顺序很关键。tone map 必须在缩放之前做,而且要在源片的传递函数上做。 先缩放,等于在 HDR 码值上重采样,然后再靠猜。
halfsize 从 0.1.9 起怎么做
探测阶段读 color_transfer,在 scale 滤镜之前插入对应的转换链,输出标记为
bt709。--dry-run 能直接看到:
$ npx halfsize IMG_2041.MOV --to 20mb --dry-run
IMG_2041.MOV
214 MB 38s 3840x2160 30.00 fps hevc
target 20.0 MB
plan H.264 1280x720 4.18 Mbps audio 128 kbps 2-pass
scale 3840x2160 → 1280x720
hdr HLG → SDR BT.709
画质检测也要走同一套。拿没处理过的 HDR 源片去和 SDR 输出比,会把传递函数的差异 当成「压缩损失」,VMAF 数字毫无意义。所以算 SSIM 和 VMAF 之前,参考帧同样要先 tone map。
检查你自己的文件
$ ffprobe -v error -select_streams v:0 \
-show_entries stream=color_transfer,color_primaries,color_space,pix_fmt \
-of default=nw=1 视频.mov
color_transfer=arib-std-b67 # HLG
color_primaries=bt2020
color_space=bt2020nc
pix_fmt=yuv420p10le
如果 color_transfer 是 smpte2084,那是 PQ。如果是
bt709,本来就是 SDR,以上都不适用。BT.2020 矩阵配 SDR 传递函数是广色域,
不是 HDR,重新编码不会有事。
「能保留 HDR 吗?」
halfsize 目前不保留。输出是 SDR BT.709,也就是 Discord、WhatsApp、微信、浏览器、 邮件客户端和大多数播放器真正能正确显示的那一版。要保留 HDR 需要另一套管线和一个支持 HDR 的 播放器;如果目的是「发到群聊里让人看得正常」,SDR 才是到处都对的那一版。
常见问题
为什么 iPhone 视频压完发灰?
源片是 HLG HDR。工具做出了 8bit 像素却没转换传递函数,于是要么在 SDR 像素上留着 HDR 标记,要么丢掉了播放器需要的元数据。两种都发灰。
把码率调高能修好吗?
不能。码率影响细节,不影响颜色。曲线错了,50 Mbps 出来还是灰的。
能保留 HDR 吗?
halfsize 目前不保留,会转成 SDR BT.709,也就是大多数软件能正确显示的那一版。保留 HDR 需要单独的管线。
怎么判断文件是不是 HDR?
用 ffprobe 看 color_transfer:arib-std-b67 是 HLG,smpte2084 是 PQ。
tone map 会损失画质吗?
它会主动改变观感:把 HDR 的高光压进 SDR 屏幕能显示的范围。替代方案不是「原汁原味」,而是一个发灰的文件。