← 返回文章

Jetson AGX Orin 视频硬件编解码 Benchmark:从单路转码到多路并发

记录在 Jetson AGX Orin 64GB 上使用 FFmpeg、GStreamer、NVDEC、NVENC 和 tegrastats 测试 1080p H.264 转码的过程、结果与 8 路并发稳定性边界。

#Jetson#AGX Orin#NVDEC#NVENC#GStreamer#FFmpeg

Jetson AGX Orin 视频硬件编解码 Benchmark:从单路转码到多路并发

最近我在 Jetson AGX Orin 64GB 上测试了一条完整的视频转码链路:读取 1080p H.264 电影文件,使用 Orin 的硬件解码器解码,再分别使用硬件 H.264 和 H.265 编码器输出。

这次测试最值得记录的不是一个看起来很大的数字,而是几个容易被混淆的事实:

  • GR3D_FREQ 0% 并不代表视频硬件没有工作;
  • CPU 只有约 9%~11%/核心,并不代表视频没有被处理;
  • 4 路测试可以稳定完成,但 8 路已经出现 decoder 初始化失败和 GStreamer 崩溃;
  • NVIDIA 资料中的“16 路”是特定条件下的视频引擎参考吞吐,不能直接等同于本文这条完整转码 pipeline 的稳定并发数。

本文把环境、命令、脚本、原始日志中的关键现象和最终结论整理在一起。所有性能数字都限定在这台机器、这份素材和这套软件栈下,不把一次实验外推成通用极限。

1. 先说结论

本次测试得到的最可靠结论是:

  1. GStreamer 的 nvv4l2decoder、nvv4l2h264enc 和 nvv4l2h265enc 确实走到了 Jetson 的硬件视频通路。成功 pipeline 的日志出现了 NvVideo: NVENC,tegrastats 也持续报告 NVDEC 和 NVENC 负载。
  2. 单路完整电影文件转码约为 20 倍实时,折算吞吐约 480~490 fps。这个测试使用了实际的 mp4mux 和 filesink。
  3. 在后续的 609.65 秒视频片段、输出丢进 fakesink 的多路测试中,1、2、4 路都完成;4 路总吞吐约 331 fps,约相当于 13.8 倍单路实时吞吐。
  4. H.264 输出和 H.265 输出在 1、2、4 路的总吞吐几乎一致,说明当前 pipeline 更像是先受到解码端限制,而不是 H.265 编码器先到顶。
  5. 8 路测试不能作为正式吞吐成绩。H.264 和 H.265 各有至少 2 个 pipeline 明确失败,日志中还出现 Host1x channel open failed、no valid frames found、malloc_consolidate 和 SIGSEGV。
  6. 本次测试没有证明 Orin 的官方多媒体规格极限,也没有证明 8 路一定是芯片层面的硬上限。它证明的是:在这份 Blu-ray H.264 素材、当前驱动和当前 GStreamer 并发方式下,4 路是已验证的稳定结果,8 路已经越过了当前软件栈的稳定边界。

2. 测试环境与库栈

2.1 硬件和素材

项目 本次测试值
设备 NVIDIA Jetson AGX Orin 64GB
输入文件 Real.Steel.2011.1080p.Bluray.x264.DTS-HDChina.mkv
输入编码 H.264 / AVC
输入分辨率 1080p
原片时长 7620 秒,约 2 小时 7 分钟
源帧率 24000/1001,约 23.976 fps
输出编码 H.264 或 H.265
输出目标码率 8,000,000 bit/s,约 8 Mbps

用于多路并发测试的是从原片中间截取的一段视频。脚本结果中的片段时长是 609.650 秒,而不是严格的 600 秒。这是因为使用 -c copy 截取时会受关键帧和容器时间戳影响,实际结果应以 results.csv 的 duration_sec 为准。

ffmpeg \
  -ss 00:45:00 \
  -i "Real.Steel.2011.1080p.Bluray.x264.DTS-HDChina.mkv" \
  -t 600 \
  -map 0:v:0 \
  -c copy \
  benchmark_10min.mkv

2.2 已确认的库版本

设备上的库版本截图记录如下:

