ARTICLE DETAIL

资讯详情

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

RK3588硬件加速实战:8路1080P实时拼接与AVS模块配置

RK3588硬件加速实战:8路1080P实时拼接与AVS模块配置 1. 项目缘起与整体方案拆解1.1 为什么要在RK3588上做8路1080P实时拼接先说说这个需求的来龙去脉。我手头有一个多路摄像头监控场景需要把8个1080P的摄像头画面实时拼成一张超宽全景图输出给大屏或者编码推流。一开始想的是用x86工控机加独立显卡来做但功耗、体积、成本三座大山压下来实在不划算。后来把目光转向RK3588这颗芯片原因很直接它自带AVSAudio Video System这里特指视频拼接相关的硬件加速模块和VPU编解码单元8路1080P的解码能力是写在规格书里的而且功耗只有几瓦整板成本能压到x86方案的三分之一以下。但这里有个关键点很多人会忽略RK3588的AVS模块并不是一个“万能拼接器”。它本质上是一个硬件加速的几何变换与混合单元能做透视变换、仿射变换、多路叠加但拼接逻辑、对齐参数、融合策略这些还是得你自己算、自己配。换句话说硬件给你的是“画笔”画什么、怎么画是软件的事。我选择这个方案的核心考量有三条第一实时性要求是硬指标8路1080P30fps意味着每秒要处理约5亿像素的读写纯CPU方案根本扛不住第二RK3588的AVS模块支持多路输入同时做变换后叠加这是硬件级的并行能力第三整个链路可以跑在Ubuntu或者Android上开发效率比裸机高太多。1.2 整体数据流与模块分工整个系统的数据流我画成了一条清晰的链路从摄像头到最终输出摄像头采集 → MIPI CSI/ USB 输入 → V4L2 取流 → VPU 硬解如果是压缩流→ AVS 几何变换与拼接 → RGA 缩放/格式转换 → 编码输出或直接送显示这里面每个环节都有讲究。摄像头如果是MIPI接口的直接走ISP管线延迟最低如果是USB摄像头或者网络RTSP流那就得先解码成NV12格式再送AVS。我实测下来MIPI摄像头的端到端延迟能控制在80ms以内USB摄像头因为UVC协议的开销会多出30到50ms。AVS模块在这里的角色是“拼接核心”。它接收多路NV12或RGB格式的输入帧按照你配置的变换矩阵对每路做透视变换然后把变换后的图层叠加到一张大画布上。注意AVS不做图像融合也就是说重叠区域的过渡处理需要你自己在送AVS之前或者之后用软件做。我一开始以为AVS能自动做羽化融合结果踩了坑后来是在RGA阶段加了一个alpha混合才解决。VPU负责的是解码和编码。8路1080P如果都是H.264/H.265压缩流那必须用VPU硬解软解的话CPU占用率直接爆表。RK3588的VPU支持8路1080P30fps解码这个规格是实打实的我实测同时解8路H.265 1080PCPU占用率不到15%。RGA是2D图形加速器负责缩放、旋转、格式转换。AVS输出的画布如果分辨率太大比如8路拼成7680x1080那送显示之前可能还需要RGA做一次缩放适配屏幕。RGA的吞吐量很高做一次4K到1080P的缩放只需要几毫秒。1.3 方案选型为什么不用软拼接有人可能会问为什么不用OpenCV或者GStreamer的软拼接插件答案很简单性能不够。我做过对比测试用OpenCV的stitcher模块拼8路1080P在RK3588的A76大核上跑单帧处理时间超过200ms也就是不到5fps完全达不到实时要求。而且CPU占用率直接拉满其他任务都没法跑了。用AVS硬拼接的好处是几何变换和叠加都是硬件并行执行的不占CPU。你只需要在初始化的时候把变换矩阵算好、配好之后每帧就是DMA搬运和硬件流水线处理。我实测AVS拼接8路1080P单帧处理时间在8ms左右加上VPU解码和RGA后处理整个链路能稳定在30fps。当然硬拼接也有代价。AVS的变换矩阵是固定的你不能每帧动态调整除非重新配置寄存器。这意味着如果你的摄像头有轻微抖动或者需要动态调整拼接参数硬拼接就不太灵活。我的做法是在系统启动时做一次标定把变换矩阵算准之后就不动了。如果确实需要动态调整那就得在AVS之后再加一层软件校正但那样会增加延迟。2. AVS模块核心细节与配置要点2.1 AVS模块的能力边界与输入输出格式RK3588的AVS模块在规格书里叫“AVS Video Stitcher”但它的实际能力比名字听起来要宽泛。它支持最多16路输入每路可以做独立的透视变换、仿射变换、裁剪和缩放然后叠加到一张最大8192x8192的输出画布上。输入格式支持NV12、NV16、RGB888、RGBA8888等输出格式主要是NV12和RGB。这里有个关键限制AVS的输入路数虽然标称16路但实际能同时跑多少路取决于带宽和VPU的解码能力。我实测8路1080P NV12输入AVS的带宽占用大约在6GB/s左右已经接近DDR的带宽瓶颈了。如果你要跑更多路要么降低分辨率要么降低帧率。另一个坑是AVS的变换矩阵精度。它用的是定点数运算具体是S15.16格式也就是1位符号位、15位整数位、16位小数位。这意味着你的变换矩阵参数需要先转成定点数再写入寄存器。我一开始直接写浮点数结果画面完全错乱后来查了手册才知道要转定点。转换公式是定点值 浮点值 × 65536然后取整。输出画布的大小也有限制。最大宽度是8192最大高度是8192但实际能跑多大取决于DDR带宽。我试过拼成7680x1080也就是8路1080P横排AVS能稳定跑30fps。如果拼成3840x2160的2x4布局也能跑但带宽占用会更高。2.2 变换矩阵的计算与标定方法拼接的核心是变换矩阵。对于平面拼接每路摄像头相对于全景画布的位置和角度决定了它的透视变换矩阵。我用的标定方法是在地面上放一个棋盘格用每路摄像头分别拍一张然后用OpenCV的findHomography算出每路到全景画布的变换矩阵。具体步骤是这样的首先在全景画布上定义好每路摄像头的目标区域比如8路横排每路占960x1080。然后对每路摄像头找到它拍摄的棋盘格角点和全景画布上对应的目标角点做匹配用findHomography算出3x3的变换矩阵。这个矩阵就是AVS需要配置的参数。但这里有个细节AVS的变换矩阵是3x3的但实际配置的时候需要拆成多个寄存器。具体来说AVS的透视变换公式是x (m00*x m01*y m02) / (m20*x m21*y m22) y (m10*x m11*y m12) / (m20*x m21*y m22)你需要把m00到m22这9个参数分别转成定点数然后写入对应的寄存器。我写了一个Python脚本来自动化这个过程输入是OpenCV算出的浮点矩阵输出是寄存器配置值。标定的时候要注意棋盘格要尽量覆盖摄像头的整个视野尤其是边缘区域。如果只标定中心区域边缘的变换误差会很大拼出来的画面会有明显的错位。我一开始只用了中心区域的角点结果边缘错位了十几个像素后来把棋盘格铺满整个视野才解决。2.3 重叠区域的处理与融合策略8路摄像头如果视野有重叠那重叠区域的融合就是必须处理的。AVS本身不做融合它只是把变换后的图层叠加后叠加的会覆盖先叠加的。所以如果你直接拼重叠区域会出现明显的硬边。我的做法是在AVS之后加一层RGA的alpha混合。具体来说AVS输出的是多路叠加后的画布但我在叠加之前先让每路摄像头的数据带一个alpha通道然后在RGA阶段做加权混合。但AVS不支持alpha通道输入所以这个方案行不通。后来我换了一个思路在AVS之前先用RGA对每路做一次羽化处理把边缘的alpha值渐变到0。但AVS还是不支持alpha所以羽化也没法直接做。最终的解决方案是在AVS之后用CPU或者RGA做一次后处理。具体来说AVS输出一张拼接好的画布但重叠区域是硬边。我在画布上预先定义好重叠区域的位置然后用RGA对重叠区域做一次线性插值混合。RGA支持两个输入做alpha混合所以我可以把AVS的输出和另一路偏移后的输出做混合。但这个方案会增加一次RGA操作延迟会增加几毫秒。如果对延迟要求极高那还有一个办法在标定的时候尽量让摄像头的视野不重叠或者重叠区域极小。这样就不需要融合直接拼就行。但这样对摄像头的安装位置要求很高实际场景中很难做到。3. 实操过程与核心环节实现3.1 环境搭建与驱动配置我用的板子是正点原子的RK3588开发板系统是Ubuntu 22.04内核是Rockchip的5.10版本。首先需要确认AVS驱动是否已经加载。用lsmod | grep avs查看如果没有需要手动加载modprobe avs。然后检查/dev/avs设备节点是否存在。VPU的驱动是mppMedia Process Platform需要确认/dev/mpp_service存在。RGA的驱动是/dev/rga。这三个设备节点是后续所有操作的基础。摄像头方面我用的是MIPI摄像头通过/dev/video0到/dev/video7访问。如果是USB摄像头设备节点可能是/dev/video8开始。用v4l2-ctl --list-devices可以查看所有摄像头。这里有个坑RK3588的MIPI CSI接口有多个但并不是所有接口都支持8路同时输入。我用的板子只有4个MIPI CSI接口所以8路摄像头是分两组每组4路通过MIPI开关切换。如果你要8路同时输入需要确认板子的MIPI接口数量或者用USB摄像头补充。3.2 摄像头取流与VPU解码取流我用的是V4L2的mmap方式这是效率最高的。每路摄像头开一个线程用VIDIOC_DQBUF取帧然后送VPU解码。如果是MIPI摄像头输出的一般是NV12格式不需要解码直接送AVS就行。如果是USB摄像头输出的是MJPEG或者H.264那就需要VPU解码。VPU解码我用的是Rockchip的MPP库。初始化的时候创建MppCtx和MppApi然后配置解码器类型为H.264或H.265。每帧送进去回调里取解码后的NV12帧。MPP的API是异步的所以需要处理好帧的同步。我实测8路H.265 1080P解码VPU的占用率在60%左右还有余量。但如果8路都是H.264 High Profile占用率会到75%。所以如果可能的话尽量用H.265编码效率更高。解码后的帧需要送AVS。这里要注意AVS的输入帧必须是连续的物理内存所以需要用DMA-BUF来传递。MPP解码输出的帧本身就是DMA-BUF可以直接送AVS。如果是V4L2取流也需要用DMA-BUF导出。3.3 AVS配置与拼接执行AVS的配置我写了一个C程序通过ioctl来配置寄存器。首先打开/dev/avs然后用AVS_IOC_SET_INPUT配置每路输入的格式、分辨率和变换矩阵。变换矩阵的9个参数需要转成定点数然后写入。配置完输入后用AVS_IOC_SET_OUTPUT配置输出画布的分辨率和格式。然后就可以用AVS_IOC_START启动拼接。每帧的流程是把8路输入帧的DMA-BUF fd传给AVS然后AVS硬件自动做变换和叠加输出到指定的输出缓冲区。这里有个关键点AVS的输入帧和输出帧都需要是DMA-BUF而且物理地址要连续。我用的是Rockchip的drm框架来分配DMA-BUF确保物理连续。如果物理不连续AVS会报错或者输出花屏。拼接执行的时候AVS是硬件自动触发的你只需要把输入帧准备好然后调用AVS_IOC_RUN硬件就会开始处理。处理完成后会触发一个中断你在中断处理里取输出帧。整个过程是异步的所以需要处理好帧的同步和缓冲区的管理。我实测AVS拼接8路1080P单帧处理时间在8ms左右加上VPU解码的5ms和RGA后处理的3ms整个链路在16ms左右也就是60fps的余量。但实际跑的时候我限制在30fps因为摄像头的帧率就是30fps。3.4 输出编码与推流拼接好的画布如果要在本地显示可以直接送DRM显示。如果要推流那就需要VPU编码。我用的是MPP的编码器配置成H.265码率8Mbps分辨率7680x1080。编码后的码流通过RTSP推流用的是Live555或者GStreamer的rtsp服务器。这里有个坑7680x1080的分辨率不是标准的16:9有些播放器可能不支持。我后来改成7680x2160也就是上下加黑边兼容性更好。编码的时候要注意VPU的编码器对非标准分辨率的支持有限最好用标准分辨率。推流的时候RTSP的传输方式我用的TCP因为UDP在丢包的时候画面会花。TCP的延迟会高一点但稳定性好很多。我实测端到端延迟在200ms左右对于监控场景来说完全可以接受。4. 常见问题与排查技巧实录4.1 画面错位与变换矩阵问题最常见的问题就是画面错位。我一开始拼出来的画面相邻两路之间有明显的错位尤其是边缘区域。排查下来发现是变换矩阵的定点数转换有问题。AVS的定点数是S15.16格式但我一开始用的是S10.22导致精度不够。解决方法是确认AVS的定点数格式然后重新转换。转换的时候要注意浮点值乘以65536之后要取整但取整的方式会影响精度。我用的是四舍五入比直接截断的精度高很多。另一个错位的原因是标定不准。如果棋盘格的角点检测有误差算出来的变换矩阵就会有偏差。我的做法是多拍几张取平均值。然后用OpenCV的calibrateCamera做一次相机标定把畸变也考虑进去。这样拼出来的画面会准很多。4.2 带宽不足与帧率下降8路1080P的数据量很大如果DDR带宽不够AVS就会丢帧或者降帧。我一开始跑的时候帧率只有15fps排查下来发现是DDR带宽被占满了。解决方法是优化内存访问。首先确保所有DMA-BUF都是物理连续的这样DMA的效率最高。其次减少不必要的格式转换。比如如果摄像头输出的是NV12那就直接送AVS不要转成RGB再送。NV12的带宽占用比RGB小一半。另外AVS的输出画布不要设得太大。我一开始设成8192x2160带宽直接爆了。后来改成7680x1080带宽就降下来了。如果确实需要大画布那就降低帧率或者用压缩格式。4.3 重叠区域硬边与融合问题前面提到过AVS不做融合重叠区域会有硬边。我试过几种解决方案最后用的是RGA后处理。具体来说在AVS输出画布上把重叠区域标记出来然后用RGA对重叠区域做一次线性插值混合。RGA的混合操作需要两个输入一个是AVS的输出另一个是偏移后的AVS输出。偏移量就是重叠区域的宽度。然后配置RGA的alpha混合模式让两个输入按权重混合。权重从0到1线性变化这样就能实现羽化效果。但这个方案会增加一次RGA操作延迟增加3到5ms。如果对延迟要求极高那就只能接受硬边或者在标定的时候尽量让重叠区域小。4.4 常见问题速查表问题现象可能原因排查方法解决方案画面错位变换矩阵定点数格式错误检查S15.16转换重新转换四舍五入画面错位标定不准检查棋盘格角点多拍取平均加畸变校正帧率下降DDR带宽不足用dmesg看带宽报警优化DMA-BUF减少格式转换帧率下降AVS输出画布太大检查画布分辨率降低画布大小或帧率重叠区域硬边AVS不做融合检查重叠区域用RGA做alpha混合花屏DMA-BUF物理不连续检查DMA-BUF分配用DRM分配连续内存解码失败VPU占用率过高用top看CPU和VPU降低分辨率或改用H.2654.5 实操心得与避坑建议第一个心得是标定一定要做准。我一开始觉得标定差不多就行结果拼出来的画面错位严重返工了好几次。后来我用了高精度的棋盘格多角度拍摄取平均值才把误差控制在1个像素以内。第二个心得是DMA-BUF的管理很重要。RK3588的AVS和VPU都依赖DMA-BUF如果DMA-BUF的物理地址不连续硬件就会报错。我建议用DRM框架来分配DMA-BUF这样能保证物理连续。如果自己用malloc分配然后转DMA-BUF大概率会出问题。第三个心得是不要追求极限性能。我一开始想把8路1080P拼成4Kx4K结果带宽根本不够。后来降到7680x1080稳定跑30fps效果也很好。实际场景中够用就行不要为了参数好看而牺牲稳定性。第四个心得是调试的时候用dmesg看内核日志。AVS和VPU的驱动都会在dmesg里输出错误信息比如带宽不足、DMA错误等。这些信息比你自己猜要准得多。第五个心得是如果要用USB摄像头尽量选UVC免驱的而且支持MJPEG输出的。MJPEG的解码比H.264简单VPU的占用率低。但MJPEG的带宽占用高所以如果USB带宽不够还是得用H.264。5. 性能优化与扩展思路5.1 带宽优化与内存布局调整带宽是8路拼接的瓶颈。我做过测试8路1080P NV12输入每帧的数据量是8 × 1920 × 1080 × 1.5 24.9MB。30fps的话每秒就是747MB的输入带宽。加上AVS的输出带宽总共超过1.5GB/s。RK3588的DDR带宽理论上是LPDDR4x 4266Mbps实际可用带宽在10GB/s左右所以1.5GB/s不算高但加上VPU解码和RGA后处理总带宽会到3GB/s左右还是有余量的。但如果你的画布更大或者帧率更高带宽就会吃紧。优化方法是第一用NV12而不是RGBNV12的带宽是RGB的一半第二用DMA-BUF的压缩格式RK3588支持AFBCArm Frame Buffer Compression能压缩30%到50%的带宽第三减少不必要的内存拷贝尽量用零拷贝的方式传递帧。5.2 多路扩展与级联方案如果你需要拼更多路比如16路那单靠一颗RK3588可能不够。我的思路是级联用两颗RK3588每颗拼8路然后第二颗把第一颗的输出和自己的8路再拼一次。但这样会增加延迟而且两颗芯片之间的数据传输需要走PCIe或者千兆网带宽可能不够。另一个思路是降低分辨率。如果16路都是720P那数据量和8路1080P差不多单颗RK3588也能扛。所以如果你的场景允许降分辨率那扩展就很容易。5.3 与AI分析的结合拼接好的全景图可以送NPU做AI分析比如人形检测、车辆识别。RK3588的NPU算力是6TOPS跑YOLOv8这种模型1080P输入能到30fps。但全景图是7680x1080直接送NPU太大了需要先切分成多个1080P区域分别送NPU然后再把结果合并。我实测下来切分成8个1080P区域每个区域跑YOLOv8NPU的占用率在80%左右还能接受。如果模型更大那就得降低帧率或者用更小的输入分辨率。这个方案后续还可以扩展成多芯片协同比如一颗RK3588做拼接另一颗做AI分析通过PCIe或者千兆网传输拼接后的画布。但这样会增加成本和复杂度适合对AI要求高的场景。
返回列表