1. 项目缘起与整体设计思路
1.1 为什么要在RK3588上做8路1080P实时拼接
先说说这个需求的来源。我手头有一个多摄像头环视项目,8个1080P摄像头分别朝向不同角度,需要把画面实时拼接成一张无缝的全景图,输出给上层做目标检测和人工监看。一开始想的是把8路流全部拉到上位机用软件拼接,结果一算带宽就放弃了——8路1080P@30fps的原始YUV数据,每帧约3MB,8路就是24MB,30fps下每秒720MB的吞吐,光传输和内存拷贝就能把CPU吃满,更别提实时性了。
后来把目光转向RK3588,原因很直接:这颗芯片自带AVS(Audio Video Stitching)硬件拼接模块,能在VPU和ISP之后、显示或编码之前,直接把多路视频在硬件层面拼成一张大图。这意味着拼接这件事不占用CPU算力,也不用来回搬数据,功耗和延迟都能压下来。对于嵌入式端侧的多路视频融合场景,这几乎是目前性价比最高的方案。
这个项目适合谁看?如果你正在做多摄像头环视、鱼眼全景、大场景监控、车载360或者任何需要“多路视频合成一张图”的嵌入式项目,并且手上有RK3588的开发板,那这篇内容基本可以当作一份实操参考。我会把AVS模块的配置逻辑、数据链路、参数计算、踩过的坑都摊开讲,尽量让你少走弯路。
1.2 AVS模块到底解决了什么问题
很多人第一次听到AVS会以为是音频那个AVS编码标准,其实在RK3588的语境里,AVS指的是视频拼接硬件单元。它的核心能力是:接收多路视频输入,按照配置的布局关系,把每一路缩放到目标区域,然后叠加到一张输出画布上,整个过程在硬件流水线里完成。
它解决的核心痛点是三个:
- 算力卸载:软件拼接需要CPU或GPU做缩放、色彩转换、内存拷贝,8路1080P的负载非常可观。AVS把这些活接过去,CPU只负责配置和调度。
- 确定性延迟:硬件流水线的延迟是固定的,不会因为系统负载波动而抖动,这对实时监看和后续算法处理很关键。
- 带宽优化:拼接后的输出是一张大图,下游只需要处理一路数据,而不是八路,内存带宽和后续编码压力都大幅下降。
但要注意,AVS不是万能的。它做的是几何拼接,也就是把多路画面按位置摆放到一张画布上,并不做图像内容层面的融合、去重、光流对齐。如果你需要的是无缝的、没有重叠区域的全景融合,那AVS只能完成前半程,后半程的融合算法还得自己补。这一点在方案设计阶段就要想清楚,否则后期会返工。
1.3 整体数据链路的规划
在动手之前,我先把整条链路画清楚,这比直接改代码重要得多。RK3588的视频输入路径大致是这样的:摄像头通过MIPI CSI进入ISP,ISP输出到内存,然后可以选择送到AVS、VOP(显示控制器)或者VENC(编码器)。
我的方案是:8路摄像头分别经过ISP处理,输出1080P的NV12数据到DDR,然后AVS从DDR读取这8路数据,按照2行4列的布局拼成一张3840x2160(也就是4K)的大图,最后把这张大图同时送给VENC编码成H.264做推流,以及送给VOP做本地预览。
这里有个关键决策:为什么选2x4布局而不是其他排列。8路1080P如果按1x8排,输出就是7680x1080,横向太长,很多显示器和编码器对超宽分辨率支持不好;按4x2排是3840x2160,正好是标准4K,编码和显示都友好;按2x4排是3840x2160,同样标准。最终我选2行4列,因为摄像头物理安装是上下两排,每排4个,这样拼接后的画面方向和实际场景一致,监看时不会产生空间错乱。
提示:布局选择不只看分辨率是否标准,还要考虑摄像头实际安装方位和后续算法的坐标映射。如果拼接图和物理世界方向不一致,后面做目标定位时会非常痛苦。
2. AVS核心细节与配置要点解析
2.1 AVS的输入输出格式约束
在配置AVS之前,必须搞清楚它对输入输出的格式要求,否则会出现“配置成功但画面花屏”的情况。根据我的实测和RK3588的文档,AVS模块对输入格式有几个硬性约束:
- 输入必须是YUV格式,常见的是NV12(Y平面加交错UV平面)。RGB数据需要先经过转换,这一步通常在ISP或RGA里完成。
- 每路输入的宽高需要是偶数,1080P是1920x1080,满足要求。
- 输入路数有上限,RK3588的AVS支持最多8路输入,这正好卡在我们的需求上,再多就不行了。
- 输出画布的宽高也有范围限制,4K(3840x2160)是安全的,再大需要确认具体SDK版本的支持情况。
输出格式同样建议用NV12,这样送给VENC时不需要额外转换。如果你想让输出直接给VOP显示,NV12也是VOP支持的格式,链路最顺。
这里有个容易忽略的点:输入和输出的色彩空间要一致。如果输入是BT.601而输出配成了BT.709,颜色会明显偏掉。我在第一次调试时就遇到了肤色发灰的问题,查了半天才发现是色彩空间没对齐。
2.2 拼接布局与坐标计算
AVS的布局配置本质上就是告诉硬件:第0路画面放在画布的哪个位置,第1路放在哪里,以此类推。每个位置用四个参数描述:起始X坐标、起始Y坐标、宽度、高度。
以2行4列、每路1080P、输出4K为例,计算过程如下:
- 输出画布宽3840,高2160。
- 每格宽度 = 3840 / 4 = 960,每格高度 = 2160 / 2 = 1080。
- 第0路:x=0, y=0, w=960, h=1080。
- 第1路:x=960, y=0, w=960, h=1080。
- 第2路:x=1920, y=0, w=960, h=1080。
- 第3路:x=2880, y=0, w=960, h=1080。
- 第4路:x=0, y=1080, w=960, h=1080。
- 以此类推到第7路。
注意这里每格宽度是960,而输入是1920宽,所以AVS会自动做水平方向2:1的缩放。这个缩放是硬件完成的,不需要你额外配置。但缩放会带来画质损失,如果你不能接受,就需要调整布局让每格宽度接近1920,比如改成4行2列,每格1920x540,但这样垂直方向又压缩了。这是一个取舍,取决于你更在意水平细节还是垂直细节。
注意:缩放比例不要超过硬件限制。RK3588的AVS缩放能力有范围,极端比例可能导致画面模糊或硬件报错。建议单方向缩放比控制在4:1以内。
2.3 内存与带宽的预估
8路1080P NV12输入,每帧大小 = 1920 * 1080 * 1.5 = 3110400字节,约3MB。8路就是24MB。30fps下,输入侧每秒需要AVS读取720MB。输出4K NV12每帧 = 3840 * 2160 * 1.5 = 12441600字节,约12MB,30fps下输出侧每秒写入360MB。
加起来,AVS模块每秒要处理超过1GB的内存读写。这对DDR带宽是有压力的,尤其是在同时还有ISP写入、VENC读取、CPU访问的情况下。我的经验是:一定要给视频链路预留足够的DDR带宽,在设备树里把相关总线的优先级调高,否则会出现丢帧或画面撕裂。
实测中,如果DDR带宽吃紧,最明显的表现是AVS输出偶尔出现一条横向撕裂线,或者某一路画面突然黑一下。这时候不要怀疑代码,先去查带宽和时钟配置。
3. 实操过程与关键环节实现
3.1 环境准备与内核配置
我用的SDK是RK3588官方Linux SDK,内核版本5.10。第一步是确认AVS驱动已经编进内核。在menuconfig里,路径是Device Drivers -> Media drivers -> Rockchip Video Processing -> Rockchip AVS,把它选成编译进内核或模块。
设备树里需要配置AVS节点,关键字段包括:
avs: avs@fdb00000 { compatible = "rockchip,rk3588-avs"; reg = <0x0 0xfdb00000 0x0 0x10000>; interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>; clocks = <&cru ACLK_AVS>, <&cru HCLK_AVS>; clock-names = "aclk", "hclk"; power-domains = <&power RK3588_PD_AVS>; status = "okay"; };同时要确保8路ISP和CSI的节点都使能,并且各自绑定到正确的摄像头。这一步如果出错,后面AVS拿不到输入数据,表现是输出全黑或全绿。
3.2 通过V4L2配置AVS
AVS在用户态通过V4L2接口操作,设备节点通常是/dev/videoX。配置流程分几步:
- 打开设备,查询能力,确认支持VIDIOC_QUERYCAP。
- 设置输出格式(也就是拼接后的画布格式),用VIDIOC_S_FMT,格式NV12,宽3840,高2160。
- 设置输入路数和每路的布局,这部分RK3588用了私有扩展控制项,需要通过VIDIOC_S_CTRL传入。
- 申请缓冲区,启动流。
布局配置是核心,我用的是结构体数组,每路一个条目:
struct avs_input_rect { __u32 x; __u32 y; __u32 width; __u32 height; }; struct avs_input_rect rects[8] = { {0, 0, 960, 1080}, {960, 0, 960, 1080}, {1920, 0, 960, 1080}, {2880, 0, 960, 1080}, {0, 1080, 960, 1080}, {960, 1080, 960, 1080}, {1920, 1080, 960, 1080}, {2880, 1080, 960, 1080}, };然后通过私有ioctl把这些参数下发给驱动。不同SDK版本的私有ioctl名字可能不同,我用的版本里是RK_AVS_SET_INPUT_RECT。
3.3 输入源的绑定与同步
AVS本身不产生数据,它需要从8路输入源拿数据。在RK3588上,输入源可以是ISP的输出,也可以是内存中的DMA缓冲区。我采用的是ISP直连AVS的方式,通过media controller建立pipeline。
用media-ctl工具可以看到整个拓扑:
media-ctl -p -d /dev/media0需要把每个ISP的输出pad链接到AVS对应的输入pad。链接建立后,用v4l2-ctl启动每一路ISP的流,再启动AVS的流。
这里有个大坑:8路输入的帧同步。如果8路摄像头不是同步曝光的,拼接出来的画面在运动场景下会出现“撕裂感”,比如一个人同时出现在两格画面里但位置对不上。硬件上最好用同一个时钟源给所有摄像头提供MCLK,软件上尽量让8路流同时启动。我的做法是先启动所有ISP流,等所有缓冲区都ready后,再一次性启动AVS,这样能把不同步控制在最小范围。
3.4 输出到编码与显示
AVS的输出缓冲区可以直接送给VENC。在V4L2里,这通过export buffer再import到VENC实现,避免了内存拷贝。VENC配置成H.264,码率我设的是16Mbps,4K@30fps下这个码率能保证画质,再低就会出现明显块效应。
显示侧走VOP,把AVS输出送到HDMI。如果本地不需要预览,可以关掉VOP节省带宽。
实测整条链路跑下来,CPU占用率不到15%,主要开销在VENC和网络推流上,AVS本身几乎不占CPU。这验证了最初选硬件拼接的思路是对的。
4. 常见问题与排查技巧实录
4.1 画面花屏与颜色异常
这是最常见的问题,表现是拼接图上有绿色或紫色的条纹,或者颜色整体偏色。排查顺序如下:
- 先确认输入格式是不是NV12。如果ISP输出的是其他格式,AVS会按NV12解析,UV平面错位就会花屏。
- 检查色彩空间配置。输入BT.601、输出BT.709会导致偏色,两者要一致。
- 检查stride对齐。NV12的Y平面stride需要16字节对齐,如果ISP输出的stride和AVS期望的不一致,画面会斜切。
我遇到过一次花屏,最后发现是某一路ISP的输出分辨率被设成了1928x1080,不是标准的1920,导致AVS读取时错位。改成标准分辨率后问题消失。
4.2 某一路黑屏或绿屏
如果8路里有一路不正常,先单独抓那一路的ISP输出,确认ISP本身有没有数据。如果ISP正常但AVS里黑,检查media pipeline里那一路的link有没有建立。有时候link建立了但pad没激活,也会黑。
绿屏通常是格式不匹配,AVS把非YUV数据当YUV解析了。检查那一路的输入格式配置。
4.3 帧率不达标与丢帧
8路1080P拼4K,理论能跑30fps,但实测如果DDR带宽不够,会掉到20fps左右。排查方法:
- 用cat /sys/kernel/debug/dri/0/bandwidth查看带宽占用。
- 降低ISP的输出帧率试试,如果降帧后稳定,说明是带宽瓶颈。
- 在设备树里提高AVS和DDR相关时钟频率。
另外,VENC的码率设太高也会反压AVS,导致丢帧。先把VENC关掉,只跑AVS到内存,看帧率是否正常,以此定位瓶颈在AVS还是VENC。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 整体花屏 | 输入格式非NV12 | 检查ISP输出格式 |
| 颜色偏色 | 色彩空间不一致 | 统一输入输出色彩空间 |
| 单路黑屏 | pipeline未链接 | media-ctl检查link |
| 单路绿屏 | 格式解析错误 | 检查该路输入格式 |
| 帧率不足 | DDR带宽瓶颈 | 查看带宽占用,降帧测试 |
| 画面撕裂 | 多路不同步 | 检查摄像头时钟源和启动时序 |
| 输出全黑 | AVS未启动流 | 确认VIDIOC_STREAMON已调用 |
4.5 几个独家避坑经验
第一,不要频繁启停AVS流。AVS硬件重新初始化的开销比想象中大,如果业务需要动态切换布局,尽量用暂停/恢复而不是关闭/重开。
第二,缓冲区数量要给够。我一开始每路只申请了2个buffer,结果在高负载下出现丢帧,加到4个后稳定。buffer多了占内存,但视频链路里这点内存换稳定性是值得的。
第三,调试时先用静态图测试。把8路输入换成同一张测试图,能快速判断是拼接逻辑问题还是摄像头采集问题。这个技巧帮我省了很多时间。
第四,关注温度。8路ISP加AVS加VENC全开,RK3588的NPU和VPU区域温度会上升,如果散热不好会触发降频,帧率就掉了。加个散热片或者小风扇,能明显改善长时间运行的稳定性。
5. 性能调优与扩展思路
5.1 带宽与功耗的平衡
跑通之后,下一步就是调优。我的目标是长时间稳定运行,所以重点看两个指标:帧率稳定性和芯片温度。帧率方面,通过把VENC码率从16Mbps降到12Mbps,DDR压力小了一些,帧率波动从±3fps收窄到±1fps。温度方面,加了一个小散热片后,满载温度从85度降到72度,不再触发降频。
如果你对功耗敏感,可以考虑降低ISP的输出帧率到25fps,人眼观感差异不大,但带宽和功耗都能降一截。
5.2 从拼接图到智能分析
拼接出4K大图之后,很自然的想法是接目标检测。RK3588自带NPU,6TOPS算力,跑YOLOv8这类模型没问题。我的做法是把AVS输出同时送给VENC和NPU,NPU做检测,检测框坐标再映射回原始8路画面的坐标,这样既能看到全景,又能定位到具体是哪一路摄像头拍到的目标。
坐标映射的关键是记录每路在拼接图中的偏移量,检测框坐标减去偏移量就是该路画面内的坐标。这一步不难,但要注意缩放比例,因为每路在拼接时被缩放了。
5.3 鱼眼矫正与AVS的配合
如果摄像头是鱼眼镜头,AVS本身不做畸变矫正。我的方案是先用RGA或者GPU做鱼眼矫正,把画面展成透视投影,再送给AVS拼接。矫正这一步比较耗算力,如果8路都做,GPU压力不小。可以考虑只对重叠区域做矫正,或者降低矫正的分辨率。
这个项目后续还可以往无缝融合方向走,也就是在AVS拼接的基础上,对重叠区域做羽化或光流融合,让全景图看起来没有拼接缝。这需要额外的算法模块,但AVS已经把最重的搬运和缩放工作做完了,剩下的融合在CPU或GPU上做,压力可控。
我个人在实际操作中的体会是,RK3588的AVS模块是一个被低估的能力,很多人做多路视频时第一反应是软件拼接,结果被算力和带宽卡住。先把硬件拼接用起来,把CPU解放出来做更有价值的事,这才是嵌入式端侧多路视频处理的正确打开方式。