库/组件 版本或状态 在本次 benchmark 中的角色
CUDA 12.6.68 通用 GPU 计算基础,但本次没有运行 CUDA 推理
cuDNN 9.3.0 深度学习算子库,本次视频转码未直接调用
TensorRT 10.3.0.30 推理优化引擎,本次视频转码未直接调用
VPI 3.2.4.0 Jetson 视觉处理接口,本次未作为独立处理步骤调用
Vulkan 1.3.204 图形/计算 API,本次未作为转码主路径
OpenCV 4.8.0 已安装,但本次 pipeline 没有使用 OpenCV
OpenCV CUDA NO 说明 OpenCV 没有编译 CUDA 模块,不等于系统没有 CUDA

这里要特别强调:这张库表是整台 Orin 的开发环境清单,不是说这次视频转码同时用到了 CUDA、cuDNN、TensorRT、VPI、Vulkan 和 OpenCV。本文的核心测试路径是 Jetson 的 GStreamer 多媒体插件和 tegrastats。

本次附件没有保留足够可靠的 JetPack/L4T 精确版本信息,所以本文不自行补填一个版本号。复现实验时,建议先把版本记录下来:

cat /etc/nv_tegra_release
dpkg-query --show nvidia-l4t-core
gst-inspect-1.0 --version
ffmpeg -version

3. FFmpeg 和 GStreamer 能力确认

3.1 FFmpeg 硬件 decoder

首先检查 FFmpeg 的 decoder 列表:

ffmpeg -decoders | grep -Ei 'nvv4l2|h264|hevc'

这台设备的输出中出现了:

h264_nvv4l2dec
hevc_nvv4l2dec

同时也能看到普通的软件 decoder:

h264
hevc

以及通用 V4L2 M2M wrapper:

h264_v4l2m2m
hevc_v4l2m2m

它们不是同一个层次的接口。对于 Jetson 专用的 FFmpeg 硬件 decoder,重点关注的是 h264_nvv4l2dec 和 hevc_nvv4l2dec。不过,decoder 出现在列表中只说明 FFmpeg 构建包含了对应实现;真正判断运行时是否走到硬件,还应结合实际负载和 tegrastats。

不生成巨大的 YUV 文件时,可以这样做一个最小验证:

ffmpeg \
  -c:v h264_nvv4l2dec \
  -i input_h264.mp4 \
  -f null -

H.265 的写法类似:

ffmpeg \
  -c:v hevc_nvv4l2dec \
  -i input_h265.mp4 \
  -f null -

3.2 FFmpeg encoder 和 GStreamer encoder 要分开看

本次保存的 FFmpeg encoder 列表中可以看到:

h264_omx
h264_v4l2m2m
hevc_v4l2m2m
libx264
libx265

在这份输出里没有看到 h264_nvv4l2enc 或 hevc_nvv4l2enc。因此,本次实际编码 benchmark 没有依赖 FFmpeg 的 encoder wrapper,而是直接使用 Jetson GStreamer 插件:

gst-inspect-1.0 nvv4l2decoder
gst-inspect-1.0 nvv4l2h264enc
gst-inspect-1.0 nvv4l2h265enc
gst-inspect-1.0 nvvidconv

这也是为什么要把“FFmpeg 是否支持硬件解码”和“GStreamer 是否能调用硬件编码器”分成两个问题验证。一个 FFmpeg 安装可以有 Jetson decoder,却不一定带有同名的 GStreamer encoder 能力;反过来也一样。

4. 测试 pipeline 与脚本方法

4.1 单路实际文件输出

最初的单路测试使用真实文件输出,以 H.265 为例:

gst-launch-1.0 -e \
  filesrc location="Real.Steel.2011.1080p.Bluray.x264.DTS-HDChina.mkv" \
  ! matroskademux name=demux \
  demux.video_0 \
  ! h264parse \
  ! nvv4l2decoder \
  ! nvvidconv \
  ! 'video/x-raw(memory:NVMM),format=NV12' \
  ! nvv4l2h265enc bitrate=8000000 \
  ! h265parse \
  ! mp4mux \
  ! filesink location=Real.Steel.2011_h265.mp4

输出 H.264 时,把 encoder 和后面的 parser 换成:

nvv4l2h264enc
...
h264parse

这类测试更接近实际“转出一个文件”的应用,但它会把 mux、文件系统和磁盘写入也纳入耗时。因此,它和后面的纯吞吐多路测试不能直接当成完全相同的实验条件。

