十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

RK3588上基于GStreamer的H.265硬解预览与MP4分段录像方案

RK3588上基于GStreamer的H.265硬解预览与MP4分段录像方案 1. 项目背景与整体设计思路这次要记录的是一个在 RK3588 平台上做的视频预览与录像方案核心诉求并不复杂接一路 H.265 编码的摄像头信号在开发板的 HDMI 输出上实时预览同时把码流按指定时长切成一段一段的 MP4 文件存到本地。方案选型的时候其实摆在我面前的有两条路一条是用 Rockchip 官方的 MPPMedia Process Platform接口直接撸代码另一条是基于 GStreamer 框架去拼 pipeline。MPP 的性能上限确实更高可以直接操作硬件编解码单元但它的 API 层级偏底层处理容器封装、音视频同步、文件切片这些问题都得自己从头造轮子开发周期至少要翻一倍。GStreamer 则是一个成熟的多媒体框架硬件编解码可以借助 Rockchip 提供的 gstreamer-rockchip 插件转接到 MPP而封装、推流、显示这些环节社区已经有大量成熟的 element 可以直接复用。我最终选择 GStreamer还有一个很重要的原因这套方案的调试效率高。GStreamer 的 pipeline 是可以通过命令行工具 gst-launch-1.0 即可快速验证的我可以先把最核心的编解码链路跑通再逐步叠加录像、显示等模块而不是像 MPP 裸开发那样要等整个工程编译完才能看到效果。对于项目初期的验证阶段这种快速迭代的能力太重要了。适合参考这篇内容的人主要是两类一类是在 RK3588 或其他 Rockchip 平台上做视频监控、图像采集类产品的嵌入式工程师另一类是对 GStreamer 框架有一定了解、但没怎么在 ARM 板子上摸过硬件编解码的开发者。下面我会把 pipeline 的搭建思路、关键插件的参数含义、以及实际联调中遇到的坑都展开讲希望能帮你少踩几个我踩过的雷区。1.1 为什么选定 GStreamer 而不是 FFmpeg 或自定义方案先说 FFmpeg。FFmpeg 在转封装、转码这些离线处理场景里几乎是王者级别的存在但它和 GStreamer 的定位其实不太一样。FFmpeg 更偏重单次执行的批处理而 GStreamer 是一个长期运行的多媒体流处理框架后者的数据流是持续不断的并且天然支持动态插拔元素。在监控录像这种需要 7x24 小时连续运行的场景下GStreamer 的 pipeline 模型显然更贴合需求。GStreamer 还有一个优势是调试手段丰富。比如我可以用GST_DEBUG环境变量精确控制某个 element 的日志输出级别可以用fpsdisplaysink直接看到实际渲染帧率还可以方便地在 pipeline 中间插入identity dumptrue来检查码流内容。这些工具在实机调试中能节省大量时间比自己在代码里打日志要直观得多。另外一点RK3588 的 SDK 里已经集成了gstreamer-rockchip插件这使得硬解 H.265 的能力直接暴露为 GStreamer 里的一个 element。这意味着我不需要去写任何涉及芯片寄存器或者 MPP 私有 API 的代码只需要把它当作一个普通的解码器插件来用再从参数层面做一些适配就行。1.2 方案架构预览与录像如何优雅地分叉设计 pipeline 时我一开始想的比较简单就是“解码 - 显示”然后录像是不是就直接从解码器后面拉一路出去就好了但实际做起来发现有几个问题需要想清楚。第一解码器和显示渲染的最佳帧率未必一致。预览如果跟随编码源走可以做到源帧率输出但录像其实不关心显示帧率它只关心码流本身的连续性。第二解码后的原始视频帧NV12 或 YUV数据量非常大如果两个分支都去拿原始帧内存带宽会白白多耗一倍。更合理的做法是让预览走解码输出的原始帧而录像则在编码码流阶段即解封装之后、解码之前把 H.265 的裸流数据复制一份直接送给封装器完全不需要再来一次解码。所以最终架构就清晰了源从摄像头或 RTSP 拉流拿到 H.265 裸码流。分叉点解析出 H.265 裸流之后用tee分成两路。一路走解码和显示做实时预览。另一路直接交给 MP4 封装器做分段存储。这样录像这条链路完全不需要解码过程CPU 和 VPU 的负载都压到了最低同时录像文件的画质就等于原始码流的画质一分钱质量损耗都没有。2. 硬解管线搭建从 rtspsrc 到屏幕渲染2.1 视频源的选择v4l2src 与 rtspsrc 的取舍先聊视频源。在板卡上本地接 MIPI-CSI 或 USB 摄像头时最常用的源插件是v4l2src它直接对接 Linux 的 V4L2 框架。但如果摄像头本来输出的就是 H.265 码流像很多 IP Camera、树莓派 Camera Module、或者专业的 H.265 USB 摄像头那v4l2src出来的数据就是 H.265 裸流可以直接进入下面的处理环节。我这次用的是一路 RTSP 协议的网络摄像头所以源插件换成了rtspsrc。这个插件内部会自动完成 RTSP 的 OPTIONS、DESCRIBE、SETUP、PLAY 等流程对使用者来说只需要指定location属性即可。不过这里有一点需要注意如果你对实时性要求比较高建议给rtspsrc加上latency0参数。RTSP 默认会引入一定的缓冲延迟默认大约 100ms 到 2s 不等latency0可以把这个延迟压到最低代价是抗网络抖动能力变弱所以要根据实际网络环境去平衡我最终在局域网内用的是 0。还有一个在调试 RTSP 时极容易踩的坑默认情况下rtspsrc会用 UDP 传输 RTP 包如果网络不稳定出现丢包解码端很容易出现花屏甚至解码器直接卡死。改成 TCP 模式protocolstcp之后抗丢包能力会好很多代价是延迟略有增加。我这边监控场景对延迟不敏感所以直接用 TCP。2.2 RK3588 硬解核心mpph265dec 的使用与调优RK3588 的 SoC 里集成了一个很强的 VPUVideo Processing Unit支持 H.265/H.264/VP9 等多种格式的硬件解码。在 GStreamer 里这个能力通过mpph265dec这个 element 暴露出来它是由 Rockchip 的 gstreamer-rockchip 插件提供的。使用它之前需要先确认板子系统里已经装好了对应的插件库。瑞芯微官方 SDK 里一般会自带但如果用的是 Debian/Ubuntu 的通用根文件系统可能需要自己编译或从 apt 源安装。验证方式很简单gst-inspect-1.0 | grep mpp如果输出里有mpph264dec、mpph265dec、mppvp9dec这几个 element就说明硬件解码插件已经就位了。把硬解元素插到 pipeline 里用法和软解avdec_h265完全一样但效果截然不同。我实测下来同样的 4K H.265 码流软解时 CPU 直接吃到 80% 以上换成mpph265dec之后 CPU 占用率掉到了个位数画面还更流畅。这正是 RK3588 的优势——8K 解码能力不是拿来摆设的4K 预览对于它来说几乎是无感操作。实际使用中我通常还会给mpph265dec加几个常用属性max-pending解码器内部排队等待显示的帧数上限默认是 2如果看到画面有明显撕裂感或延迟感可以适当加大一般 3~4 就够改太大反而会让延迟变高。frame-drop当下游显示端处理不过来时是否允许丢帧。这对保持实时性很重要默认是关的建议打开。不然解码器输出的帧会在队列里越积越多延迟越来越大仿佛在看直播回放。num-extra-frames额外分配的帧缓冲数量。这个可以根据码流分辨率适当加大尤其在高帧率场景下可以避免解码器因为帧缓冲不够而周期性卡顿。2.3 显示后端选型waylandsink 与 ximagesink解码之后就是显示。RK3588 上常见的显示方案有两个ximagesink和waylandsink。如果系统跑的是基于 X11 的桌面环境那ximagesink可以做到开箱即用如果跑的是 Wayland 合成器比如 Weston那就用waylandsink。从性能角度讲waylandsink比ximagesink更推荐用在 RK3588 上。Wayland 架构下应用直接把缓冲区提交给合成器合成和显示流程更加精简延迟更低而且能更好地利用 RK3588 的显示控制器进行零拷贝渲染。如果只是做一个没有桌面环境的嵌入式 GUI直接用waylandsink挂到 Weston 上会非常干净。不过如果系统没装 Wayland也不必非要去折腾ximagesink在 1080P 场景下完全够用。真正需要优先考虑的是另一个问题显示时是否需要做色彩格式转换。H.265 解码出来的通常是 NV12 格式而显示端可能需要 BGRA 或 RGB转换工作可以由videoconvert或glupload 着色器来完成。如果对格式转换的逻辑不熟悉最简单的方式是让 GStreamer 自动插入转换插件gst-launch-1.0 rtspsrc locationrtsp://... protocolstcp latency0 ! \ rtph265depay ! h265parse ! tee namet \ t. ! queue ! mpph265dec ! videoconvert ! waylandsinkvideoconvert会自动完成色彩空间的适配不用手动指定输出格式。3. MP4 分段录像的实现细节3.1 splitmuxsink 分段参数设置录像这部分是这次实践里最有意思也是坑最多的地方。GStreamer 里做 MP4 封装的标准做法是mp4mux但如果只用一个mp4mux一直跑下去录出来的文件会越来越大而且一旦程序异常退出整个文件基本就废了。所以我们要的是分段录制每段固定时长自动切片成多个 MP4 文件。GStreamer 恰好自带一个专门干这个活的 elementsplitmuxsink。它本质上是一个多路复用器外壳内部可以封装mp4mux并在切片条件满足时自动断开当前文件创建新文件继续写。关键参数如下location输出文件名的模板比如/data/video/record_%04d.mp4其中的%04d会用四位数字自动递增。max-size-time单个文件的最大时长单位是纳秒。比如60000000000表示 60 秒一个文件。max-size-bytes按文件大小切分一般监控场景建议用时间为主大小限制为辅。muxer指定底层的封装器我们要的是mp4mux。一个典型的命令长这样gst-launch-1.0 rtspsrc locationrtsp://192.168.1.100/h265 protocolstcp latency0 ! \ rtph265depay ! h265parse ! tee namet \ t. ! queue ! mpph265dec ! videoconvert ! waylandsink \ t. ! queue ! splitmuxsink location/data/video/record_%04d.mp4 \ max-size-time60000000000 muxermp4mux其中tee负责把码流复制给两个下游预览和录像而且录像分支直接从压缩码流开始不做解码这是最省资源的方式。3.2 关键帧对齐、时间戳连续性与 MP4 可播放性的底层逻辑分段录像表面上看着简单但实际跑起来发现了不少细节最典型的就是录出来的第一个 MP4 文件在播放器里打不开或者时长严重不对前面一小段画面直接就丢了分段衔接处会有卡顿或花屏。排了一圈之后问题定位到了三个核心原因。第一个原因是 MP4 文件的 moov box 不在文件头。MP4 封装器默认会把一些关键索引信息moov box放在文件末尾只有在写入完成并正常关闭时才会回写文件头。如果文件没有正常关闭比如进程被 kill播放器就找不到索引自然无法播放。这个问题在 GStreamer 1.14 之后的mp4mux中已经通过faststart属性缓解了把关键索引信息挪到文件开头但真正保证文件可靠还是要靠正常关闭 pipeline。第二个原因是关键帧对齐问题。MP4 是支持随机访问的封装格式它要求每个切分点必须落在关键帧上否则切出来的文件第一帧不是关键帧播放器解码时就会黑屏或报错。splitmuxsink在切换段的瞬间实际上是等待下一个关键帧到来才开始写新文件但这里有个隐藏的坑如果你的编码器关键帧间隔是 2 秒而你设置的分段时长是 1 秒那么每个分段实际长度会在 1 到 3 秒之间浮动这是正常的不要以为是 bug。第三个原因是时间戳连续性。MP4 封装内部要求每个 sample 的 PTSPresentation Timestamp是单调递增的。如果上游因为网络抖动、丢包重传导致时间戳回退封装出来的文件就会出现花屏甚至无法解码。解决思路是让数据在进封装器之前经过一个queue做缓冲整理同时在 rtspsrc 层尽量把网络问题先消化掉。结合这些经验我建议实战中的分段参数这样设参数推荐值说明编码器关键帧间隔2 秒如果摄像头支持设为 2s 是均衡值与 I 帧开销的折中max-size-time60 秒或 300 秒按业务需求定但建议是关键帧间隔的整数倍max-size-bytes0不限大小防止文件过大影响后续上传或检索splitmuxsink内部 buffer默认即可必要时调大max-size-time的余量3.3 彻底解决“第一个文件打不开”的诡异现象前面提到第一个文件打不开这个问题曾经困扰了我挺长时间。后来看 GStreamer 源码和邮件列表才明白问题出在splitmuxsink的启动时序上当 pipeline 刚启动第一个关键帧还没到来之前分片器已经在等待了。可如果上游因为某些原因迟迟没有送入关键帧比如 rtspsrc 刚连接时跳过了部分帧导致第一个分段里没有任何完整 GOP那么当分段超时触发时MP4 文件里可能只有一个空壳或一个不完整的帧自然无法播放。解决方式很粗暴但有效在启动录像时先等待一段时间再开始录像或者干脆按首次关键帧到达的实际时间作为分段起始点。GStreamer 里splitmuxsink有个send-keyframe-requests属性可以允许分片器向下游请求紧急关键帧。我做的时候直接简单处理RTSP 拉流后让 pipeline 先跑 3 秒再开始写文件保证第一个分段里一定有完整的关键帧。还要注意一个容易被忽略的点MP4 的 moov 索引需要正常关闭才会回写。所以在长时间录像后如果想安全地退出建议通过发送EOSEnd of Stream事件来优雅关闭 pipeline而不是直接 kill 进程。简单操作是按下 CtrlC 时让 GStreamer 自动发送 EOS或者代码里调用gst_element_send_event(pipeline, gst_event_new_eos())。4. 常见问题排查与避坑指南4.1 H.265 硬解不生效CPU 占用却一直居高不下第一个高发问题pipeline 能跑但 CPU 占用率居高不下。这时候多半是硬解没有真正启用GStreamer 自动选择了软解。排查顺序如下用gst-inspect-1.0 mpph265dec确认插件存在且状态正常。确认 pipeline 里明确写了mpph265dec而不是avdec_h265。如果没写GStreamer 可能自动挑选一个默认解码器未必是硬解的。用GST_DEBUG2查看日志看有没有提示mpp初始化失败或格式不支持的警告。检查输入码流是不是真正的 H.265可以用ffprobe验证。如果摄像头实际输出的是 H.264你硬塞给mpph265dec自然不工作日志会有一堆解码错误。我自己还遇到过一个相对隐蔽的情况当系统里同时装了软解插件和硬解插件时某些编码格式会被软解优先匹配。这种情况下可以直接在 decodebin 后面用force-caps或者干脆不让它自动选而是直接硬编码用mpph265dec。监控项目里编码格式是固定的没必要用动态识别写死反而更稳。4.2 录像文件时长不准或无法拖拽进度条有段时间我录出来的文件播放倒是能播但进度条不能拖一拖就卡死或者整个文件实际只有 1 帧。排查下来基本都指向同一个问题时间戳不连续。RTSP 拉流时如果网络有抖动或者摄像头端的编码器输出不均匀RTP 包到达的时间戳可能会有跳变。MP4 封装要求时间戳连续否则音频/视频同步和 seek 都会异常。解决办法是在rtph265depay之后加h265parse它会解析 NAL Unit 并修正时间戳。适当调大queue的max-size-buffers和max-size-time给时序整理留出缓冲空间。检查 rtspsrc 的几个延迟参数必要时用latency500做一次试点看问题是否缓解再逐步降低。此外splitmuxsink的每个分段文件时长并不是精确等于你设置的数字它会在关键帧边界对齐所以实际时长通常比设定值长最坏情况会多一个关键帧间隔。如果业务上有严格的时长要求比如做录像回放检索建议在上层做层叠检索不要依赖单个文件的精确时长。4.3 MP4 文件的 moov 索引损坏与恢复策略正常工作流程下moov 索引由mp4mux在 EOS 时回写。但实际现场经常有断电、程序崩溃、手动强杀进程的情况文件留下来了却打不开。这里有几个策略程序内部用信号处理和优雅退出机制捕获 SIGINT/SIGTERM 后向 pipeline 发送 EOS等待 file 写入完成后才真正退出。如果服务异常崩溃可以用 FFmpeg 尝试修复文件ffmpeg -i damaged.mp4 -c copy repaired.mp4更彻底的方案是用fragmented MP4也就是让mp4mux开启fragment模式把 moov 索引分散到文件各个分段里。这种格式对异常中断的容忍度更高但代价是文件会比普通 MP4 略大且部分播放器兼容性略差。我自己的监控类项目就换成了片段式 MP4断电损坏的概率大幅降低。格式优点缺点适用场景普通 MP4兼容性最好普遍播放器都支持异常退出后文件损坏率高需要大量分享/审计的场景分段式 MP4如 fMP4容错性好流媒体友好文件略大个别老播放器不支持长时间无人值守录像4.4 播放器打不开 H.265 文件是封装问题还是解码能力问题录好文件拿到电脑上用系统自带的播放器打开结果直接报错了。很多人第一反应是“文件坏了”但更可能就是播放器不支持 H.265 硬解。这个问题其实很常见MP4 只是个壳里面的编码格式是 H.265Windows 自带的播放器如果不装 HEVC 解码扩展是无法播放的。建议录完测试用 VLC或者用支持良好硬解的播放器比如 PotPlayer。不过有一种情况确实跟文件有关就是前面提到的 moov 索引损坏或者时间戳回退导致 seek 时卡死。判断方法很简单用 FFmpeg 读取文件ffprobe -v error -show_entries streamcodec_name,duration -of defaultnoprint_wrappers1 output.mp4能看到准确的编码信息和时长说明文件本身是健康的问题在播放器。如果是用 FFmpeg 转封装修复后播放正常就是封装已经有问题了。4.5 长时间录像时内存/内存碎片的缓慢泄漏问题这是嵌入式平台最容易踩又最不容易定位的问题。我用的是 Debian 系统 GStreamer 1.20连续跑两天之后发现内存占用一点点往上爬最后直接 OOM。排了一圈结论是问题出在 rtspsrc 和 queue 之间的交互上。当网络短暂丢包时rtspsrc 会尝试缓冲重发而下游 queue 的动态缓冲调整max-size-bytes、max-size-time的自动扩展会让内存用量在突发时猛增然后释放的不完全干净。连续 48 小时后小碎块积累成问题。解决方式给 queue 设置固定上限不要让它无限扩展。开启 queue 的leakydownstream属性队列满的时候丢弃最旧的数据保持实时性。用 top 和/proc/pid/status持续观察 VmRSS 的变化一旦有线性增长优先检查 queue 和 rtspsrc 的配合。5. 聊聊我在这个项目里学到的三件事第一个心得是关于复用的。整个项目最终跑通后我统计了一下真正手写的业务逻辑代码只有几百行剩下的全是 GStreamer 插件的组合。这就是框架的价值你不用关心 VPU 寄存器怎么配、H.265 的 SPS/PPS 怎么解析、MP4 的 box 结构怎么写只要把正确的积木搭在一起剩下的交给生态。第二个心得是硬件平台的能力只有落到工具链上才有意义。RK3588 再强如果你不会在 GStreamer 里启用硬解那它就只是一块发热的板子。这也是我为什么一直强调用mpph265dec而不是直接avdec_h265的原因一行插件名的差别可能就是 80% 和 8% CPU 占用率的差别。第三个心得是关于录像可靠性的。真实环境里没人会温柔地按 CtrlC 让你优雅退出断电、死机、网络断开什么都可能发生。所以监控类项目我强烈推荐用fragmented MP4或者干脆上matroskaMKV封装这两种格式对异常中断的容忍度比普通 MP4 高了一个数量级。如果你对兼容性有硬性要求那就宁可牺牲一点存储空间和兼容性换取数据安全。再分享一个小技巧。调试 pipeline 的时候可以在任意两个元素之间塞一个identity dumptrue来抓取数据流或者用fpsdisplaysink替代waylandsink来实时观察帧率。这些工具在联调时能帮你快速定位卡顿发生在哪个环节比对着日志猜要高效得多。这个方案稳定跑下来之后我还顺手做了一版带 RTP 推流和按键截图的功能扩展原理和录像几乎一样都是在 tee 的某个分支上接不同的 sink。后面有空再单独写一篇把推流和截图的部分补上。
返回列表