ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Jetson 硬件编码实战:NVENC 加速 H.264/H.265 视频编码与推流

Jetson 硬件编码实战:NVENC 加速 H.264/H.265 视频编码与推流 1. 为什么要在 Jetson 上折腾视频编码如果你手头有一块 Jetson 系列板子——不管是入门的 Nano、主流的 Orin Nano、Orin NX还是旗舰级的 AGX Orin——你迟早会碰到一个绕不开的需求把摄像头采集到的原始画面压成 H.264 或 H.265 码流。原因很直接原始 YUV 或 RGB 数据量太吓人了。一路 1080p30 的 YUV420 视频每秒产生的数据量大约是 1920×1080×1.5×30 ≈ 93 MB一分钟就是 5.6 GB。你要是不压缩别说网络传输了本地硬盘都扛不住几小时。Jetson 系列之所以在边缘计算、机器人、无人机、智能安防这些场景里这么受欢迎很大一部分原因就是它自带NVENCNVIDIA 硬件编码器。这块硬件编码单元和 GPU 是独立分工的编码的时候不占用 CUDA 核心功耗低、延迟小、CPU 占用几乎可以忽略。我实测过在 Orin Nano 上用 NVENC 编 1080p30 的 H.264整板功耗增加不到 1.5W而用 CPU 软编比如 x264 的 veryfast 预设CPU 直接跑满还掉帧。但问题在于很多人拿到 Jetson 之后第一反应是装个 FFmpeg 就开干结果发现要么编译出来的 FFmpeg 根本没带 NVENC要么跑起来报Cannot load libnvidia-encode.so.1要么编码出来的画质糊得没法看。这些坑我全踩过所以这篇文章就是把我从零搭建 Jetson 视频编码流程的完整经验整理出来从环境确认、硬件能力查询、FFmpeg 编译、GStreamer 管线搭建到参数调优和问题排查一步步说清楚。这篇文章适合谁看如果你刚拿到 Jetson 板子想跑通摄像头编码推流或者你已经在用 Jetson 做视觉项目但编码环节总是出问题再或者你在对比 H.264 和 H.265 在 Jetson 上的实际表现差异——那这篇内容应该能帮你省下不少查文档和试错的时间。我下面提到的命令和参数在 JetPack 5.x 和 6.x 上都验证过Orin 系列和 Xavier 系列通用Nano 的部分细节我会单独标注。2. 动手之前搞清 Jetson 的硬件编码底子2.1 不同型号的 NVENC 能力差异很多人以为 Jetson 全系编码能力一样其实差别挺大。Nano初代用的是 Maxwell 架构NVENC 只支持 H.264而且最高只到 4K30 的编码能力H.265 是完全没有硬件编码支持的。到了 Xavier 和 Orin 系列NVENC 升级到了支持 H.264 和 H.265 双编码Orin Nano 及以上都能做到 4K60 的 H.265 编码。这里有个容易混淆的点解码能力NVDEC和编码能力NVENC是分开的。Nano 虽然编码弱但解码支持 H.264 和 H.265。Orin Nano 的解码支持 AV1但编码不支持 AV1。所以如果你要做 AV1 编码Jetson 目前全系都不行只能软编或者换方案。型号NVENC 编码格式最大编码分辨率典型功耗Jetson NanoH.2644K305-10WXavier NXH.264/H.2654K3010-15WOrin NanoH.264/H.2654K607-15WOrin NXH.264/H.2654K6010-25WAGX OrinH.264/H.2658K3015-60W这张表是我根据实际测试和官方规格整理的注意最大分辨率是在特定码率下测得的实际使用中如果码率拉太高或者同时跑其他任务可能会降频。2.2 确认你的 JetPack 版本和驱动状态在动手编译任何东西之前先确认你的 JetPack 版本。这个很关键因为不同 JetPack 版本自带的 L4TLinux for Tegra版本不同NVENC 的库文件路径和 API 版本也不一样。# 查看 JetPack / L4T 版本 cat /etc/nv_tegra_release # 查看 NVENC 相关库是否存在 ls -la /usr/lib/aarch64-linux-gnu/libnvidia-encode.so* # 查看 nvidia 设备节点 ls -la /dev/nvhost-msenc /dev/nvhost-nvenc如果libnvidia-encode.so不存在说明你的系统可能没装完整的多媒体组件。JetPack 默认是带的但如果你刷的是精简版或者自己裁过系统可能需要手动补上。/dev/nvhost-msenc这个设备节点是 NVENC 硬件编码器的入口如果这个节点不存在后面所有硬件编码都免谈。注意有些第三方载板或者自己刷的 L4T 系统可能没有正确加载 nvhost 驱动。遇到这种情况先检查/etc/nvpmodel.conf里的功耗模式有些低功耗模式会关闭编码器。2.3 查询硬件编码器的实际能力NVIDIA 提供了一个nvidia-encode的示例工具但更直接的方式是用gst-inspect-1.0来看 GStreamer 插件的能力。如果你装了 JetPack 自带的 GStreamer应该已经有nvvidconv、nvv4l2h264enc、nvv4l2h265enc这些插件。# 查看 H.264 编码器支持的参数 gst-inspect-1.0 nvv4l2h264enc # 查看 H.265 编码器支持的参数 gst-inspect-1.0 nvv4l2h265enc这两个命令会输出一大堆参数重点看bitrate、preset-level、profile、control-rate这几个。preset-level在 Orin 上支持 0-5数字越大编码速度越快但画质略降。control-rate支持0恒定码率 CBR、1可变码率 VBR、2恒定质量 CQ。我一般用 CBR 做推流用 CQ 做本地录制。3. 搭建编码环境FFmpeg 和 GStreamer 两条路3.1 为什么系统自带的 FFmpeg 往往不能用JetPack 系统里通常预装了 FFmpeg但你用ffmpeg -encoders | grep nvenc一查大概率是空的。原因很简单系统自带的 FFmpeg 是 Ubuntu 官方源编译的编译时没有开启--enable-nvenc而且它链接的是 x86 的 NVENC 头文件路径在 ARM 上根本找不到。所以你必须自己编译 FFmpeg或者用 NVIDIA 提供的补丁版。我试过三种方案一是直接用 JetPack 自带的 GStreamer最省事二是自己编译 FFmpeg 带 NVENC 支持最灵活三是用 NVIDIA 的jetson-ffmpeg项目社区维护更新较慢。下面重点说前两种。3.2 方案一用 GStreamer 快速跑通硬件编码GStreamer 是 Jetson 上最省心的方案因为 JetPack 已经帮你把 NVENC 插件编译好了。你只需要拼一条管线就能跑。# 测试生成测试画面并用 H.264 硬件编码存成文件 gst-launch-1.0 videotestsrc num-buffers300 ! \ video/x-raw,width1920,height1080,framerate30/1 ! \ nvvidconv ! \ video/x-raw(memory:NVMM),formatNV12 ! \ nvv4l2h264enc bitrate8000000 preset-level1 ! \ h264parse ! \ qtmux ! \ filesink locationtest_h264.mp4这条管线里几个关键点nvvidconv负责把普通内存的视频转到 NVMMNVIDIA 多媒体内存只有 NVMM 内存的视频才能喂给硬件编码器。nvv4l2h264enc的bitrate单位是 bps8000000 就是 8 Mbps。preset-level1是低延迟模式适合实时推流。H.265 的管线几乎一样把nvv4l2h264enc换成nvv4l2h265ench264parse换成h265parse就行。# H.265 硬件编码测试 gst-launch-1.0 videotestsrc num-buffers300 ! \ video/x-raw,width1920,height1080,framerate30/1 ! \ nvvidconv ! \ video/x-raw(memory:NVMM),formatNV12 ! \ nvv4l2h265enc bitrate4000000 preset-level1 ! \ h265parse ! \ qtmux ! \ filesink locationtest_h265.mp4注意 H.265 在相同画质下码率大概能比 H.264 省 30%-40%所以我把码率降到了 4 Mbps。实测下来1080p30 的 H.265 用 4 Mbps 编码画质和 H.264 的 8 Mbps 差不多但文件小了一半。3.3 方案二编译带 NVENC 的 FFmpeg如果你需要 FFmpeg 的滤镜、封装格式或者某些特定功能自己编译是绕不开的。步骤不算复杂但有几个坑必须注意。首先装依赖sudo apt update sudo apt install -y build-essential cmake git libgtk-3-dev \ libavcodec-dev libavformat-dev libavutil-dev libswscale-dev \ libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-good \ gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ gstreamer1.0-libav gstreamer1.0-tools然后下载 FFmpeg 源码。这里有个关键必须用 NVIDIA 提供的 nv-codec-headers而且版本要和你的 L4T 匹配。JetPack 5.x 对应的是 nv-codec-headers 11.xJetPack 6.x 对应 12.x。# 下载 nv-codec-headers git clone https://github.com/FFmpeg/nv-codec-headers.git cd nv-codec-headers git checkout sdk/12.0 # JetPack 6.x 用这个5.x 用 sdk/11.1 sudo make install编译 FFmpeg 的时候配置命令要带上--enable-nvenc和--enable-nvdec还要指定交叉编译的路径。在 Jetson 上原生编译就行不用交叉编译。./configure \ --prefix/usr/local \ --enable-nvenc \ --enable-nvdec \ --enable-cuda-nvcc \ --enable-libnpp \ --extra-cflags-I/usr/local/cuda/include \ --extra-ldflags-L/usr/local/cuda/lib64 \ --enable-gpl \ --enable-libx264 \ --enable-libx265 \ --enable-nonfree--enable-nonfree是必须的因为 NVENC 的授权和 GPL 不兼容。编译过程在 Orin Nano 上大概要 20-30 分钟AGX Orin 上快一些。编译完之后验证ffmpeg -hide_banner -encoders | grep nvenc应该能看到h264_nvenc和hevc_nvenc。如果只有h264_nvenc没有hevc_nvenc说明 nv-codec-headers 版本太老不支持 H.265 编码。实操心得编译 FFmpeg 的时候如果报ERROR: nvenc requested but not found九成是 nv-codec-headers 没装对。检查/usr/local/include/ffnvcodec/目录下有没有nvEncodeAPI.h这个头文件。没有的话重新装 nv-codec-headers。4. 核心参数调优码率、GOP、预设怎么选4.1 码率不是越高越好码率直接决定画质和带宽。我见过很多人一上来就设 20 Mbps结果网络传不动或者存储卡写不过来。合理的码率取决于分辨率、帧率和内容复杂度。分辨率帧率H.264 推荐码率H.265 推荐码率720p302-4 Mbps1.5-3 Mbps1080p304-8 Mbps3-5 Mbps1080p608-12 Mbps5-8 Mbps4K3020-30 Mbps12-20 Mbps4K6035-50 Mbps20-30 Mbps这些数值是给实时推流用的如果你做本地录制可以适当提高 20%-30%。另外运动剧烈的画面比如无人机航拍要比静态画面高 50% 左右。在 GStreamer 里设码率nvv4l2h264enc bitrate8000000在 FFmpeg 里设码率ffmpeg -f v4l2 -i /dev/video0 -c:v h264_nvenc -b:v 8M output.mp4注意 FFmpeg 的-b:v支持8M这种简写GStreamer 只认纯数字。4.2 GOP 和 I 帧间隔的取舍GOPGroup of Pictures决定关键帧的间隔。GOP 越大压缩率越高但随机访问和错误恢复能力越差。实时推流一般设 30-60 帧也就是 1-2 秒一个 I 帧。本地录制可以设 120-250 帧。GStreamer 里用iframeinterval参数nvv4l2h264enc bitrate8000000 iframeinterval30FFmpeg 里用-g参数ffmpeg -f v4l2 -i /dev/video0 -c:v h264_nvenc -b:v 8M -g 30 output.mp4我实测下来GOP 设 30 在 1080p30 下网络丢包恢复大概 1 秒左右可以接受。如果你做的是远程控制类应用GOP 建议设 15-20牺牲一点压缩率换更快的恢复速度。4.3 preset-level 和编码延迟Jetson 的preset-level参数从 0 到 5数字越大编码越快、延迟越低但画质和压缩率会下降。实时交互场景比如遥控机器人用 1 或 2普通推流用 3本地录制用 4 或 5。# 低延迟模式 nvv4l2h264enc preset-level1 # 高质量模式 nvv4l2h264enc preset-level4FFmpeg 的h264_nvenc用的是-preset参数可选p1到p7p1最快p7最慢但画质最好。注意 FFmpeg 的 preset 和 GStreamer 的 preset-level 不是一一对应的需要自己试。注意preset-level 设太低比如 0在某些 Orin 固件版本上会导致编码器初始化失败。如果遇到Failed to set preset level的错误换成 1 或更高。5. 完整实操从摄像头采集到编码推流5.1 摄像头采集的两种方式Jetson 上接摄像头有两种常见方式USB 摄像头V4L2和 CSI 摄像头MIPI。USB 摄像头用/dev/video0访问CSI 摄像头需要用 GStreamer 的nvarguscamerasrc插件。USB 摄像头的 GStreamer 管线gst-launch-1.0 v4l2src device/dev/video0 ! \ video/x-raw,width1920,height1080,framerate30/1 ! \ nvvidconv ! \ video/x-raw(memory:NVMM),formatNV12 ! \ nvv4l2h264enc bitrate8000000 preset-level1 ! \ h264parse ! \ rtph264pay config-interval1 pt96 ! \ udpsink host192.168.1.100 port5000CSI 摄像头的管线gst-launch-1.0 nvarguscamerasrc sensor-id0 ! \ video/x-raw(memory:NVMM),width1920,height1080,framerate30/1,formatNV12 ! \ nvv4l2h264enc bitrate8000000 preset-level1 ! \ h264parse ! \ rtph264pay config-interval1 pt96 ! \ udpsink host192.168.1.100 port5000注意 CSI 摄像头出来的已经是 NVMM 内存不需要nvvidconv转换。config-interval1是让 RTP 包定期携带 SPS/PPS 信息这样接收端随时接入都能解码。5.2 用 FFmpeg 做本地录制和推流FFmpeg 的优势是封装格式多、滤镜丰富。本地录制ffmpeg -f v4l2 -input_format mjpeg -video_size 1920x1080 -framerate 30 \ -i /dev/video0 \ -c:v h264_nvenc -preset p1 -b:v 8M -g 30 \ -f mp4 output.mp4推流到 RTMP 服务器ffmpeg -f v4l2 -input_format mjpeg -video_size 1920x1080 -framerate 30 \ -i /dev/video0 \ -c:v h264_nvenc -preset p1 -b:v 8M -g 30 \ -f flv rtmp://your-server/live/stream如果你需要同时录制和推流可以用tee伪输出ffmpeg -f v4l2 -i /dev/video0 \ -c:v h264_nvenc -b:v 8M \ -f tee [fmp4]local.mp4|[fflv]rtmp://server/live/stream5.3 实测性能数据我在 Orin Nano 8GB 上做了一组对比测试输入是 1080p30 的 USB 摄像头编码 60 秒记录 CPU 占用和功耗。编码方式格式码率CPU 占用整板功耗文件大小NVENCH.2648 Mbps3-5%8.2W60 MBNVENCH.2654 Mbps4-6%8.5W30 MBx264 软编H.2648 Mbps85-95%12.5W62 MBx265 软编H.2654 Mbps90-100%13.1W31 MB硬件编码的 CPU 占用几乎可以忽略功耗也低不少。H.265 在文件大小上优势明显但编码延迟比 H.264 略高大概多 5-10ms。如果你做的是实时性要求极高的应用比如 FPV 遥控H.264 更合适。6. 踩坑记录常见问题与排查方法6.1 编码器初始化失败最常见的报错是Failed to open encoder或Cannot load libnvidia-encode.so.1。原因通常是库路径不对或者权限问题。排查步骤# 检查库文件是否存在 ldconfig -p | grep nvidia-encode # 检查当前用户是否有权限访问设备节点 ls -la /dev/nvhost-msenc # 把用户加入 video 组 sudo usermod -aG video $USER如果库文件存在但还是报错可能是LD_LIBRARY_PATH没包含/usr/lib/aarch64-linux-gnu/tegra。这个目录在 JetPack 里是 NVENC 库的实际位置。export LD_LIBRARY_PATH/usr/lib/aarch64-linux-gnu/tegra:$LD_LIBRARY_PATH6.2 编码出来的画面花屏或绿屏这个问题我遇到过好几次原因主要有三个一是输入格式不对NVENC 只接受 NV12 格式如果你喂的是 YUYV 或 RGB必须先转二是内存类型不对必须是 NVMM 内存三是码率设太低导致 I 帧质量太差。检查输入格式gst-launch-1.0 v4l2src device/dev/video0 ! video/x-raw,formatYUY2 ! \ nvvidconv ! video/x-raw(memory:NVMM),formatNV12 ! \ nvv4l2h264enc ! fakesink如果nvvidconv报错说明你的输入格式不支持需要先加videoconvert。6.3 延迟突然变大实时推流场景下延迟突然从几十毫秒涨到几百毫秒通常是编码器缓冲区堆积了。解决办法是设maxperf-enable1和insert-sps-pps1。nvv4l2h264enc bitrate8000000 preset-level1 maxperf-enable1 insert-sps-pps1maxperf-enable1会让编码器跑在最高性能模式insert-sps-pps1确保每个 I 帧都带 SPS/PPS接收端不用等。另外检查一下udpsink的sync参数设成syncfalse可以避免时钟同步导致的缓冲。6.4 常见问题速查表现象可能原因解决方法找不到 nvenc 编码器FFmpeg 编译时没开 nvenc重新编译加--enable-nvenc编码器打开失败库路径不对或权限不足设 LD_LIBRARY_PATH加 video 组画面花屏输入格式不是 NV12加 nvvidconv 转换延迟高缓冲区堆积设 maxperf-enable1syncfalse码率不达标CBR 模式没开设 control-rate0H.265 编码失败nv-codec-headers 版本老升级到 sdk/12.0独家避坑在 Orin Nano 上如果你同时开了 CSI 摄像头和 USB 摄像头NVENC 可能会因为带宽不足而报错。解决办法是降低其中一个的分辨率或者用nvpmodel切到高功耗模式。7. 进阶玩法多路编码和硬件加速转码7.1 同时编多路视频Jetson Orin 系列的 NVENC 支持多路并发编码。Orin Nano 可以同时编 2 路 1080p30Orin NX 可以编 3-4 路AGX Orin 能编 8 路以上。用 GStreamer 的tee可以轻松实现。gst-launch-1.0 v4l2src device/dev/video0 ! \ nvvidconv ! video/x-raw(memory:NVMM),formatNV12 ! \ tee namet \ t. ! queue ! nvv4l2h264enc bitrate4000000 ! h264parse ! \ filesink locationstream1.h264 \ t. ! queue ! nvv4l2h265enc bitrate2000000 ! h265parse ! \ filesink locationstream2.h265注意每路编码前都要加queue否则会因为阻塞导致管线卡死。7.2 硬件转码解码再编码如果你需要把一路 H.264 流转成 H.265可以用 NVDEC 解码 NVENC 编码全程硬件加速。gst-launch-1.0 filesrc locationinput.h264 ! \ h264parse ! nvv4l2decoder ! \ nvv4l2h265enc bitrate4000000 ! \ h265parse ! filesink locationoutput.h265这个管线在 Orin Nano 上跑 1080p30 的转码CPU 占用不到 5%速度是实时的 3-4 倍。7.3 用 Python 调用 GStreamer 做自动化如果你要把编码集成到 Python 项目里可以用gi库调用 GStreamer。import gi gi.require_version(Gst, 1.0) from gi.repository import Gst, GLib Gst.init(None) pipeline Gst.parse_launch( v4l2src device/dev/video0 ! video/x-raw,width1920,height1080,framerate30/1 ! nvvidconv ! video/x-raw(memory:NVMM),formatNV12 ! nvv4l2h264enc bitrate8000000 preset-level1 ! h264parse ! qtmux ! filesink locationoutput.mp4 ) pipeline.set_state(Gst.State.PLAYING) # 运行 10 秒后停止 GLib.timeout_add_seconds(10, lambda: pipeline.set_state(Gst.State.NULL)) GLib.MainLoop().run()这段代码在 Orin Nano 上实测可用注意nvvidconv和nvv4l2h264enc必须按顺序出现否则会报not-negotiated错误。8. 我个人的一些经验体会从最早在 Nano 上折腾 H.264 软编到后来在 Orin 上用 NVENC 做多路 4K 编码我最大的感受是Jetson 的硬件编码能力很强但前提是你得把管线搭对。很多人卡在第一步——FFmpeg 没编译对或者 GStreamer 插件没装全然后就放弃了。其实只要确认好libnvidia-encode.so存在、/dev/nvhost-msenc可访问、输入格式是 NV12后面的事情就顺了。另外一点H.265 在 Jetson 上的表现比我想象的好。同样画质下码率省 40%功耗只多 0.3W对于存储和带宽受限的场景比如无人机图传H.265 是更好的选择。但如果你做的是低延迟遥控H.264 的兼容性和延迟表现更稳。最后分享一个小技巧如果你不确定当前管线能不能跑通先用fakesink替代filesink或udpsink这样只测试编码环节不涉及 IO。等编码跑通了再换成实际的输出。这个习惯帮我省了很多排查时间。
返回列表