4.2 多路并发 benchmark

为了减少 SSD 写入和 MP4 mux 对结果的影响,多路 benchmark 把编码后的 bitstream 送进 fakesink,不落盘、不处理音频:

filesrc
  → matroskademux
  → h264parse
  → nvv4l2decoder
  → nvvidconv
  → NVMM / NV12
  → nvv4l2h264enc 或 nvv4l2h265enc
  → h264parse 或 h265parse
  → fakesink

H.264 输出的核心命令是:

gst-launch-1.0 -q -e \
  filesrc location="$INPUT" \
  ! matroskademux \
  ! h264parse \
  ! nvv4l2decoder \
  ! nvvidconv \
  ! 'video/x-raw(memory:NVMM),format=NV12' \
  ! nvv4l2h264enc bitrate=8000000 \
  ! h264parse \
  ! fakesink sync=false

H.265 输出只需要替换编码器和 parser:

gst-launch-1.0 -q -e \
  filesrc location="$INPUT" \
  ! matroskademux \
  ! h264parse \
  ! nvv4l2decoder \
  ! nvvidconv \
  ! 'video/x-raw(memory:NVMM),format=NV12' \
  ! nvv4l2h265enc bitrate=8000000 \
  ! h265parse \
  ! fakesink sync=false

脚本对每一个 codec 和并发数做以下事情:

  1. 启动 tegrastats,每秒采样一次;
  2. 同时启动 N 个相同的 GStreamer pipeline;
  3. 等待这一组 pipeline 全部退出;
  4. 根据片段时长和墙钟耗时计算倍速、总 FPS 和单路 FPS;
  5. 保存每组原始 tegrastats 和 GStreamer 日志;
  6. 只在整组 pipeline 都成功时写入 results.csv。

一次完整运行的调用方式是:

chmod +x orin_video_benchmark.sh

CONCURRENCY="1 2 4 8" \
CODECS="h264 h265" \
./orin_video_benchmark.sh benchmark_10min.mkv

脚本里启动监控的核心形式如下:

sudo tegrastats \
  --interval 1000 \
  --logfile "$TEGRALOG" &
TEGRA_PID=$!

sleep 2
START_NS=$(date +%s%N)

PIDS=()
for ((i=1; i<=streams; i++)); do
  run_pipeline "$codec" "$i" \
    "$RESULT_DIR/gstreamer_${NAME}_${i}.log" &
  PIDS+=( "$!" )
done

FAIL=0
for pid in "${PIDS[@]}"; do
  wait "$pid" || FAIL=1
done

END_NS=$(date +%s%N)
sudo kill "$TEGRA_PID" 2>/dev/null || true

这里的 done 前面省略了脚本中不同输出 codec 的 pipeline 分支;关键点是并发启动、统一计时和逐路等待。

4.3 指标计算

脚本的计算关系是:

每路实时倍速 = 片段时长 / 并发组耗时
总实时倍速   = 每路实时倍速 × 并发路数
单路 FPS     = 源帧率 × 每路实时倍速
总 FPS       = 单路 FPS × 并发路数

例如 4 路 H.265:

609.650 / 176.599 ≈ 3.452× 每路实时
3.452 × 4         ≈ 13.809× 总实时
23.976 × 13.809   ≈ 331.077 fps 总吞吐

“总实时倍速”是把多路吞吐折算成一个单路实时倍数,不应直接写成“13.8 路稳定并发”。稳定并发数仍然要看每一路是否能完整结束、是否发生错误,以及应用是否还需要音频、mux、写盘和后处理。

5. 单路完整电影结果

单路日志来自最初的完整文件输出测试。输入时长为 7620 秒,源帧率为 23.976 fps;耗时来自 time/日志对应的有效转码区间。

输入 → 输出 视频时长 转码耗时 实时倍速 平均吞吐 CPU 平均/核心 NVDEC 平均 NVENC 平均 CPU 温度平均
H.264 → H.264 7620 s ≈372 s ≈20.48× ≈491 fps ≈9.7% ≈98% ≈78% ≈53.3°C
H.264 → H.265 7620 s ≈380 s ≈20.05× ≈481 fps ≈8.9% ≈96% ≈83% ≈52.3°C

功耗日志里比较醒目的是:

