028、端到端延迟优化实战——从sensor曝光到屏幕显示的延迟拆解与高通骁龙平台的流水线优化
028、端到端延迟优化实战——从sensor曝光到屏幕显示的延迟拆解与高通骁龙平台的流水线优化去年年底有个项目客户拿了一台基于SM8350的样机过来说预览取景框里挥动手臂屏幕上的画面明显“慢半拍”而且这个慢半拍不是均匀的是那种一顿一顿的滞后感。我们当时第一反应是“这还不简单调低分辨率、关降噪、开性能模式”结果全试了一遍客户还是摇头。后来把示波器接上从sensor的VSYNC到显示端的DSI TE信号一帧一帧数格子才发现问题根本不在ISP处理速度而在整个流水线的“相位错位”——sensor出帧的节奏和显示刷新的节奏没对齐导致每一帧数据在中间环节多等了大半个vsync周期。今天就把这个拆解过程写出来全是实际调试时踩过的坑。先明确一个概念端到端延迟不是“处理时间”的简单累加。很多人一上来就盯着ISP的吞吐量觉得只要单帧处理够快延迟就低。但真实系统里延迟的构成是“曝光等待 读出时间 传输时间 处理时间 显示排队时间”。其中最容易忽略、也最坑人的是“曝光等待”——sensor不是你想让它什么时候曝光就什么时候曝光的它得等下一个vsync的上升沿。假设你的sensor工作在30fps周期33.3ms如果应用层在t0时刻下发了一个“开始预览”的命令但sensor的下一帧曝光窗口在t020ms才开启那这20ms就是纯纯的等待还没算曝光本身的时间。高通的Camera HAL里有个SensorSynchronizer就是干这个的但很多第三方集成商根本没调它默认配置下sensor和ISP的同步是靠“碰运气”的。再往下拆读出时间。这个跟sensor的接口类型强相关。骁龙平台主流是MIPI CSI-24 lane每lane速率通常在1.5Gbps到2.5Gbps之间。假设一颗48M像素的sensor10bit RAW一帧数据量大概480Mbit在2Gbps的链路上纯传输就要240ms——这显然不可能所以实际sensor内部都有多路并行读出或者用了DOLDigital Overlap模式把曝光和读出重叠起来。但DOL有个副作用它会引入“帧内延迟”也就是同一帧的不同行曝光时间点不同这在运动场景下会产生果冻效应而且对延迟的影响是隐性的——你测出来的延迟是“最后一行读出完成”的时间但用户感知到的可能是“画面中心区域”的延迟这两者能差出好几毫秒。高通的CamX框架里有个SensorMode配置里面有个cropInfo如果你没把sensor的ROI设置成跟实际输出分辨率一致读出时间会被白白拉长。传输时间这块骁龙平台有个容易被忽视的点ICPImage Control Processor和IFEImage Front End之间的带宽分配。如果你同时开了三路流预览、拍照、视频IFE的带宽会被抢占导致预览流的数据在内部FIFO里排队。我们当时用perfetto抓trace发现预览帧在IFE到ICP之间平均多等了4.2ms就是因为后台有个4K视频录制在抢带宽。解决办法是把预览流设置成HighPriority在CamX的Pipeline配置里有个priority字段默认是Normal改成High之后排队时间降到了0.8ms。但注意别把拍照流也设成High否则会跟预览抢资源反而增加抖动。处理时间也就是ISP的pipeline延迟这个相对固定。骁龙的IFE是硬件流水线从RAW输入到YUV输出典型延迟在2到3帧之间取决于你开了多少降噪和HDR。但这里有个坑如果你用了Multi-Camera的虚拟流比如广角长焦融合处理时间会翻倍因为要等两个sensor的帧都到齐才能做对齐。我们当时为了做平滑变焦开了双摄融合结果预览延迟从80ms飙到150ms后来改成“切换式变焦”先出广角画面再切长焦延迟立刻降回来。所以融合功能不是免费的它拿延迟换画质你要想清楚产品定位。最后是显示排队时间。这个最容易被忽略因为它是“显示系统”的事不归camera管。骁龙平台的显示控制器DPU有VTSWVideo Timing Switch机制它会把camera送来的帧缓存到Layer Mixer里等到下一个DSI TE信号来的时候才刷出去。如果你的camera帧率是30fps显示刷新率是60Hz那理论上每两帧显示周期会插入一帧camera帧但DPU的调度策略是“尽可能晚地取帧”以减少撕裂。这就导致camera帧在DPU里平均多等了一个显示周期16.7ms。解决办法是让camera的vsync和显示器的TE信号同步高通叫DisplaySync在SurfaceFlinger里配置EXTERNAL_VSYNC把camera的帧率锁定到显示刷新率的整数分之一。我们当时把预览帧率从30fps改成60fpssensor支持的话延迟直接降了12ms因为每帧在DPU里最多等一个vsync而不是两个。现在把整个链路串起来算一笔账。假设sensor是30fps曝光时间10ms读出时间8msDOL模式MIPI传输2msIFE处理5ms单帧ICP到DPU排队3msDPU等待16.7ms。总延迟 曝光等待平均16.7ms因为不知道什么时候下发命令 曝光10ms 读出8ms 传输2ms 处理5ms 排队3ms 显示等待16.7ms 61.4ms。但用户实际感知的延迟还要加上“人眼反应时间”和“触控采样到应用层的时间”所以体感上会超过80ms。这还没算HDR多帧合成、夜景模式这种动辄多等2到3帧的功能。优化思路分三层。第一层是“减少等待”把sensor的曝光窗口跟应用层的命令下发对齐高通有AEAuto Exposure的FrameDuration配置你可以把曝光窗口的起始时间提前但别超过sensor的最小帧间隔否则会丢帧。第二层是“减少处理”关掉不必要的ISP模块比如预览流不需要MFNR多帧降噪也不需要HDR这些在CamX的Usecase里可以按流配置。第三层是“减少排队”把预览流的buffer数量从4个减到2个减少在内存里的驻留时间但注意别减到1个否则sensor的下一帧会因为没有空buffer而阻塞反而增加延迟。还有一个容易踩的坑BufferQueue的acquire和release时机。如果你在应用层用了ImageReader默认的maxImages是2但如果你在onImageAvailable回调里做耗时操作比如转成Bitmap就会阻塞生产者的dequeueBuffer导致sensor侧停等。我们当时在预览流里加了一个“画质增强”的滤镜每帧处理要15ms结果预览延迟直接爆表。后来把滤镜挪到后台线程用ImageReader的acquireLatestImage只取最新帧丢弃旧帧延迟才恢复正常。最后说点个人经验。第一别迷信“低延迟模式”这种开关它通常只是把帧率调高但没解决相位错位的问题你得自己去看vsync的相位关系。第二调试延迟一定要用“硬件事件”来标定别用System.currentTimeMillis()那玩意儿误差太大。高通有CameraTrace工具可以精确到微秒级把sensor_sof、ife_done、display_te这些事件打点画在一张时间轴上一眼就能看出瓶颈在哪。第三量产阶段一定要做“温度漂移测试”因为sensor的时钟在高温下会漂移导致帧率变化进而影响跟显示器的同步。我们有个项目在实验室测得好好的一到户外35度就延迟变大最后发现是sensor的PLL在高温下锁不住帧率从30fps掉到29.7fps累积几秒后就差了一帧DPU那边就开始等。这套东西说穿了就是“把每一毫秒都算清楚然后让每一毫秒都花在刀刃上”。别指望有一个万能配置能通吃所有场景你得根据产品形态是拍照为主还是视频为主、sensor特性读出速度、DOL支持、显示刷新率60Hz还是120Hz来动态调整。我们现在的做法是做一个“延迟预算表”在启动时根据当前场景动态分配各环节的延迟预算比如在暗光下牺牲一点延迟换降噪在运动场景下牺牲一点画质换低延迟。这才是量产级该有的思路。