乐趣区

关于京东云:京东小程序接入ARVR的技术方案和性能调优-京东云技术团队

作者:京东批发 戴旭

京东小程序是一个凋谢技术平台,正在被越来越多的头部品牌抉择,用于站内私域流量的营销和经营。诸如各种日化、奢侈品等品牌对 ARVR 有较多的诉求,心愿京东小程序引擎提供一些底层能力,叠加品牌自主的个性化开发和定制,以反对更加丰盛的场景和玩法,比方 AR 试妆、试戴等。

咱们小程序引擎联结 ARVR 团队,在单方产研测的致力和合作下,实现了相干能力的设计和开发。整体性能于京东 APP11.6.6 版本公布上线,期待为更多的商家和品牌赋能。

体验门路和成果(负责相干模块的产品小姐姐情谊录屏)

技术计划

这里以人脸识别为例,先介绍整体的技术计划。

概念介绍

技术关键词:相机、实时帧、AR 算法、同层渲染、WebGL。

这几个关键词外面,前三个比拟好了解,人脸识别,会用相机采集人脸的实时帧数据,调用 AR 算法,获取计算结果,把数据传输给小程序前端。

前面两个关键词和小程序的场景有关系,WebGL 技术是小程序为了反对游戏、ARVR 等高性能渲染的需要,采纳原生的 OpenGL 实现了一套 WebGL 的接口。小程序页面是 WebView 渲染,而咱们既然提到了采纳 OpenGL 原生渲染,就须要把原生组件,正确的插入到 Web 的视图层级,同层渲染就是将原生组件和 WebView DOM 元素放在一起进行混合渲染的技术,可能保障原生组件和 DOM 元素在渲染层级、滚动、触摸事件处理等方面保持一致。

总体流程

小程序引擎在底层原生反对了相机、实时帧、AR、WebGL 等能力,同时裸露了若干 js 的 api。小程序开发者通过相干 api 的调用,执行开启相机、获取实时帧数据,调用 AR 接口,获取计算结果数据,进行 WebGL 渲染等操作。简要的流程如下:

分层设计

从分层的角度看整个技术计划的设计,大抵如下:

其中在 AR 引擎这一层,分为内置和内部 AR 引擎,也是因为小程序自身是凋谢的技术平台,咱们采纳了接口协议化的设计,反对第三方宿主采纳自主的 AR 引擎,同时提供了相机、实时帧、WebGL 等原子化能力,小程序服务商能够构建专有的 AR 引擎为下层业务赋能。

技术挑战

WebGL 技术原理的篇幅过大,它也不仅仅是为了 ARVR 这个场景服务,所以包含 AR 算法之内,都不在本篇的具体介绍范畴之内。

在这部分,咱们专一于小程序和 ARVR 叠加的畛域:内存和帧率的优化。

咱们晓得在观赏电视和电影画面时,只有画面刷新率达到 24 帧 / 秒,就能满足人们的需要,也就是说咱们至多要在中端甚至中低端的机器上达到 24 帧以上的帧率。

为了保障根本的画质,相机实时帧的分辨率设置为 1280*720,以 RBGA 格局存储,那么每一帧的数据是 1280*720*4=3686400Byte,约 3.5MB,每秒 24 帧以上的帧率,这个是不小的数据量。总的来说,在性能优化上,咱们遇到的次要挑战如下:

挑战 1, 数据从原生传输到 js,在从 js 传递到原生,如此大的数据量将会成为 js 和原生通信的瓶颈;

挑战 2, 在 iOS 平台上,相机 output 只能指定 BGRA 格局,因为原始相机实时帧 CMSampleBufferRef 对象内蕴含 CVPixelBuffer 对象,CoreVideo 对象不反对 RGBA 格局,参考官网文档
https://developer.apple.com/library/archive/qa/qa1501/_index….

而 WebGL 规范的接口不反对 BGRA 格局,参考文档:
https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderi…,数据格式的转换会减轻性能的累赘;

挑战 3, 即使以 24 帧为规范,每一帧的解决工夫大概只有 41ms,须要经验原生相机生产、数据格式转换、数据双向传输、ar 算法、webgl 绘制等流程,每一环节都很重,咱们须要思考如何利用并发调度劣势,并且保障实时帧的时序不会产生错乱,因为时序一旦乱了,影像尽管始终在输入,然而视觉感触是凌乱的。

针对上述挑战,进行了一系列的优化,最终在中低端手机(iPhone8 Plus)上达到均匀 26~27 帧的帧率,整体体验较为晦涩,具体调优上面具体介绍。

性能调优

1、数据传输优化

原生和 js 之间传输大量的数据会成为性能的瓶颈,数据传输优化就是缩小数据传输频次,最好是数据保留一份,只传递数据的标记。

咱们设计了一个 NativeBuffer 缓存来优化这个问题。次要流程如下

然而在 js 环境中,最终还是要应用 js 对象,原生相机实时帧的数据须要被转换为 js 对象。那么如何做能力让数据只保留一份呢?

NO COPY

iOS 端抉择运行小程序的 js 框架是 JavaScriptCore,JavaScriptCore 提供了一些 C 语言的接口办法,能够以 NO COPY 的形式,把一个 void 类型的二进制数据指针作为 backing store,创立绝对应的 js 对象,个别类型是 ArrayBuffer 或者 TypeArray。也就是说原生和 js 对象背地的数据是同一份,共享这部分内存。

