跳过正文
  1. 博客文章/

AMD VAAPI 审计视频压缩

·740 字·4 分钟·
DevOps Media AMD VAAPI FFmpeg Video Docker DevOps
Zayn
作者
Zayn
专注 Kubernetes、CI/CD、可观测性等云原生技术栈,记录生产环境中的实战经验与踩坑复盘。
目录
生产变更复盘 - 这篇文章属于一个选集。
9: 本文
审计视频通常数量多、码率高、保留周期长。AMD VAAPI 可以把压缩任务从 CPU 挪到 GPU 编码单元上,用更低的主机 CPU 成本完成批量转码。
示例使用 /dev/dri/renderD128/mnt/video-inputregistry.example.internal。实际环境要按自己的 GPU 设备、挂载路径和镜像仓库调整。

0. 结论和边界
#

这份记录适合把一批 .mp4 审计视频压缩成更小体积,并尽量复用 AMD GPU 的 VAAPI 编码能力。

处理逻辑:

  • 系统安装 AMD firmware、Mesa VA 驱动、vainfoffmpeg
  • 通过 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 EncSlicevainfo 报错或无编码入口
ffmpegffmpeg -encoders | grep vaapi有 VAAPI encoderffmpeg 不支持 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 报权限问题,先检查当前用户是否在 videorender 组里,容器场景还要检查设备映射。

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/drirenderD*
VAAPIvainfo --display drm --device /dev/dri/renderD128能看到 EncSlice
ffmpegffmpeg -encoders | grep vaapih264_vaapihevc_vaapi
容器权限容器内执行 vainfo能访问 render node
输出文件ffprobe compressed/*.mp4codec、分辨率、帧率正常
体积变化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,或者按业务保留周期拆分不同策略。

源文件已经是低码率

先用 ffprobebit_rate。低码率视频继续压缩收益很小,可能不值得处理。

生产变更复盘 - 这篇文章属于一个选集。
9: 本文

相关文章

用 Docker Compose 部署 ALLinSSL 并接入 Nginx 反向代理
·564 字·3 分钟
SRE SSL TLS Nginx DevOps +4
用 Docker Compose 部署 Draw.io 并接入 GitLab 存储
·1060 字·5 分钟
SRE Nginx DevOps Draw.io GitLab +4
生产级 DNS 集群从零部署:dnsdist 与 Technitium 三节点架构
·2755 字·13 分钟
SRE DNS DDNS SRE DevOps +4