输入 → 输出 VIN_SYS_5V0 平均值 VIN_SYS_5V0 峰值
H.264 → H.264 ≈5.39 W ≈5.63 W
H.264 → H.265 ≈5.19 W ≈5.43 W

这里的 VIN_SYS_5V0 是一个电源 rail,不是整块 AGX Orin 或整块载板的总功耗。不能把它写成“Orin 转码只耗 5.2 W”。VDD_GPU_SOC、VDD_CPU_CV 和 VIN_SYS_5V0 的统计范围也不能简单相加来推导整机功耗。

从单路结果看,H.265 编码只比 H.264 编码多用了约 8 秒,整体差异约 2%。与此同时,H.264→H.265 的 NVENC 平均值略高,但 NVDEC 仍然处于 96% 以上,已经是更值得关注的瓶颈。

6. 多路并发结果:1、2、4 路有效

以下数据直接来自结果压缩包中的 results.csv。这组测试使用 609.650 秒片段,输出进入 fakesink,不写 MP4。

输出编码 并发 耗时 每路实时倍速 总实时倍速 总吞吐 单路平均 FPS
H.264 1 89.035 s 6.847× 6.847× 164.172 fps 164.172 fps
H.264 2 120.778 s 5.048× 10.095× 242.047 fps 121.023 fps
H.264 4 176.777 s 3.449× 13.795× 330.743 fps 82.686 fps
H.265 1 88.774 s 6.867× 6.867× 164.653 fps 164.653 fps
H.265 2 120.293 s 5.068× 10.136× 243.023 fps 121.512 fps
H.265 4 176.599 s 3.452× 13.809× 331.077 fps 82.769 fps

这张表有两个明显特征。

第一,总吞吐随着并发增加而增加,但不是线性增加:

1 路 → 2 路:约 164 fps → 242 fps
2 路 → 4 路:约 242 fps → 331 fps

这说明增加 pipeline 确实可以提升总吞吐,但共享的视频硬件资源已经开始产生竞争,不能简单用单路成绩乘以路数。

第二,H.264 和 H.265 的结果非常接近。4 路时:

H.264 输出:330.743 fps
H.265 输出:331.077 fps

在这份数据的精度下,两者几乎没有可见差别。这并不意味着 H.264 和 H.265 在所有编码参数下成本都相同,而是说明这次实验里,编码器差异被前端解码吞吐和共享 pipeline 开销掩盖了。

7. 4 路时硬件和 CPU 到底在做什么

从 4 路有效日志中提取的活跃采样如下:

指标 H.264 输出 H.265 输出
CPU 平均占用/核心 11.03% 11.20%
NVDEC 平均 98.61% 98.86%
NVENC 平均 82.48% 87.29%
CPU 温度平均 54.8°C 54.7°C
VIN_SYS_5V0 平均 ≈5.43 W ≈5.35 W

典型的 tegrastats 行大致是:

CPU [10%@729,10%@729,4%@729,...]
GR3D_FREQ 0%@[305,305]
NVENC 84%@998
NVDEC 99%@998
VIC off

它可以这样读:

  • CPU […] 里面是一组 CPU 核心的占用率和频率,不是一个单独的“整机 CPU 百分比”;
  • GR3D_FREQ 是通用 GPU/3D 引擎的统计口径,不包含所有专用多媒体引擎;
  • NVDEC 是视频解码引擎的采样,NVENC 是视频编码引擎的采样;
  • VIC off 表示采样时没有看到 VIC 正在忙,不代表 nvvidconv 这个元素没有出现在 pipeline 中;
  • VIN_SYS_5V0 仍然只是一个 rail。

7.1 为什么 CPU 占用低

CPU 主要承担的是“组织工作”:

读取文件
demux
解析码流
GStreamer 调度
时间戳和线程同步
buffer 管理
驱动调用

而视频像素处理里最重的部分,例如:

H.264/H.265 熵解码
运动补偿
帧重建
变换和量化
熵编码

由专用视频硬件完成。只要 pipeline 没有中途把帧转成普通 CPU 内存再用软件 codec 处理,CPU 就不需要逐像素执行这些工作。

因此,看到每核心约 10%~11% 的 CPU 占用是合理的。12 个核心的平均值如果是 11%,对应的是大约 1.3 个核心的平均忙碌量,而不是 11 个核心满载。

