最近在 app 里接入了一个第三方图像处理 SDK,可以实时给视频添加很多酷炫的效果,然而运行的时候,大概过几秒钟视频的显示延迟就十分严重。排查了很久最后发现还是自己的锅。。。

分析性能首选 Instruments,跑起来之后,定位到耗时严重的地方:

看起来是使用了第三方 SDK 的锅,为了更直观的看一下这个方法的耗时,打印一下(不是精确的值):

问题似乎确定了:这个 SDK 处理一帧的时间过长。然而编译运行 SDK 提供的 Demo,十分流畅,没有卡顿的现象,因此这个问题似乎不是简单地由 SDK 引起的(毕竟人家靠这个赚钱的)。

注意到 SDK 使用的是 OpenGL ES,而我使用 Metal 来显示 CMSampleBuffer,会不会是由于在 OpenGL ES 和 Metal 之间切换 context(pipeline state)导致卡顿?事实上是不会的,GPU 切换 context 的速度很快,不是目前遇到的瓶颈的根本原因。

朋友大熊提醒我是不是因为 OpenGL ES 和 Metal 设置的颜色空间(color space)不一致导致 SDK 处理完后用 Metal 显示时 GPU 做了颜色空间的转换导致延迟,因此我检查了一下两边的颜色空间的设置,都是 bgra,因此也不是颜色空间不一致导致的问题。

光靠猜测是不行的,还是要用 Metal System Trace 跑一下看看:

从耗时来看,也没有啥问题。这时,群里一个哥们问我是渲染是如何刷新的,我是用 MTKView 来渲染显示,在它的 draw(in view: MTKView) 代理方法里将 CMSampleBuffer 转换为 texture(纹理)渲染到 MTKView 的 drawable 上去。MTKView 默认的刷新率是 60fps, 而我相机输出设置的帧率是 30 帧。问题似乎确定了:过高的刷新率导致重复渲染产生耗时。因此,我将 MTKView 的刷新率设置为与相机输出帧率一致,编译运行,果然不再卡顿了。

这次的调试过程也让我再次感受到问题往往是一个不起眼的小地方导致的,还是得考虑到各方面的因素才能解决问题。另外,在调试的过程中,我使用 iOS 11 自带的录屏功能录制,也就是一遍运行 app,一遍录制屏幕,发现 app 运行没有卡顿的现象,当时猜测是不是 SDK 跟系统产生了某种冲突,现在回想起来应该是录屏的时候系统降低了屏幕的刷新率,因此我的 app 也跟着受到影响降低了刷新率,意外的“消除”了问题。检查录屏保存下来的视频,果然帧率在 30 到 50 帧之间。