作者:京东零售 戴旭

京东小程序是一个敞开技能渠道,正在被越来越多的头部品牌挑选,用于站内私域流量的营销和运营。诸如各种日化、奢侈品等品牌对ARVR有较多的诉求,希望京东小程序引擎提供一些底层才能,叠加品牌自主的个性化开发和定制,以支撑更加丰厚的场景和玩法,比方AR试妆、试戴等。

咱们小程序引擎联合ARVR团队,在双方产研测的努力和协作下,完成了相关才能的规划和开发。全体功用于京东APP11.6.6版别发布上线,等待为更多的商家和品牌赋能。

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

体会路径和作用(担任相关模块的产品小姐姐友谊录屏)

技能方案

这儿以人脸识别为例,先介绍全体的技能方案。

概念介绍

技能关键词:相机、实时帧、AR算法、同层烘托、WebGL。

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

后边两个关键词和小程序的场景有联系,WebGL技能是小程序为了支撑游戏、ARVR等高功能烘托的需求,选用原生的OpenGL完成了一套WebGL的接口。小程序页面是WebView烘托,而咱们已然说到了选用OpenGL原生烘托,就需求把原生组件,正确的插入到Web的视图层级,同层烘托便是将原生组件和WebView DOM 元素放在一起进行混合烘托的技能,能够确保原生组件和 DOM 元素在烘托层级、翻滚、接触事件处理等方面保持一致。

全体流程

小程序引擎在底层原生支撑了相机、实时帧、AR、WebGL等才能,一起暴露了若干 js 的api。小程序开发者经过相关api的调用,履行开启相机、获取实时帧数据,调用AR接口,获取计算结果数据,进行WebGL烘托等操作。简要的流程如下:

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

分层规划

从分层的视点看整个技能方案的规划,大致如下:

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

其间在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格局,参阅官方文档
developer.apple.com/library/arc…

而WebGL规范的接口不支撑BGRA格局,参阅文档:
developer.mozilla.org/en-US/docs/…

应战3,即使以24帧为规范,每一帧的处理时间大约只要41ms,需求阅历原生相机出产、数据格局转化、数据双向传输、ar算法、webgl制作等流程,每一环节都很重,咱们需求考虑怎么利用并发调度优势,而且确保实时帧的时序不会发生紊乱,因为时序一旦乱了,影像虽然一向在输出,可是视觉感受是紊乱的。

针对上述应战,进行了一系列的优化,终究在中低端手机(iPhone8 Plus)上到达平均26~27帧的帧率,全体体会较为流通,详细调优下面详细介绍。

功能调优

1、数据传输优化

原生和js之间传输大量的数据会成为功能的瓶颈,数据传输优化便是削减数据传输频次,最好是数据保存一份,只传递数据的符号。

咱们规划了一个NativeBuffer缓存来优化这个问题。首要流程如下

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

可是在js环境中,终究仍是要运用js目标,原生相机实时帧的数据需求被转化为js目标。那么怎么做才能让数据只保存一份呢?

NO COPY

iOS端挑选运行小程序的js结构是JavaScriptCore,JavaScriptCore提供了一些C言语的接口办法,能够以NO COPY的办法,把一个void类型的二进制数据指针作为backing store,创立相对应的js目标,一般类型是ArrayBuffer或许TypeArray。也便是说原生和js目标背面的数据是同一份,共享这部分内存。

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

这样一来咱们只需求确保缓存的原始相机实时帧的数据不释放,那么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缓存池中查找,假如找到了则无需再做数据复制。这样确保了数据始终只要一份。

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

数据类型

在实践的过程中,js端在挑选二进制目标的数据类型的时分,可能会用ArrayBuffer或许TypeArray。一旦js端进行了数据类型转化,比方ArrayBuffer转TypeArray,引擎在调用setNativeBuffer的时分,传递的是转化后的数据类型,将会导致setNativeBuffer内部的写入优化失效,进而在低端机上带来显着的卡顿。在这儿,咱们统一运用一致的数据类型,不能随意的转化数据类型。

2、相机实时帧格局转化

在技能应战中咱们说到,iOS渠道上,相机output只能指定为BGRA格局,而WebGL规范的接口不支撑该格局。假如不进行格局转化,会导致红蓝色彩颠倒,赤色物体呈现蓝色,蓝色物体呈现赤色。所以在数据缓存和传输之前,要做格局转化,咱们需求找到一个快速低成本的办法。

要想做数据格局转化,需求了解一些根本的图像数据在内存中的布局状况,如下图所示。

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

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

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

3、并发调度

在技能应战中还说到,每一帧的处理时间大约只要41ms,需求阅历原生相机出产、数据格局转化、数据双向传输、ar算法、webgl制作等这么多流程,怎么利用并发优势,而且确保实时帧的时序不会发生紊乱呢?

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

实时帧的处理、AR算法分别放在不同的线程,为了确保实时帧时序,均选用串行行列。

选用了多线程之后,NativeBuffer数据的存储和整理需求加上线程安全保护。

这样全体利用了多核的优势,并确保了调用时序。线程调度和处理流通如下图所示:

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

4、资源管理

理想状况下,原生相机发生一个实时帧数据,JS耗费一个,在中高端机器上,功能能够满足需求,全体表现较为平稳,可是在低端机器中,线程抢占非常频频,当主线程和子线程发生线程抢占的时分,会导致供需不匹配,一旦实时帧数据耗费不及时,内存会发生爆炸式的增加,所以需求限定缓存池的容量,这个一般能够依据实际调试的状况指定一个数值即可。

还有一旦出现内存正告或许当缓存满的时分,需求去整理缓存池,buffer假如正在被运用,就不能去整理,不然可能会出现白屏的现象,咱们给buffer加了一个是否被消费的符号,当一个buffer被消费后,它不能以惯例的办法整理,需求等待js消费完成之后整理,这个在上面也有介绍。

在页面退出的时分,引擎需求监听相关的事情,确保实时帧的监听被停止,不然会出现多个js相机的监听事件并存,一个数据被屡次消费而引发异常。

结语

京东小程序致力于打造卓越的技能敞开渠道,咱们在提高功能、用户体会上不断努力,咱们也在建造和完善小程序的各种才能,欢迎我们提供宝贵的建议。

本文正在参加「金石计划」