在一次 6 路运行的现场观察中也看到过“CPU 约 20%、GR3D 为 0%”的组合。这个观察和上面的机制是一致的,但那次没有与 CSV 同样完整的计时和日志,所以这里只把它作为现象说明,不把它加入正式性能表。

7.2 为什么 GR3D 可以是 0%

Orin 里面不只有一块“负责所有事情的 GPU”。视频转码主要经过的是:

               Jetson AGX Orin SoC

CPU ───────┐
           ├─ demux / 调度 / buffer 管理
           │
NVDEC ────┼─ H.264/H.265 解码
           │
NVMM ─────┼─ 适合 Jetson 多媒体通路的帧缓冲
           │
VIC ──────┼─ 必要时做格式转换、缩放、合成
           │
NVENC ────┼─ H.264/H.265 编码
           │
GR3D ─────┴─ 通用 GPU/3D/CUDA 计算,本文没有让它承担转码主任务

所以这次同时看到:

GR3D_FREQ 0%
NVDEC     96%~99%
NVENC     78%~87%
CPU       约 9%~11%/核心

并不矛盾。更准确的说法是:通用 GPU 统计口径接近空闲,但视频编解码专用引擎正在高负载工作。

nvvidconv 也不能简单等同于“必然使用 CUDA GPU”。它可以根据内存类型和转换需求选择合适的路径。本文没有做缩放,也没有强制 RGB 等复杂格式转换;日志里 VIC off,说明这次 nvvidconv 很可能只是完成了必要的协商/路径衔接,没有产生持续的 VIC 负载。

8. 8 路为什么不能算正式成绩

8.1 H.264 8 路

8 个 pipeline 中,至少第 5 路和第 7 路出现了明确错误:

InitNVDEC: Host1x channel open failed
Failed to get NVDEC Channel handle
Failed to process frame
ERROR: pipeline doesn't want to preroll.
no valid frames found
malloc_consolidate(): unaligned fastbin chunk detected

其他一些 pipeline 看起来可以启动并持续产生硬件负载,但整组测试已经不是“8 路全部完成”,所以脚本没有把 h264,8 写进 results.csv。

8.2 H.265 8 路

H.265 8 路的第 4 路和第 8 路出现了同一类 NVDEC 初始化/处理错误,并进一步出现:

Caught SIGSEGV
malloc_consolidate(): invalid chunk size

这次运行中,tegrastats 总共记录了 1845 个采样点,但只有前 342 个采样点仍能看到数值化的 NVDEC/NVENC 活动;后面大量采样属于 pipeline 卡住或结束后的闲置阶段。把整份日志混在一起求平均,会得到一个看起来很低、但没有代表性的 CPU 或视频引擎占用率。

在仍然活跃的 342 个采样点中,大致看到:

CPU/core  ≈ 11.2%
NVDEC     ≈ 98.5%
NVENC     ≈ 86.2%

这些数字只能说明“失败发生前,已经有视频引擎在高负载运行”,不能说明 8 路已经稳定完成,更不能用来计算 8 路总 FPS。

8.3 原因应该怎样判断

从日志证据看,比较稳妥的判断是:8 路并发触发了 decoder session、Host1x/NVDEC 通道、buffer 或软件栈并发处理方面的问题,随后 GStreamer/NvMMLite 的错误清理又暴露出内存分配错误和段错误。

但目前没有足够证据把根因进一步锁定为某一项:

  • 可能是硬件 decoder session/通道资源在当前条件下无法再创建;
  • 可能是当前 JetPack、驱动或 GStreamer 插件的并发稳定性问题;
  • 也可能和输入码流、buffer 配置、内存碎片或启动时序有关。

日志里的 Host1x channel open failed 支持“资源/驱动通道获取失败”的判断,但它本身不能证明“Orin 芯片最多只能 6 路”。malloc_consolidate 和 SIGSEGV 更像是失败路径上的软件栈问题,而不是一个干净、可解释的硬件吞吐上限。

这次脚本没有给每个 pipeline 设置 watchdog,因此 H.265 8 路出现了长时间等待,最后只能强制终止。以后要继续测临界点,应给每一路或每一组加超时,并在任一路异常时自动回收整个测试组。

9. “16 路”到底该怎样理解

