M4 硬件加速批量转码:234 个 WMV/RM 转 MP4 实践
M4 硬件加速批量转码:234 个 WMV/RM 转 MP4 实践

NAS 中有 234 个早期 WMV/RM 视频,总计约 42.8GB、78.5 小时。目标是在不改动源目录的前提下转换为兼容性更好的 MP4,同时控制体积、画质、功耗,并让长时间任务能够安全中断和恢复。
最终选择是 h264_videotoolbox -q:v 50:720p 样本中速度约为 x264 的两倍,输出体积接近 x264 CRF 27,SSIM 只相差约 0.008,持续转码时主机也明显安静。
先盘点,不要直接批量运行
源文件包含 WMV3、RealVideo 4,以及不同分辨率和音频格式。先用 ffprobe 获取真实分布:
ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,width,height,r_frame_rate,duration,bit_rate \
-of default=noprint_wrappers=1 sample.wmv批量扫描时应记录文件名、编码、分辨率和时长。这样可以先发现没有音轨、异常时长或特殊编码的文件,而不是运行数小时后才发现整批参数不兼容。
用真实片段选择编码器
从真实文件中截取 60 秒,同时比较体积、速度、SSIM 和 CPU 负载:
ffmpeg -ss 200 -t 60 -i sample.wmv \
-c:v libx264 -preset medium -crf 27 \
-c:a aac -b:a 96k -movflags +faststart \
-y /tmp/x264.mp4
ffmpeg -ss 200 -t 60 -i sample.wmv \
-c:v h264_videotoolbox -q:v 50 \
-c:a aac -b:a 96k -movflags +faststart \
-y /tmp/videotoolbox.mp4
ffmpeg -i sample.wmv -i /tmp/videotoolbox.mp4 \
-lavfi "ssim" -f null -本次 720p 样本结果如下:
| 方案 | 60 秒体积 | 速度 | SSIM | 负载 |
|---|---|---|---|---|
| x264 CRF 27 | 12.1MB | 7.2x | 0.939 | CPU 高负载 |
| H.264 VideoToolbox q50 | 11.5MB | 15.6x | 0.931 | 主要由媒体引擎承担 |
HEVC VideoToolbox 在本组老视频和所测参数下反而生成了更大的文件,因此没有采用。这个结果不能简单推广到所有素材:硬件编码器、输入噪声和质量参数都会改变结论,批处理前的片段基准才是可靠依据。
另一个容易踩坑的地方是参数方向。x264 的 CRF 越小质量越高,而 VideoToolbox 的 -q:v 越大质量越高、体积也越大。换编码器后不能沿用原来的参数直觉。
Apple Silicon 的实际流水线
这里并不是“GPU 把所有工作都做了”。M4 没有 WMV3/RV40 硬件解码器,因此实际链路是:
WMV3 / RV40 软件解码
↓
VideoToolbox 调用 Apple 媒体引擎编码 H.264
↓
AAC 音频编码与 MP4 封装旧视频分辨率不高,软件解码开销有限;最重的编码阶段交给独立媒体引擎后,持续负载和噪声显著下降。
可恢复的批量脚本
下面的脚本具备四个关键特性:不覆盖源文件、已完成文件跳过、先写临时文件、成功后原子改名。
#!/bin/bash
set -u
SRC="/Volumes/NAS/legacy-video"
DST="/Volumes/NAS/legacy-video-mp4"
LOG="/tmp/video-convert.log"
mkdir -p "$DST"
name="$1"
base="${name%.*}"
out="$DST/${base}.mp4"
tmp="$out.part"
if [ -f "$out" ]; then
echo "$(date '+%F %T') SKIP $name" >> "$LOG"
exit 0
fi
rm -f "$tmp"
if nice -n 10 ffmpeg -v error -nostdin -i "$SRC/$name" \
-map 0:v:0 -map 0:a:0? \
-c:v h264_videotoolbox -q:v 50 \
-c:a aac -b:a 96k \
-movflags +faststart -f mp4 -y "$tmp" 2>> "$LOG"; then
mv "$tmp" "$out"
echo "$(date '+%F %T') OK $name" >> "$LOG"
else
rm -f "$tmp"
echo "$(date '+%F %T') FAIL $name" >> "$LOG"
fi.part 后缀不能让 ffmpeg 推断容器,因此必须显式添加 -f mp4。否则会报 Unable to choose an output format,并导致整批任务立即失败。
两路并行运行:
find "$SRC" -maxdepth 1 -type f \
\( -iname '*.wmv' -o -iname '*.rm' \) -print0 |
xargs -0 -n 1 -P 2 basename |
xargs -n 1 -P 2 /tmp/convert-one.sh如果文件名可能包含换行符,最好将脚本改为直接接收完整路径,避免 basename 管道破坏边界。普通文件名环境下,上述方式更容易阅读。
结果验证不能只看退出码
旧 WMV 常带有坏包,日志中可能出现 Bits overconsumption、spectral RLE 或无效 packet 警告。ffmpeg 经常会跳过坏帧并继续完成输出,所以应把“源文件警告”和“任务失败”分开判断。
至少检查以下项目:
grep -c ' OK ' /tmp/video-convert.log
grep -c ' FAIL ' /tmp/video-convert.log
find "$DST" -name '*.part' -print
ffprobe -v error -show_entries format=duration \
-of default=noprint_wrappers=1 "$DST/example.mp4"还应抽样播放开头、中间和结尾,并比较源文件与输出时长。若任务中断,先确认旧进程已经退出,再清理 .part 文件并重启,避免旧日志和新日志混在一起造成错误统计。
总结
- 大批量转码前,先用真实片段做参数矩阵,而不是凭经验选编码器。
- Apple Silicon 的 VideoToolbox 使用独立媒体引擎;它不是通用 GPU 编码的同义词。
-q:v与 CRF 的数值方向不同,应先小范围扫描。- 批处理必须同时设计断点续传、半成品隔离、结构化日志和输出验证。
- 源文件有损坏警告不代表任务一定失败,但输出必须通过时长与抽样播放检查。
这次任务最终保持源目录只读,234 个文件全部在新目录生成 MP4;相较持续占满 CPU 的 x264 方案,硬件编码更适合这种持续数十小时的家庭媒体归档任务。