这样一来咱们只须要保障缓存的原始相机实时帧的数据不开释,那么 js 对象援用的这部分数据就会始终无效。那这部分数据要在什么时候去清理呢?

销毁

在创立 js 对象的时候,能够指定一个 C 的函数指针作为入参。当 JavaScriptCore 检测到这个 js 对象销毁的时候,会主动触发该 C 函数的调用。咱们须要依照指定的函数原型实现一个 C 的办法,在这个函数里去做缓存的清理,能够看一下这个函数的原型:

typedef void (*JSTypedArrayBytesDeallocator)(void* bytes, void* deallocatorContext);

该函数有 2 个参数,第一个 bytes 是原始相机实时帧的二进制数据,第二个是上下文环境,这里咱们传的是 NativeBuffer 治理类的实例,在这个函数的具体实现中,咱们去匹配 NativeBuffer 治理的缓存地址,找到相干数据进行清理。

写入优化

后面咱们说过,数据流转是双向的。原生把相机的数据传输到 js 侧,js 调用 ARVR 的人脸检测接口,还须要把这份数据在传输到原生。因为相机和人脸检测是互相独立的接口,js 拿到相机数据不肯定非要调用人脸检测,调用人脸检测的数据也不肯定非要来自于相机,还能够是一个本地的图片。

绝对应的,咱们在 NativeBuffer 的设计中,提供数据双向传递的接口,getNativeBuffer:id 和 setNativeBuffer:id。在原生传递到 js 的数据中,咱们用了 NO Copy 的形式去做优化,那么在 js 传递到原生的数据,因为咱们不晓得数据起源,所以须要开拓一份新的内存空间,调用 memcpy 复制数据。然而实际上,咱们在做数据复制之前,能够用 JavaScriptCore 提供的接口,从 js 的 ArrayBuffer 对象中提取到实在数据的内存地址,而后在 NativeBuffer 缓存池中查找,如果找到了则无需再做数据复制。这样保障了数据始终只有一份。

 数据类型

在实际的过程中,js 端在抉择二进制对象的数据类型的时候,可能会用 ArrayBuffer 或者 TypeArray。一旦 js 端进行了数据类型转换,比方 ArrayBuffer 转 TypeArray,引擎在调用 setNativeBuffer 的时候,传递的是转换后的数据类型,将会导致 setNativeBuffer 外部的写入优化生效,进而在低端机上带来显著的卡顿。在这里,咱们对立应用统一的数据类型,不能随便的转换数据类型。

2、相机实时帧格局转换

在技术挑战中咱们提到,iOS 平台上,相机 output 只能指定为 BGRA 格局,而 WebGL 规范的接口不反对该格局。如果不进行格局转换,会导致红蓝色彩颠倒,红色物体出现蓝色,蓝色物体出现红色。所以在数据缓存和传输之前,要做格局转换,咱们须要找到一个疾速低成本的办法。

要想做数据格式转换,须要理解一些根本的图像数据在内存中的布局状况,如下图所示。

这里咱们选取的 BGRA 和 RGBA 格局都是 32 位,也就是每一个像素点是 4 个字节。

实在图像数据因为内存对齐的起因,大小并不一定是 width*height* 4 个字节,CoreVideo 框架提供了获取相机数据宽高的办法,咱们要计算出待处理的字节大小,每 4 个字节做一次循环,把第一位和第三位做一个调换,就能无需 malloc 内存,把 BGRA 转换为 RGBA 格局。

3、并发调度

在技术挑战中还提到,每一帧的解决工夫大概只有 41ms,须要经验原生相机生产、数据格式转换、数据双向传输、ar 算法、webgl 绘制等这么多流程,如何利用并发劣势,并且保障实时帧的时序不会产生错乱呢?

咱们为了保障 UI 主线程的晦涩,要尽可能把更多的环节放到子线程执行,这个时候哪怕写入缓存这样一个轻量的操作放到主线程都可能会带来画面的卡顿。

实时帧的解决、AR 算法别离放在不同的线程,为了保障实时帧时序,均采纳串行队列。

采纳了多线程之后,NativeBuffer 数据的存储和清理须要加上线程平安爱护。

这样整体利用了多核的劣势,并保障了调用时序。线程调度和解决流转如下图所示:

4、资源管理

现实状况下,原生相机产生一个实时帧数据,JS 耗费一个,在中高端机器上,性能可能满足需要,整体体现较为安稳,然而在低端机器中,线程抢占十分频繁,当主线程和子线程产生线程抢占的时候,会导致供需不匹配,一旦实时帧数据耗费不及时,内存会产生爆炸式的增长,所以须要限定缓存池的容量,这个个别能够依据理论调试的状况指定一个数值即可。

还有一旦呈现内存正告或者当缓存满的时候,须要去清理缓存池,buffer 如果正在被应用,就不能去清理,否则可能会呈现白屏的景象,咱们给 buffer 加了一个是否被生产的标记,当一个 buffer 被生产后,它不能以惯例的形式清理,须要期待 js 生产实现之后清理,这个在下面也有介绍。

在页面退出的时候,引擎须要监听相干的事件,确保实时帧的监听被进行,否则会呈现多个 js 相机的监听事件并存,一个数据被屡次生产而引发异样。

结语

京东小程序致力于打造卓越的技术开放平台,咱们在晋升性能、用户体验上一直致力,咱们也在建设和欠缺小程序的各种能力,欢送大家提供贵重的倡议。

退出移动版