NVIDIA 的 Jetson AGX Orin Developer Kit Reviewer’s Guide 在规格表中列出了视频引擎的参考吞吐,例如 16× 1080p30 H.265 视频编码和 22× 1080p30 H.265 视频解码。

这些数字应该理解为:

特定分辨率
特定帧率
特定 codec/profile
特定质量和码率条件
特定测试路径

下的产品级参考能力。它们不是下面这件事的直接承诺:

任意 Blu-ray H.264
→ demux
→ 硬解
→ NVMM
→ 转换
→ 硬编 H.265
→ 多进程并发
→ 还要稳定运行并写出文件

因此,达到这个“16 路”不需要额外加一块外部视频解码模块;NVDEC 和 NVENC 本来就是 Orin SoC 内的视频硬件单元。只是官方参考吞吐和本文的完整应用 pipeline 不是同一种测试。

如果要严谨对齐官方规格,应该另做三类实验:

decode-only
encode-only
decode + encode transcode

并使用固定的 1080p30 测试源、固定 profile、固定码率、尽量简单的 pipeline,同时排除磁盘和 mux 影响。本文的结果更接近“真实文件输入下,这套 Jetson GStreamer 链路能稳定跑到哪里”。

10. 参考实现和复现注意事项

NVIDIA 的 Accelerated GStreamer 文档 给出了 nvv4l2decoder、nvv4l2h264enc、nvv4l2h265enc 和 nvvidconv 的使用方式;R36.4.3 的 tegrastats 文档 说明了 NVDEC、NVENC、CPU、温度和电源 rail 的统计字段;V4L2 Video Encoder API 则记录了 Jetson 编码设备和支持的像素格式。

复现时,至少要记录以下信息:

# 输入时长
ffprobe -v error \
  -show_entries format=duration \
  -of default=noprint_wrappers=1:nokey=1 \
  benchmark_10min.mkv

# 输入帧率
ffprobe -v error \
  -select_streams v:0 \
  -show_entries stream=avg_frame_rate \
  -of default=noprint_wrappers=1:nokey=1 \
  benchmark_10min.mkv

# 插件是否存在
gst-inspect-1.0 nvv4l2decoder
gst-inspect-1.0 nvv4l2h264enc
gst-inspect-1.0 nvv4l2h265enc
gst-inspect-1.0 nvvidconv

# Jetson 的实时状态
sudo tegrastats --interval 1000 --logfile tegrastats.log

还要注意几个会显著影响结果的变量:

  • JetPack/L4T、内核和 NVIDIA multimedia library 版本;
  • nvpmodel 功耗模式与时钟是否锁定;
  • H.264/H.265 的 profile、参考帧、B 帧和码率;
  • 是否做 resize、颜色空间转换、滤镜或 AI 推理;
  • 是否写 SSD、是否处理音频、是否进行 MP4 mux;
  • 多路 pipeline 是多进程、多个线程,还是一个统一的 GStreamer graph;
  • 是否给失败和卡死增加超时回收机制。

其中任何一项不同,都可能让结果发生变化。

11. 最终结论

这次 benchmark 最重要的收获,是把“GPU 负载低”和“视频硬件没有工作”这两个概念分开了。

在本次 AGX Orin 64GB 测试中:

CPU        ≈ 9%~11%/核心
GR3D       ≈ 0%
NVDEC      ≈ 96%~99%
NVENC      ≈ 78%~87%

这正是专用视频流水线工作的样子:CPU 负责调度,NVDEC 负责解码,必要时由 VIC 做图像转换,NVENC 负责编码,通用 CUDA/GR3D GPU 不需要参与主要的像素编解码工作。

就这份素材和当前软件栈而言,最可信的性能结论是:

单路完整文件:约 20× 实时,约 480~490 fps
多路 fakesink:1、2、4 路完成
4 路总吞吐:约 331 fps
8 路:出现 decoder/session/软件栈异常,不计入正式吞吐

所以,本文不会把“官方 16 路参考规格”写成“这台机器已经实测稳定 16 路”,也不会把“8 路启动后有部分 pipeline 活跃”写成“8 路 benchmark 成功”。对工程实践来说,能明确区分已验证结果、诊断信息和官方参考值,比一个未经条件限定的峰值数字更有用。

Conversation

留言

加载中

正在读取留言…

Leave a note

留下你的想法

留言提交后会先进入审核,审核通过才会公开显示。