一 背景回顾
上篇结尾说解码的 13.85ms 另有隐情,这篇填坑。
13.85ms 里真解码只有 0.5ms,解块(Detile,block-linear->pitch-linear)占了约 13.3ms。
Block-Linear(BL,块线性)和Pitch-Linear(PL,行线性/扫描线)是图像数据两种内存布局,之所以同时存在,根本原因在于图像处理(GPU/渲染/采样)与图像传输(显示输出/DMA/连续内存读写)在硬件缓存(Cache)命中率与总线吞吐效率上的矛盾。通常,GPU 中图像处理用 BL 布局而 CPU/显示用 PL 布局。
二 方案选择
端侧多个硬件模块支持解块,之前考虑用 GPU 解块,但是 GPU 的引入会使代码复杂,而且有“杀鸡用牛刀”的感觉:解块是固定套路的搬运活,不值得占用 GPU。最终选择片上专用硬件模块做 NV12 BL→ARGB 转换。原则就是:能下放给专用硬件的,就别让通用算力干。
新的 pipeline 如下:
图1:手关键点检测处理流程
三 调试过程遇到的坑
3.1 多平面格式跨硬件拷贝的 plane offset 陷阱
前期做视频解码后的 NV12 BL 上屏验证过程中,遇到问题:屏幕虽然能显示动画,但画面错位。
这是典型的 Y 数据对,UV 数据不对的症状。查看代码,我直接用 memcpy 把 decoder 后的 buffer 送到显示 buffer,但事实上 decoder buffer 的大小和显示 buffer 不一样:解码:[Y data][UV data],显示:[Y data][16KB pad][UV data]
根因:不同硬件模块对 NV12/NV21/I420 的 plane offset 假设不一致!这个坑在 Qualcomm UBWC、ARM AFBC 等都会遇到。
3.2 Cache 的两面性
- memcpy 慢了 40x:在做解码到显示 buffer 拷贝时,因为没有设置解码 buffer 的 cache enable,导致 profile 时候发现 memcpy 拷贝一帧(3.4MB)需要 5.6ms,这远慢于 CPU 内存拷贝的正常水平,调试发现是解码 buffer 属性没有 cache enable。
- 显示黑屏:视频解码成功,显示也没有错误,但是屏幕黑屏,无任何显示。调试发现 cache line 有数据,但内存中没有。
根因:同一个参数(cpuCache),在不同方向,表现出不同的行为:读设置错:“慢”;写设置错:“黑”。
3.3 方法论:渐进式验证
调试过程中,还有其他问题,不具体描述。实现的时候遵循纯色填充→单引擎工作→多引擎级联。
- 纯色填充:验证显示支持 ARGB/NV12 PL 等格式。
- 单引擎工作:分别验证解码/硬件颜色空间转换/显示等各硬件引擎能工作。
- 整个处理流程级联起来
嵌入式 GPU/VPU 等调试挂死就要重启,直接端到端联调成本极高!
四 实验结果
优化前后对比:
| 阶段 | 优化前耗时(ms) | 优化后耗时(ms) |
|---|---|---|
| 视频解码 | 13.85 | 0.97 |
| 颜色空间转换(NV12->RGB) | 4.23 | 0.81 |
| 去Alpha plane | N/A | 1.6 |
| 手掌模型预处理 | 1.48 | 1.44 |
| 手掌检测推理 | 0.71 | 0.70 |
| 手掌检测后处理 | 0.04 | 0.04 |
| 关键点模型预处理 | 3.45 | 3.42 |
| 关键点检测 | 1.17 | 1.15 |
| 关键点后处理 | 1.9 | 1.88 |
| 显示提交 | 4.15 | 4.15(沿用之前测试数据) |
| 总耗时 | 30.98 | 16.16 |
NOTE:受显示硬件 30Hz 限制,实际可观测帧率上限为 30fps;换 60Hz 显示硬件后预计可达 60fps。
硬件模块颜色空间转换输出的是 ARGB,比模型要的 RGB 多一个 Alpha 通道,得剥掉(1.6ms)——即便如此总账还是赚的。优化后,视频解码+颜色空间转换+去 Alpha plane=3.5ms!
dump 第 100 帧如下:
图2:第100帧手关键点检测结果
图左上角 OSD 消息显示帧率 81FPS 是没有上屏的结果。
五 心得总结
到此,MediaPipe 端侧移植和优化做阶段性总结:
- 第一篇:移植不在翻译代码,在胶水代码
- 第二篇:瓶颈不在模型,在等屏幕
- 本篇:瓶颈不在解码,在解块
回头看,端侧模型优化就是不断把活儿交还给合适的硬件,
往前看,摄像头接入整个 pipeline,真正做到“实时看世界”!