012、安防NVR/DVR多路编码架构:海思Hi3516与瑞芯微RK3588的ISP与编码协同
去年年底接了个项目,客户要把老旧的DVR方案从海思Hi3516DV300整体切到瑞芯微RK3588,理由是算力不够要上AI。结果板子回来第一天,8路1080P预览就卡成PPT,CPU占用飙到85%,编码器利用率才60%。查了半天,问题出在ISP输出格式和编码器输入格式的匹配上——海思的VI通道默认输出NV12,RK3588的ISP默认输出NV12但带了个奇怪的stride对齐,而编码器那边又要求按16字节对齐。就这么个看似不起眼的差异,导致编码器每次都要做一次内存拷贝,8路下来带宽直接打满。
先聊海思的架构。Hi3516DV300这颗芯片,ISP和编码器是紧耦合设计的,VI(Video Input)通道直接挂VGS(Video Graphics Subsystem)做缩放和格式转换,然后进VENC(Video Encoder)。它的数据通路是ISP -> VI -> VGS -> VENC,中间有个关键点:VI通道的buffer是硬件管理的,支持按帧自动轮转,而且VGS可以做到零拷贝的格式转换——只要你的目标格式和源格式在同一个内存池里,VGS直接改描述符就行,不搬数据。这个设计在安防场景非常实用,因为NVR/DVR的典型需求是:8路1080P输入,同时要出4路CIF子码流和1路主码流,海思的VGS可以一条命令链搞定一路画面的多路缩放,而且带宽开销极小。
瑞芯微这边就完全是另一套思路。RK3588的ISP(实际是RKISP3.x)输出走的是RGA(Raster Graphic Acceleration)或者直接进VPU(Video Processing Unit)的编码器。RGA是个2D图形加速器,能做缩放、旋转、格式转换,但它的设计初衷是给UI合成用的,不是给视频管线用的。问题在于,RGA的输入输出格式要求非常严格,比如NV12输入要求宽度按16对齐,高度按2对齐,而且它的stride(行字节数)计算方式跟海思的VGS不一样——海思是按实际宽度算stride,RK3588是按对齐后的宽度算。这就导致你从ISP拿到的buffer,如果直接丢给编码器,编码器会按自己的对齐规则去读,读出来的画面就是斜的或者花屏。
我在RK3588上调第一版的时候,就是直接ISP输出NV12丢给MPP(Media Process Platform)的编码器,结果8路里有3路画面是歪的,查了半天发现是stride不匹配。海思的VI通道会自动帮你把stride对齐到编码器要求的值,RK3588的ISP不会,你得自己在RGA或者CPU里做一次拷贝。后来我改成ISP输出NV12_MST(多平面格式),然后通过RGA做一次格式转换到NV12,同时指定输出stride为编码器要求的对齐值,问题就解决了。但代价是每路多了一次RGA操作,8路下来RGA的负载到了40%左右,好在RK3588的RGA有3个核,可以并行。
再说编码器本身的差异。海思的VENC是硬编码器,支持H.264/H.265,但它的码控策略比较保守,尤其是CBR模式,在场景切换剧烈的时候会突然掉帧。RK3588的VPU编码器码控更激进,但有个坑:它的GOP(Group of Pictures)结构默认是IDR帧间隔等于GOP大小,不像海思那样支持自适应IDR插入。在安防场景,这意味着如果网络丢包导致关键帧丢失,海思的方案会在下一个IDR自动恢复,RK3588可能要等很久。解决办法是在应用层做强制IDR请求,但MPP的接口里这个功能藏得比较深,得用MPP_ENC_SET_CFG配合MppEncRcCfg里的gop_mode参数,设成GOP_MODE_NORMAL然后手动触发MPP_ENC_SET_IDR_FRAME。
还有个容易踩的坑是内存分配。海思的mpp(注意,海思的mpp是Media Process Platform,跟瑞芯微的MPP同名但完全不同的东西)内存池是统一管理的,你申请buffer的时候指定MMB_VI、MMB_VGS、MMB_VENC这些属性,系统会自动做cache一致性管理。RK3588的MPP用的是dma_buf,你得自己保证cache同步,否则会出现编码出来的画面有随机噪点——尤其是ISP写入buffer后,CPU或者RGA读的时候,如果没做dma_buf_sync,那画面就是花的。我一开始没注意这个,调试了三天,最后用dma_buf_begin_cpu_access和dma_buf_end_cpu_access包住RGA操作才解决。
多路编码的调度策略也完全不同。海思的VENC是硬件多路并行,每路独立编码,你只需要配置好通道参数,硬件自己调度。RK3588的VPU虽然也是多路并行,但它的编码器核是共享的,如果一路的码率控制参数设置不当(比如码率上限设太高),会挤占其他路的带宽,导致其他路画面质量下降。我做过一个测试,8路1080P同时编码,如果一路设成4Mbps,其他路设成2Mbps,结果那路4Mbps的实际码率能跑到5Mbps,其他路掉到1.5Mbps。后来我把所有路的rc_mode设成MPP_RC_MODE_CBR,并且把max_i_bit_rate和max_p_bit_rate都设成跟目标码率一致,才把这个问题压住。
再说说ISP和编码器协同的延迟问题。安防场景对延迟敏感,尤其是PTZ控制的时候,从云台转动到画面更新,延迟超过200ms就感觉卡顿。海思的ISP到VENC的延迟大约在40ms左右,因为VI通道是零拷贝直通的。RK3588这边,ISP输出到RGA再进VPU,每级都有buffer排队,延迟轻松到80ms以上。我试过用RK3588的ISP直接输出到VPU的mpp_buffer,绕过RGA,但ISP的输出格式是NV12_MST,VPU编码器不支持,必须转。后来发现RK3588的ISP有个rkisp_vir节点,可以配置输出NV12(非MST),但这样会牺牲一些ISP的3A统计功能。权衡之下,我选择了保留RGA转换,但把RGA的buffer池缩小,减少排队延迟,最终做到60ms左右,勉强能接受。
最后说产线调试的经验。海思的方案,产线上主要用himm工具直接改寄存器,调试方便,但风险高,改错了直接死机。RK3588的调试工具是rk_mpi_aiq和rkisp_demo,可以在线调3A参数,但它的日志系统比较啰嗦,建议用export RK_LOG_LEVEL=2只打错误信息。另外,瑞芯微的MPP有个mpp_info命令,可以查看编码器状态,包括每路的帧率、码率、编码耗时,这个对产线测试非常有用,海思那边没有直接对应的工具,得自己写脚本解析/proc下的统计信息。
总结一下我的经验:如果项目是纯安防NVR/DVR,没有AI需求,海思Hi3516系列依然是更稳的选择,它的ISP和编码器协同是经过十几年量产验证的,坑少。如果一定要上RK3588做AI,那就要接受它的架构差异,重点做好三件事:一是统一stride对齐策略,二是管好dma_buf的cache同步,三是用MPP的码控参数把多路编码的带宽隔离做好。别指望RK3588能像海思那样"开箱即用",它的优势在算力不在影像管线,你得花时间把管线调顺。