/dev/dri/renderD128、/mnt/video-input 和 registry.example.internal。实际环境要按自己的 GPU 设备、挂载路径和镜像仓库调整。0. 结论和边界#
这份记录适合把一批 .mp4 审计视频压缩成更小体积,并尽量复用 AMD GPU 的 VAAPI 编码能力。
处理逻辑:
- 系统安装 AMD firmware、Mesa VA 驱动、
vainfo、ffmpeg。 - 通过
vainfo检查 HEVC / H.264 硬件编码入口。 - 脚本默认跳过小文件,其他文件按体积和分辨率动态选择 QP。
- 音频直接
copy,只压缩视频流。 - 容器运行时把
/dev/dri映射进去,保证 ffmpeg 能访问 render node。
边界:
- VAAPI 编码质量和可用 profile 取决于 GPU、驱动和 Mesa 版本。
- QP 不是码率上限,不能保证每个文件压缩到固定体积。
- 脚本默认只处理当前目录下的
.mp4。 - 如果源视频体积很小或已经是低码率,继续压缩可能只会增加画质损失。
- 脚本不覆盖源文件,输出统一写入
compressed/;源文件保留到抽样验收完成后再处理归档。
Review Gate:
| 检查项 | 命令 | 继续条件 | 停止条件 |
|---|---|---|---|
| GPU 设备 | ls -l /dev/dri/ | 有可访问的 renderD* | 没有 render node |
| VAAPI 编码 | vainfo --display drm --device /dev/dri/renderD128 | 有 H.264 或 HEVC EncSlice | vainfo 报错或无编码入口 |
| ffmpeg | ffmpeg -encoders | grep vaapi | 有 VAAPI encoder | ffmpeg 不支持 VAAPI |
| 输入目录 | find "$SRC_DIR" -maxdepth 1 -name '*.mp4' | 能找到待处理文件 | 没有输入文件 |
| 输出空间 | df -h "$OUT_DIR" | 空间足够存放输出 | 空间不足 |
1. 驱动和 VAAPI 检查#
安装依赖:
sudo apt update
sudo apt install -y firmware-amd-graphics mesa-va-drivers vainfo vdpauinfo ffmpeg
确认设备存在:
ls -l /dev/dri/
一般至少能看到:
card0
renderD128
检查 VAAPI:
vainfo --display drm --device /dev/dri/renderD128
如果输出里看到类似入口,说明硬件编码可用:
VAEntrypointEncSlice
VAProfileH264
VAProfileHEVCMain
如果 vainfo 报权限问题,先检查当前用户是否在 video 或 render 组里,容器场景还要检查设备映射。
2. 压缩脚本#
脚本会优先检测 HEVC,失败后回退 H.264;默认跳过小于 50MB 的文件,其他文件按体积和分辨率调 QP。如果确实要处理小文件,显式设置 COMPRESS_SMALL_FILES=1。
#!/usr/bin/env bash
set -euo pipefail
SRC_DIR="${SRC_DIR:-.}"
OUT_DIR="${OUT_DIR:-./compressed}"
VA_DEVICE="${VA_DEVICE:-/dev/dri/renderD128}"
COMPRESS_SMALL_FILES="${COMPRESS_SMALL_FILES:-0}"
mkdir -p "$OUT_DIR"
if vainfo --display drm --device "$VA_DEVICE" 2>/dev/null | grep -Eq "VAProfileHEVC(Main|Main10).*EncSlice(LP)?"; then
ENCODER="hevc_vaapi"
else
ENCODER="h264_vaapi"
fi
command -v ffmpeg >/dev/null
command -v ffprobe >/dev/null
sanitize_uint() {
case "$1" in
'' | *[!0-9]*) printf '0' ;;
*) printf '%s' "$1" ;;
esac
}
sanitize_resolution() {
case "$1" in
'' | *[!0-9x]* | *x*x* | x* | *x) printf '0x0' ;;
*) printf '%s' "$1" ;;
esac
}
for f in "$SRC_DIR"/*.mp4; do
[ -e "$f" ] || continue
base="$(basename "$f")"
output="$OUT_DIR/$base"
size_mb="$(du -m "$f" | awk '{print $1}')"
raw_bit_rate="$(ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -of default=nokey=1:noprint_wrappers=1 "$f" 2>/dev/null || true)"
raw_resolution="$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0:s=x "$f" 2>/dev/null || true)"
bit_rate="$(sanitize_uint "$raw_bit_rate")"
resolution="$(sanitize_resolution "$raw_resolution")"
width="${resolution%x*}"
height="${resolution#*x}"
bit_rate_bps="$bit_rate"
if [ "$bit_rate_bps" -eq 0 ]; then
printf 'skip=%s reason=missing-bitrate resolution=%s\n' "$base" "$resolution"
continue
fi
case "$width:$height" in
*[!0-9:]* | "" | *::*)
printf 'skip=%s reason=invalid-metadata resolution=%s bit_rate_bps=%s\n' "$base" "$resolution" "$bit_rate_bps"
continue
;;
esac
if [ "$width" -eq 0 ] || [ "$height" -eq 0 ]; then
printf 'skip=%s reason=missing-resolution\n' "$base"
continue
fi
if [ "$size_mb" -lt 50 ] && [ "$COMPRESS_SMALL_FILES" != "1" ]; then
printf 'skip=%s reason=small-file size_mb=%s bit_rate_bps=%s\n' "$base" "$size_mb" "$bit_rate_bps"
continue
fi
if [ "$size_mb" -lt 50 ]; then
# Small files are opt-in and use a conservative fixed QP.
# Do not add resolution penalties here unless sample playback proves it is needed.
qp=24
elif [ "$size_mb" -gt 150 ]; then
qp=30
else
qp=28
fi
if [ "$size_mb" -ge 50 ] && { [ "$width" -gt 1920 ] || [ "$height" -gt 1080 ]; }; then
qp=$((qp + 2))
fi
printf '\ncompressing=%s size_mb=%s resolution=%sx%s qp=%s encoder=%s\n' \
"$base" "$size_mb" "$width" "$height" "$qp" "$ENCODER"
ffmpeg -hide_banner -y \
-init_hw_device vaapi=va:"$VA_DEVICE" \
-filter_hw_device va \
-hwaccel_output_format vaapi \
-i "$f" \
-vf "format=nv12,hwupload" \
-c:v "$ENCODER" \
-rc_mode CQP \
-qp "$qp" \
-c:a copy \
"$output"
done
执行:
chmod +x compress-vaapi.sh
SRC_DIR=/mnt/video-input OUT_DIR=/mnt/video-output VA_DEVICE=/dev/dri/renderD128 ./compress-vaapi.sh
3. 容器内测试#
容器里跑 ffmpeg 时,需要把 /dev/dri 映射进去。
docker run --rm -it \
--entrypoint /bin/bash \
--device /dev/dri/renderD128:/dev/dri/renderD128 \
--device /dev/dri/card0:/dev/dri/card0 \
--group-add video \
-v /mnt/video-input:/mnt/video-input \
-v /mnt/video-output:/mnt/video-output \
registry.example.internal/devops/video-conversion:main
如果不确定 render node 编号,可以先映射整个目录:
docker run --rm -it \
--entrypoint /bin/bash \
--device /dev/dri:/dev/dri \
--group-add video \
-v /mnt/video-input:/mnt/video-input \
-v /mnt/video-output:/mnt/video-output \
registry.example.internal/devops/video-conversion:main
不建议长期使用 --privileged。除非是在排查阶段临时确认权限问题,否则优先精确映射设备。
4. NFS 输入目录#
如果视频在 NFS 上,先安装客户端:
sudo apt install -y nfs-common
挂载示例:
mkdir -p /mnt/video-input
mount -t nfs 192.0.2.31:/mnt/data/video /mnt/video-input
建议把批处理输出写到本地磁盘或另一个输出目录,避免边读边写同一个 NFS 路径导致排查困难。
5. 编码结果检查#
用 ffprobe 检查输出文件:
ffprobe -v error \
-select_streams v:0 \
-show_entries stream=codec_name,bit_rate,width,height,r_frame_rate \
-of default=nokey=1:noprint_wrappers=1 \
compressed/sample.mp4
输出示例:
h264
1920
1080
5/1
651583
还要对比压缩前后体积:
du -h sample.mp4 compressed/sample.mp4
抽样播放:
ffplay compressed/sample.mp4
6. 验证矩阵#
| 检查项 | 命令 | 通过标准 |
|---|---|---|
| GPU 设备 | ls /dev/dri | 有 renderD* |
| VAAPI | vainfo --display drm --device /dev/dri/renderD128 | 能看到 EncSlice |
| ffmpeg | ffmpeg -encoders | grep vaapi | 有 h264_vaapi 或 hevc_vaapi |
| 容器权限 | 容器内执行 vainfo | 能访问 render node |
| 输出文件 | ffprobe compressed/*.mp4 | codec、分辨率、帧率正常 |
| 体积变化 | du -h | 体积符合预期 |
7. 停止和回滚#
批量压缩的回滚原则是保留源文件、隔离输出目录、按批次验收。
停止当前批次:
pkill -f "ffmpeg.*vaapi" || true
删除未验收输出:
rm -rf ./compressed
如果 NFS 挂载异常,先确认没有 ffmpeg 进程还在读写,再卸载:
mountpoint -q /mnt/video-input && umount /mnt/video-input
如果 VAAPI 不可用,有两个选择:
- 先停止本批次,修驱动、权限或容器设备映射。
- 临时改用 CPU 编码跑小样本,确认业务上是否接受耗时和画质。
小文件建议默认跳过。上面的脚本把小于 50MB 的文件视为低收益样本,除非显式设置 COMPRESS_SMALL_FILES=1,否则不会二次压缩。显式处理小文件时固定使用 QP=24,并且不再叠加高分辨率惩罚,这是有意取舍:小文件优先保画质和降低二次压缩风险。只有在抽样播放确认高分辨率小文件仍然过大时,才单独调小文件策略。
8. 常见问题#
vainfo 看不到 HEVC 编码
脚本会回退到 h264_vaapi。如果必须使用 HEVC,先确认 GPU 型号、Mesa 版本和驱动支持。
容器内访问 /dev/dri 失败
检查 --device 映射、容器用户、video / render 组权限。排查时可以临时映射整个 /dev/dri。
压缩后画质不稳定
QP 是质量参数,不是体积目标。可以把阈值调低,例如把大文件 QP 从 30 改成 28,或者按业务保留周期拆分不同策略。
源文件已经是低码率
先用 ffprobe 看 bit_rate。低码率视频继续压缩收益很小,可能不值得处理。
