这个系列写到第三篇,前两篇我们已经把RK3588上的ZLMediaKit环境跑通,也解决了最基本的RTSP/RTMP拉流与推流联通问题。但说实话,只做流媒体转发和转码,对玩RK3588的人来说远远不够过瘾——大家真正想看到的是把“视频流处理”和“AI推理”串成一条完整流水线:从摄像头或上级平台把流拉下来,解码成YOLO能吃的数据,在NPU上做目标检测,检测结果再编码成标准视频流推出去,最后随便用哪个播放器都能实时预览。
这篇就来填上这个闭环。我这次用的是瑞芯微官方MPP做硬件解码和硬件编码,用RKNN-Toolkit2把YOLOv8模型转换到RK3588的NPU上跑,流媒体服务仍然走ZLMediaKit,整条链路是“拉流 → MPP硬解 → RGA缩放 → YOLO推理 → MPP硬编 → ZLMediaKit推流”。这么一套组合下来,1080P视频流在板子上能以接近30FPS的帧率稳定跑完检测并推送出去,CPU占用还低得感人。这篇内容适合已经在RK3588上折腾过基础环境、想进一步把AI和流媒体打通的朋友,尤其是打算做边缘AI盒子、智能网关、安防边缘节点的同学。
1. 全链路设计与方案选型
1.1 为什么是ZLMediaKit + MPP + RKNN这个组合
先说方案选型的逻辑。RK3588这块板子的优势非常明确:CPU强、NPU有6TOPS算力、自带硬件编解码器,而且周边生态在国产平台里算很成熟的。但优势要发挥出来,前提是把“硬解码、NPU推理、硬编码、流媒体收发”这几件事合理地分工,而不是一把梭全塞给CPU软解软编。
我第一次跑通YOLO检测时偷懒,直接用FFmpeg软解RTSP流,再把解码后的帧转成RGB送进NPU推理,结果是1080P的流勉强能跑个10到15帧,CPU直接拉满,连带推流也开始发烫掉帧。后来改成MPP硬解,解码这一块的CPU占用几乎可以忽略,整条链路的瓶颈就从“解码”转移到了“模型推理”和“数据搬运”上,这个方向才是对的。
ZLMediaKit在这套组合里的角色是“流媒体中枢”。它负责把上游的RTSP/RTMP流接进来,也负责把处理后的流以标准协议重新推送出去,中间还带后台管理页面和RESTful API,方便调试。MPP则负责视频帧的硬件解码和硬件编码,RKNN负责跑YOLO推理,三者各管一摊,互不干扰。
我对比过几套常见方案,给你看下区别:
| 方案组合 | 解码方式 | 推理方式 | 编码方式 | 综合表现 |
|---|---|---|---|---|
| FFmpeg软解 + 纯CPU推理 + 软推流 | CPU软解 | CPU推理 | 软编码 | 1080P下只有10-15FPS,CPU满载 |
| FFmpeg软解 + NPU推理 + 软推流 | CPU软解 | RKNN NPU | 软编码 | 20FPS左右,CPU仍偏高 |
| MPP硬解 + RKNN NPU推理 + MPP硬编 + ZLMediaKit | 硬件解码 | RKNN NPU | 硬件编码 | 1080P可达25-30FPS,CPU占用低 |
1.2 端到端的数据流走向
整个流程我建议按以下的顺序来设计,这也是我在板子上实际跑通的架构:
IPC/摄像头 RTSP流 ↓ ZLMediaKit addStreamProxy 拉流(或外部组件拉流) ↓ RTSP裸流/PS流 取回 ↓ MPP 硬件解码(H.264/H.265 → NV12帧) ↓ RKNN 前置处理(RGA做缩放与格式转换 → 640x640 RGB) ↓ YOLO 模型 NPU推理,输出检测框 ↓ 检测结果处理(画框 / 叠加标注 / 仅提取元数据) ↓ MPP 硬件编码(NV12帧 → H.264码流) ↓ ZLMediaKit 推流(通过MediaSource推送或FFmpeg封装后推送) ↓ 播放端(ffplay / VLC / WebRTC 拉流预览)几个关键点值得提前强调一下。第一,解码出来的帧格式默认是NV12,也就是YUV420SP,但YOLO模型输入一般是RGB三通道,这里中间加一层RGA硬件加速缩放和转格式,比在CPU上用cvtColor快得多。第二,编码和推流之间需要做好帧率控制,不能让编码器拼命输出,否则下游播放延迟会被拉开。第三,整条链路里帧数据都是内存拷贝大户,尽量减少不必要的拷贝,最好用MPP buffer和RGA buffer之间的零拷贝或共享内存机制,这一点在后面实操部分会详细讲。
2. 核心环节拆解:拉流侧与ZLMediaKit的正确用法
2.1 拉流到底应该谁来拉
我之前被问得最多的一个问题就是:“ZLMediaKit已经能拉流了,为什么我还要自己写代码拉流再解码?”这里要分清楚一件事:ZLMediaKit拉流是为了“转发和分发”,而我们的场景里,拉流之后要做AI推理,推理完成后的帧还要再编码推回给ZLMediaKit。也就是说,ZLMediaKit既当“上游源”,又当“下游池”。
实际工程里有两种常见做法,我都试过,各有适用场景。
第一种做法是把拉流交给ZLMediaKit的addStreamProxy接口,让它自动从IPC拉取RTSP流。处理完后的视频流再通过ZLMediaKit的RESTful API或SDK以另一个流ID推送回去。这种做法的好处是代码省事,ZLMediaKit内部帮你处理了RTSP协商、超时重连等问题,而且这套拉流能力已经被大量生产环境验证过。缺点是如果你想在拉流后立刻对每一帧做精细控制,得从ZLMediaKit内部取帧,需要你对它的C++ API比较熟。
第二种做法是拉流和解码完全自己负责。进程自己发起RTSP会话,把流取回来交给MPP解码,推理完再推给ZLMediaKit。这种做法的控制力最强,每一帧经过什么处理都由你说了算,而且可以绕开ZLMediaKit内部的帧分发逻辑,直接在进程内存里完成“解码-推理-编码”。我这次的例子主要采用这种方式,同时把ZLMediaKit当作推流接收端。
从分工上总结:
| 责任方 | 负责内容 | 适用场景 |
|---|---|---|
| ZLMediaKit addStreamProxy | 拉流、RTSP会话管理、自动重连 | 单纯做流分发、多路转发 |
| 自研拉流 + MPP解码 | 精确控制每帧、全程插入AI处理 | 边缘AI盒子、智能分析设备 |
| 混合模式 | ZLMediaKit拉流,再通过回调取帧处理 | 想复用ZLMediaKit的拉流稳定性,又想做AI |
2.2 取流与解码的衔接
确定了自己拉流之后,接下来要解决的是“怎么把网络流变成MPP能解码的数据”。RTSP协商这块我用的是FFmpeg的libavformat库,但不启用它的软件解码器,只取到裸的H.264或H.265码流,然后喂给MPP。
为什么这么做?因为FFmpeg各版本对H.264的软解兼容性虽然好,但解码效率远不如MPP硬解。而我们需要的只是它帮你完成RTSP的SDP协商、RTP组包、然后输出AVPacket。把AVPacket的data和size直接送进MPP解码器,这样既有FFmpeg的协议兼容性,又有MPP的性能。
有一点要注意,RK3588的MPP解码器对输入码流格式有要求。H.264要的是AVCC格式或者Annex-B格式都能处理,但如果从FFmpeg拿到的是AVPacket,一般默认是Annex-B的startcode封装,直接喂给MPP是没问题的。真正容易踩坑的是某些IPC会产生SPS/PPS频繁变化或者有SEI大字段的数据,这时候需要对FFmpeg的AVPacket做一些预处理,比如把extradata里的SPS/PPS先解析出来,提前喂给MPP,这样解码器初始化会更稳定。
另外,RTP推流场景下偶尔会出现乱序包,FFmpeg内部已经做了重排序,所以不用太担心。但如果你是自己手写RTSP客户端,一定要处理RTP序号回绕和乱序,否则MPP解码会出现大量花屏,我在开发初期就吃过这个亏。
3. RK3588上MPP硬解码与硬编码的实操要点
3.1 MPP解码初始化与缓冲池管理
MPP是瑞芯微媒体处理平台的缩写,在RK3588上,它同时支持视频解码和视频编码。我们这次解码场景主要用的是H.264和H.265,两种编码格式在MPP里分别对应MPP_VIDEO_CodingAVC和MPP_VIDEO_CodingHEVC。
解码器的初始化流程基本固定,先创建MPP上下文,再设置解码类型为MPP_CTX_DEC,然后配置输入码流格式。注意,新版本的MPP接口推荐使用mpp_dec_cfg来配置解码参数,旧的直接通过control传递参数方式也不要丢弃,比较稳妥的做法是用mpp_dec_cfg_init初始化配置对象,再调用mpp_dec_cfg_set_u32设置参数。
我实际跑的简化流程大致如下:
// 解码器初始化 MppCtx ctx = NULL; MppApi *mpi = NULL; mpp_create(&ctx, &mpi); mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC); // 配置解码器 MppDecCfg cfg = NULL; mpp_dec_cfg_init(&cfg); mpp_dec_cfg_set_u32(cfg, "base:grp_size:width", 1920); mpp_dec_cfg_set_u32(cfg, "base:grp_size:height", 1080); mpp_dec_cfg_set_u32(cfg, "base:frame:format", MPP_FMT_YUV420SP); mpi->control(ctx, MPP_DEC_SET_CFG, cfg);这里我特别建议把输出帧格式设置成MPP_FMT_YUV420SP,也就是NV12。因为RK3588的NPU和RGA对NV12的亲和度很好,后续如果要转RGB走RGA也是一步到位,不用再做YUV平面重排。
缓冲池管理是解码侧最容易翻车的点。MPP内部会维护一组解码帧缓冲区,你要做的是解码前从解码器里获取空的group buffer,解码完成后拿着数据往里填。如果缓冲区不足,MPP会返回MPP_ERR_BUFFER_FULL,这时候千万不能无脑重试,否则容易卡死。正确的做法是先把已经处理完的帧释放掉,再重新取空缓冲。
简单说,我在这块踩出来的经验就是:宁可开稍大一点的缓冲池,也不要让解码器长期处于满缓冲状态。缓冲池太小会导致解码吞吐上不去,太大则导致内存占用飙升。以1080P为例,四到六帧的解码缓冲已经能稳定跑满30FPS,再多就是浪费。
3.2 解码输出帧如何高效喂给YOLO
MPP解码输出的NV12帧,不能直接塞给大多数YOLO模型。因为YOLO的输入要求是RGB三通道的连续内存,通常是640x640或者1280x1280。因此这里需要一个“缩放 + 格式转换”的步骤。
这条路有两种走法。第一种走法是传统CPU方案,用opencv的cvtColor把NV12转成BGR,再做resize。优点是好写,缺点是慢。1080P的NV12转RGB在RK3588的CPU上大概要10到20毫秒,这一步就把整个链路的性能拖垮了。
第二种走法是用RGA硬件加速,也就是RK3588里的图像处理加速器,它可以一边缩放一边做色彩空间转换。通过librga的接口,把NV12输入、RGB输出、目标尺寸640x640配置好,一次IMMEDIATE操作就能完成转换,耗时通常在2到5毫秒之间,相比CPU方案快了一个量级。
我建议你是不是要做RGA之前先别纠结,直接按RGA方案来规划。代码逻辑大概是这样的:
// 用im2d接口实现NV12→RGB缩放 rga_buffer_t src = wrapbuffer_virtualaddr(src_va, src_w, src_h, RK_FORMAT_YCbCr_420_SP); rga_buffer_t dst = wrapbuffer_virtualaddr(dst_va, 640, 640, RK_FORMAT_RGB_888); im_rect src_rect = {0, 0, src_w, src_h}; im_rect dst_rect = {0, 0, 640, 640}; RgaSURF_FORMAT format = RK_FORMAT_YCbCr_420_SP; imresize(src, dst, src_rect, dst_rect, 0);RGA还有个好处是可以在同一个操作里完成letterbox,也就是等比例缩放并填充灰边。这个在后面YOLO预处理中很关键。
3.3 硬件编码与推流对接
AI推理完成后,检测结果通常需要叠加到画面中再推流。这一步可以选择在RGB帧上画框,也可以选择仅在元数据层输出检测结果,由后端再决定怎么用。我这一次直接在画面上画框,这样预览的时候一眼就能看到检测效果。
画完框后的帧还是RGB格式,要编码成H.264,可以再走一次RGA把RGB转回NV12,然后交给MPP编码器。很多人觉得来回转换很浪费,但在RK3588上这反而是性能最优解,因为编码器本身只吃NV12。
MPP编码器的初始化和解码器类似,但参数上要多关注码率控制和GOP设置。我这边重点设了几个参数:编码类型是MPP_VIDEO_CodingAVC,码率根据场景设定为4Mbps到8Mbps,GOP也就是关键帧间隔设为2秒,帧率设为30FPS。
// 编码器配置要点 mpp_enc_cfg_set_s32(cfg, "prep:width", 1920); mpp_enc_cfg_set_s32(cfg, "prep:height", 1080); mpp_enc_cfg_set_s32(cfg, "prep:format", MPP_FMT_YUV420SP); mpp_enc_cfg_set_s32(cfg, "rc:bps", 4 * 1024 * 1024); mpp_enc_cfg_set_s32(cfg, "rc:gop", 60); mpp_enc_cfg_set_s32(cfg, "rc:fps", 30);编码器输出的H.264码流怎么送到ZLMediaKit,我放在第5节详细展开,这里先提一个原则:编码器输出的裸码流不能直接通过RTMP推流,RTMP需要FLV封装;如果走RTSP,则需要RTP打包。这些封装工作可以用ZLMediaKit的SDK辅助完成,也可以交给FFmpeg的libavformat来做,看你自己更熟悉哪条路。
4. RK3588上YOLO系列推理:模型转换与板端部署
4.1 从YOLOv8到RKNN的模型转换
YOLO系列模型在RK3588上不能直接跑原生的PyTorch权重,必须先转换成RKNN格式,也就是瑞芯微NPU专用的模型格式。这个转换过程在PC上完成,用瑞芯微官方的rknn-toolkit2工具包。
我以YOLOv8为例,大致步骤是这样:
- 在PC上安装rknn-toolkit2,通过pip安装,注意它基于Python3。
- 准备好YOLOv8的ONNX模型。可以用ultralytics官方导出脚本,也可以自己写torch.onnx.export,推荐用官方脚本,参数更省心。
yolo export model=yolov8n.pt format=onnx opset=12 simplify=True- 在PC端写一个转换脚本,加载ONNX并配置RKNN目标平台为rk3588。
from rknn.api import RKNN rknn = RKNN() rknn.config(target_platform='rk3588') rknn.load_onnx(model='yolov8n.onnx') rknn.build(do_quantization=True, dataset='dataset.txt') rknn.export_rknn('yolov8n.rknn')这里最关键的是量化。YOLOv8如果用FP16精度,在RK3588 NPU上也能跑,但速度不是最优的。INT8量化之后,推理速度能明显提升,不过量化需要准备一个校准数据集,里面的图片最好是真实场景下的视频帧,而不是网上随便找的风景图。
4.2 板端推理流程与后处理
模型转换完成后,把rknn文件拷贝到板子上,用RKNN的C接口加载,推理过程并不复杂。每次从RGA拿到640x640的RGB帧后,用rknn_inputs_set喂给NPU,再调用rknn_run,紧接着rknn_outputs_get拿到输出。
YOLOv8的输出是三个尺度的特征图,需要做解码。解码过程包括:将特征图中的检测框坐标解码到原始图像坐标系,过滤置信度低于阈值的框,最后做NMS去除重叠框。RK3588的CPU跑NMS不会太慢,但如果你检测目标很多,建议优化一下NMS逻辑,或者只保留TopK,避免CPU耗时过大。
后处理代码我简化了一下,按照YOLOv8的官方格式来:
// 假设输出在 outputs[0],shape 为 [1, 84, 8400] 或 [1, 8400, 84] // 需要根据导出模型的分支确定维度排列 for (int i = 0; i < num_anchors; i++) { float obj_conf = outputs[4 * num_anchors + i]; if (obj_conf < conf_thres) continue; // 解析 box cx, cy, w, h // 再解析 80 个类别得分 // 记录到候选列表 } // 执行 NMS这里有个小坑:不同版本的YOLOv8导出ONNX后,输出张量的排列可能不同,有的版本是[1, 84, 8400],有的版本经过优化后是[1, 8400, 84],一定要先通过Python打印输出shape,确认好了再写C后处理。
4.3 性能基线与调优方向
我实测下来的一组数据,YOLOv8n在RK3588 NPU上INT8量化后,640x640输入的单帧推理耗时大概在15到23毫秒之间,这个数据会根据NPU频率、内存带宽和是否开了多核模式有浮动。配合MPP解码、RGA缩放、MPP编码,整条1080P链路可以稳定跑到25到30FPS,CPU占用在20%到30%左右,比一开始软解软编的方案好太多。
如果还想继续压榨性能,有几个方向可以参考:模型层面可以换YOLOv5s或者YOLOv6的小模型;输入分辨率可以从640x640降到416x416,检测精度会有一点损失,但帧率能拉到35FPS以上;推理层面可以尝试把NPU和RGA的并行度拉满,让RGA处理下一帧的同时NPU推理当前帧。
5. 推流到ZLMediaKit的两种姿势与后台验证
5.1 直接推流给ZLMediaKit的RESTful API流代理
如果你不需要自己做复杂的媒体封装,最省力的方式是让ZLMediaKit通过addStreamProxy去拉取上游,处理完的视频流再交给ZLMediaKit的另一个流ID。这种方式对编码输出的码流要求不高,只要你的程序能把H.264裸流写成一个RTSP地址即可。
但在我这个场景里,最自然的做法是:推理完的帧交给MPP编码,编码器输出的H.264码流,通过自定义推流模块推给ZLMediaKit。ZLMediaKit作为服务端,会监听RTSP或RTMP的推送,你可以使用ZLMediaKit提供的openRtpServer接口先开一个RTP服务端口,然后把自己的编码器输出通过RTP方式推入。
5.2 用ZLMediaKit的MediaSource API推流
另一种更贴近ZLMediaKit设计思路的方式,是使用它的C++ SDK,在你自己的程序里直接创建一个MediaSource,把编码后的H.264帧封装进去。这样流媒体数据的流动完全在ZLMediaKit框架内完成,不经过额外的网络分发。
这个方案写起来代码量更大,但好处是延迟更低,而且你可以直接用ZLMediaKit自带的后台管理页面看到流状态。如果你对ZLMediaKit的源码结构比较熟,这套方案可以让你的程序成为一个轻量级流媒体节点。
从工程角度,我推荐大多数开发者选用FFmpeg封装推流的方式。原因很简单:MPP编码输出的是裸H.264,你要自己拼FLV tag或者自己组RTP包,都有不少细节。FFmpeg的libavformat帮你搞定了封装格式,你只需要把AVCodecContext填对,把AVPacket喂进去,它就能帮你完成RTMP或RTSP推流。
下面是我验证过的推流命令模板,适合开发阶段快速测试,编码器出来的码流先存成文件,再用ffmpeg推给ZLMediaKit:
# 从本地码流文件推流到 ZLMediaKit RTMP ffmpeg -re -i output.h264 -c copy -f flv rtmp://192.168.1.100/live/ai_stream # 推流到 ZLMediaKit RTSP ffmpeg -re -i output.h264 -c copy -f rtsp rtsp://192.168.1.100:554/live/ai_stream实际工程里,我建议直接把这段逻辑集成到主程序里,用libavformat的API来推流,而不是自己组RTP包。这套方案成熟稳定,踩坑少。
5.3 后台管理页面与流预览
ZLMediaKit自带的Web后台管理页面一般在8080端口,开启后可以直接在浏览器里看到所有通过它转发或接收的流。每次推流成功后,在页面上的“流列表”里就能看到新的流ID,点击预览就能用内置播放器直接看到实时画面,这对于验证整个链路通不通非常方便。
我自己调试时习惯先看后台管理页面的流列表有没有出现目标流,再拉流预览。这样能把问题快速分成两类:一类是流没有到达ZLMediaKit,那说明推流端有问题,需要去看编码器和推流模块;另一类是流已经出现在列表里但预览不了,那多半是播放协议或延迟参数的问题。
6. 上板过程中的常见问题与排查技巧
6.1 MPP解码报错与花屏
MPP解码最容易遇到的就是“流不全”导致的解码异常。尤其是一些IPC在弱网环境下,RTP丢包重传不及时,导致MPP收到不完整的帧。这时候解码器一般会返回MPP_ERR_INVAL或MPP_ERR_UNKNOW,同时输出一个带错误标记的帧。
我的经验是:不要对单帧报错过度敏感,MPP本身有抗丢包能力,只要后续帧正常,解码器会快速恢复。但如果连续大量报错,就要检查拉流端的组包逻辑或者网络缓冲区设置。花屏问题多出在I帧不完整但P帧继续解码的情况下,此时与其强行容错,不如主动发送IDR请求或重建解码器。
6.2 推理输入与解码帧率不匹配
当MPP解码输出30FPS,而YOLO推理只能跑到20FPS时,必然导致帧积压。如果不处理,内存会持续增长,系统最终被拖死。这里我建议做一个最简单的丢帧策略:解码器出来的帧先放进一个环形缓冲,推理端取最新的一帧来处理,缓冲区里过期帧直接丢弃。
一开始我有强迫症,每一帧都要推理,结果帧率上不去。后来想通了,AI检测场景下,丢帧换取实时性是非常划算的。设置一个最大两帧的缓冲池,取帧时永远取最新一帧,并调用mpp_buffer_put把旧帧立刻还回去,这样整条链路的内存占用非常稳定。
6.3 推流卡顿或延迟过大
卡顿和延迟是两个不同的指标,我分开说。延迟大一般是缓冲引起的,ZLMediaKit侧的缓冲队列、编码器的码控策略、播放端的缓冲设置都会影响。如果你追求低延迟,需要对ZLMediaKit做低延迟配置,比如关闭或缩短缓存区间,同时编码器要控制GOP大小,尽量不使用B帧。
卡的常见原因则是带宽不足或编码码率设置过高。我之前设置成8Mbps码率在局域网内没问题,换到4G网络就卡得不行,原因就是推流带宽不够。解决办法是调整编码器码率,或者在推流端做码率自适应。
6.4 常见问题速查表
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| MPP解码报MPP_ERR_BUFFER_FULL | 缓冲池耗尽,解码帧未被及时释放 | 检查解码帧消费逻辑,确认推理分支没有长时间持有buffer |
| 推理结果全是乱框 | 输入帧没有做letterbox,或缩放尺寸不对 | 确认RGA输出为640x640并保证RGB顺序 |
| 推流后画面偏绿 | 编码输入NV12的uv数据顺序不对 | 检查RGA输出格式是否为RK_FORMAT_YCbCr_420_SP |
| 预览画面卡顿 | 码率设置过高或播放端缓冲过大 | 降低码率,启用ZLMediaKit的低延迟配置 |
| 后台页面看不到流 | 推流地址或流ID配置错误 | 抓包看RTMP/RTSP握手是否成功 |
| RKNN推理延迟异常大 | 没有开启NPU或模型仍跑在CPU模拟环境 | 确认rknn_init的flag为0,并用rknn_query查询底层设备状态 |
6.5 几条独家避坑经验
第一,RK3588的NPU和MPP都属于硬件资源,多路并发时要小心资源冲突。如果同时开很多路解码和推理,建议先用官方性能文档估算好资源上限,再安排路数,别硬刚。
第二,编译程序时,RKNN的librknnrt.so和librga.so版本要和PC端转换工具的版本匹配,否则会出现模型加载失败或者推理结果诡异的问题。我的经验是统一使用同一版本的SDK包,不要在板子上混用不同日期的库。
第三,上电后先检查NPU频率和CPU调频策略。有些开发板默认的NPU频率很低,推理速度会慢得离谱。通过/ssys/kernel/debug/rknpu/power等节点可以查看NPU状态,必要时在代码里调用系统接口把NPU频率拉高。
7. 写在最后的几点体会
这套“ZLMediaKit + RK3588 + YOLO + MPP”全流程跑通之后,我做边缘AI产品的思路一下子清晰了很多。以前总觉得在板子上做实时视频AI很折腾,要把网络协议、硬件编解码、AI推理、流媒体分发这些不同领域的东西强行捏在一起,每个环节都能单独写一篇深度博客。但真正把链路打通之后再回头看,RK3588这套生态的成熟度确实比想象中高,只要每个环节选对工具,性能完全不是问题。
我个人在实际操作中最大的体会是:一定要先把“数据流”规划清楚再写代码。解码器吐什么格式、RGA转成什么格式、NPU吃什么格式、编码器接收什么格式,这四个问题如果一开始没想明白,后面每一层都要返工。把这几个格式定下来,整个项目的代码结构就会非常清爽。
再分享一个小技巧,开发阶段可以在MPP解码之后、YOLO推理之前,把NV12帧先存成几张图片看看内容是否正常。这一步看着简单,却能帮你快速定位问题出在拉流、解码还是推理,省下大量瞎猜的时间。
后面我打算在这个基础上再扩展两件事:一是把多路IPC同时接入,做一个多路并发推理和分发的小平台;二是尝试在推理结果中输出结构化JSON数据,配合ZLMediaKit的WebHook能力,把检测事件主动推送给上层的业务系统。到时候再回来更新这个系列,咱们下次见。