作者:京東零售 戴旭
京東小程序是一個(gè)開放技術(shù)平臺(tái),正在被越來越多的頭部品牌選擇,用于站內(nèi)私域流量的營銷和運(yùn)營。諸如各種日化、奢侈品等品牌對(duì)ARVR有較多的訴求,希望京東小程序引擎提供一些底層能力,疊加品牌自主的個(gè)性化開發(fā)和定制,以支持更加豐富的場(chǎng)景和玩法,比如AR試妝、試戴等。
我們小程序引擎聯(lián)合ARVR團(tuán)隊(duì),在雙方產(chǎn)研測(cè)的努力和協(xié)作下,完成了相關(guān)能力的設(shè)計(jì)和開發(fā)。整體功能于京東APP11.6.6版本發(fā)布上線,期待為更多的商家和品牌賦能。
體驗(yàn)路徑和效果(負(fù)責(zé)相關(guān)模塊的產(chǎn)品小姐姐友情錄屏)
技術(shù)方案
這里以人臉識(shí)別為例,先介紹整體的技術(shù)方案。
概念介紹
技術(shù)關(guān)鍵詞:相機(jī)、實(shí)時(shí)幀、AR算法、同層渲染、WebGL。
這幾個(gè)關(guān)鍵詞里面,前三個(gè)比較好理解,人臉識(shí)別,會(huì)用相機(jī)采集人臉的實(shí)時(shí)幀數(shù)據(jù),調(diào)用AR算法,獲取計(jì)算結(jié)果,把數(shù)據(jù)傳輸給小程序前端。
后面兩個(gè)關(guān)鍵詞和小程序的場(chǎng)景有關(guān)系,WebGL技術(shù)是小程序?yàn)榱酥С钟螒颉RVR等高性能渲染的需求,采用原生的OpenGL實(shí)現(xiàn)了一套WebGL的接口。小程序頁面是WebView渲染,而我們既然提到了采用OpenGL原生渲染,就需要把原生組件,正確的插入到Web的視圖層級(jí),同層渲染就是將原生組件和WebView DOM 元素放在一起進(jìn)行混合渲染的技術(shù),能夠保證原生組件和 DOM 元素在渲染層級(jí)、滾動(dòng)、觸摸事件處理等方面保持一致。
總體流程
小程序引擎在底層原生支持了相機(jī)、實(shí)時(shí)幀、AR、WebGL等能力,同時(shí)暴露了若干 js 的api。小程序開發(fā)者通過相關(guān)api的調(diào)用,執(zhí)行開啟相機(jī)、獲取實(shí)時(shí)幀數(shù)據(jù),調(diào)用AR接口,獲取計(jì)算結(jié)果數(shù)據(jù),進(jìn)行WebGL渲染等操作。簡(jiǎn)要的流程如下:
分層設(shè)計(jì)
從分層的角度看整個(gè)技術(shù)方案的設(shè)計(jì),大致如下:
其中在AR引擎這一層,分為內(nèi)置和外部AR引擎,也是由于小程序本身是開放的技術(shù)平臺(tái),我們采用了接口協(xié)議化的設(shè)計(jì),支持第三方宿主采用自主的AR引擎,同時(shí)提供了相機(jī)、實(shí)時(shí)幀、WebGL等原子化能力,小程序服務(wù)商可以構(gòu)建專有的AR引擎為上層業(yè)務(wù)賦能。
技術(shù)挑戰(zhàn)
WebGL技術(shù)原理的篇幅過大,它也不僅僅是為了ARVR這個(gè)場(chǎng)景服務(wù),所以包括AR算法之內(nèi),都不在本篇的詳細(xì)介紹范圍之內(nèi)。
在這部分,我們專注于小程序和ARVR疊加的領(lǐng)域:內(nèi)存和幀率的優(yōu)化。
我們知道在欣賞電視和電影畫面時(shí),只要畫面刷新率達(dá)到24幀/秒,就能滿足人們的需求,也就是說我們至少要在中端甚至中低端的機(jī)器上達(dá)到24幀以上的幀率。
為了保證基本的畫質(zhì),相機(jī)實(shí)時(shí)幀的分辨率設(shè)置為1280*720,以RBGA格式存儲(chǔ),那么每一幀的數(shù)據(jù)是1280*720*4=3686400Byte,約3.5MB,每秒24幀以上的幀率,這個(gè)是不小的數(shù)據(jù)量。總的來說,在性能優(yōu)化上,我們遇到的主要挑戰(zhàn)如下:
挑戰(zhàn)1,數(shù)據(jù)從原生傳輸?shù)絡(luò)s,在從js傳遞到原生,如此大的數(shù)據(jù)量將會(huì)成為js和原生通信的瓶頸;
挑戰(zhàn)2,在iOS平臺(tái)上,相機(jī)output只能指定BGRA格式,因?yàn)樵枷鄼C(jī)實(shí)時(shí)幀 CMSampleBufferRef對(duì)象內(nèi)包含CVPixelBuffer對(duì)象,CoreVideo對(duì)象不支持RGBA格式,參考官方文檔
https://developer.apple.com/library/archive/qa/qa1501/_index.html
而WebGL標(biāo)準(zhǔn)的接口不支持BGRA格式,參考文檔:
https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D,數(shù)據(jù)格式的轉(zhuǎn)換會(huì)加重性能的負(fù)擔(dān);
挑戰(zhàn)3,即便以24幀為標(biāo)準(zhǔn),每一幀的處理時(shí)間大約只有41ms,需要經(jīng)歷原生相機(jī)生產(chǎn)、數(shù)據(jù)格式轉(zhuǎn)換、數(shù)據(jù)雙向傳輸、ar算法、webgl繪制等流程,每一環(huán)節(jié)都很重,我們需要考慮如何利用并發(fā)調(diào)度優(yōu)勢(shì),并且保證實(shí)時(shí)幀的時(shí)序不會(huì)發(fā)生錯(cuò)亂,因?yàn)闀r(shí)序一旦亂了,影像雖然一直在輸出,但是視覺感受是混亂的。
針對(duì)上述挑戰(zhàn),進(jìn)行了一系列的優(yōu)化,最終在中低端手機(jī)(iPhone8 Plus)上達(dá)到平均26~27幀的幀率,整體體驗(yàn)較為流暢,具體調(diào)優(yōu)下面詳細(xì)介紹。
性能調(diào)優(yōu)
1、數(shù)據(jù)傳輸優(yōu)化
原生和js之間傳輸大量的數(shù)據(jù)會(huì)成為性能的瓶頸,數(shù)據(jù)傳輸優(yōu)化就是減少數(shù)據(jù)傳輸頻次,最好是數(shù)據(jù)保留一份,只傳遞數(shù)據(jù)的標(biāo)記。
我們?cè)O(shè)計(jì)了一個(gè)NativeBuffer緩存來優(yōu)化這個(gè)問題。主要流程如下
但是在js環(huán)境中,最終還是要使用js對(duì)象,原生相機(jī)實(shí)時(shí)幀的數(shù)據(jù)需要被轉(zhuǎn)換為js對(duì)象。那么如何做才能讓數(shù)據(jù)只保留一份呢?
NO COPY
iOS端選擇運(yùn)行小程序的js框架是JavaScriptCore,JavaScriptCore提供了一些C語言的接口方法,可以以NO COPY的方式,把一個(gè)void類型的二進(jìn)制數(shù)據(jù)指針作為backing store,創(chuàng)建相對(duì)應(yīng)的js對(duì)象,一般類型是ArrayBuffer或者TypeArray。也就是說原生和js對(duì)象背后的數(shù)據(jù)是同一份,共享這部分內(nèi)存。
這樣一來我們只需要保證緩存的原始相機(jī)實(shí)時(shí)幀的數(shù)據(jù)不釋放,那么js對(duì)象引用的這部分?jǐn)?shù)據(jù)就會(huì)一直有效。那這部分?jǐn)?shù)據(jù)要在什么時(shí)候去清理呢?
銷毀
在創(chuàng)建js對(duì)象的時(shí)候,可以指定一個(gè)C的函數(shù)指針作為入?yún)?。?dāng)JavaScriptCore檢測(cè)到這個(gè)js對(duì)象銷毀的時(shí)候,會(huì)自動(dòng)觸發(fā)該C函數(shù)的調(diào)用。我們需要按照指定的函數(shù)原型實(shí)現(xiàn)一個(gè)C的方法,在這個(gè)函數(shù)里去做緩存的清理,可以看一下這個(gè)函數(shù)的原型:
typedef void (*JSTypedArrayBytesDeallocator)(void* bytes, void* deallocatorContext);
該函數(shù)有2個(gè)參數(shù),第一個(gè)bytes是原始相機(jī)實(shí)時(shí)幀的二進(jìn)制數(shù)據(jù),第二個(gè)是上下文環(huán)境,這里我們傳的是NativeBuffer管理類的實(shí)例,在這個(gè)函數(shù)的具體實(shí)現(xiàn)中,我們?nèi)テヅ銷ativeBuffer管理的緩存地址,找到相關(guān)數(shù)據(jù)進(jìn)行清理。
寫入優(yōu)化
前面我們說過,數(shù)據(jù)流轉(zhuǎn)是雙向的。原生把相機(jī)的數(shù)據(jù)傳輸?shù)絡(luò)s側(cè),js調(diào)用ARVR的人臉檢測(cè)接口,還需要把這份數(shù)據(jù)在傳輸?shù)皆?。因?yàn)橄鄼C(jī)和人臉檢測(cè)是相互獨(dú)立的接口,js拿到相機(jī)數(shù)據(jù)不一定非要調(diào)用人臉檢測(cè),調(diào)用人臉檢測(cè)的數(shù)據(jù)也不一定非要來自于相機(jī),還可以是一個(gè)本地的圖片。
相對(duì)應(yīng)的,我們?cè)贜ativeBuffer的設(shè)計(jì)中,提供數(shù)據(jù)雙向傳遞的接口,getNativeBuffer:id和setNativeBuffer:id。在原生傳遞到j(luò)s的數(shù)據(jù)中,我們用了NO Copy的方式去做優(yōu)化,那么在js傳遞到原生的數(shù)據(jù),由于我們不知道數(shù)據(jù)來源,所以需要開辟一份新的內(nèi)存空間,調(diào)用memcpy復(fù)制數(shù)據(jù)。但是實(shí)際上,我們?cè)谧鰯?shù)據(jù)復(fù)制之前,可以用JavaScriptCore提供的接口,從js的ArrayBuffer對(duì)象中提取到真實(shí)數(shù)據(jù)的內(nèi)存地址,然后在NativeBuffer緩存池中查找,如果找到了則無需再做數(shù)據(jù)復(fù)制。這樣保證了數(shù)據(jù)始終只有一份。
數(shù)據(jù)類型
在實(shí)踐的過程中,js端在選擇二進(jìn)制對(duì)象的數(shù)據(jù)類型的時(shí)候,可能會(huì)用ArrayBuffer或者TypeArray。一旦js端進(jìn)行了數(shù)據(jù)類型轉(zhuǎn)換,比如ArrayBuffer轉(zhuǎn)TypeArray,引擎在調(diào)用setNativeBuffer的時(shí)候,傳遞的是轉(zhuǎn)換后的數(shù)據(jù)類型,將會(huì)導(dǎo)致setNativeBuffer內(nèi)部的寫入優(yōu)化失效,進(jìn)而在低端機(jī)上帶來明顯的卡頓。在這里,我們統(tǒng)一使用一致的數(shù)據(jù)類型,不能隨意的轉(zhuǎn)換數(shù)據(jù)類型。
2、相機(jī)實(shí)時(shí)幀格式轉(zhuǎn)換
在技術(shù)挑戰(zhàn)中我們提到,iOS平臺(tái)上,相機(jī)output只能指定為BGRA格式,而WebGL標(biāo)準(zhǔn)的接口不支持該格式。如果不進(jìn)行格式轉(zhuǎn)換,會(huì)導(dǎo)致紅藍(lán)顏色顛倒,紅色物體呈現(xiàn)藍(lán)色,藍(lán)色物體呈現(xiàn)紅色。所以在數(shù)據(jù)緩存和傳輸之前,要做格式轉(zhuǎn)換,我們需要找到一個(gè)快速低成本的方法。
要想做數(shù)據(jù)格式轉(zhuǎn)換,需要了解一些基本的圖像數(shù)據(jù)在內(nèi)存中的布局情況,如下圖所示。
這里我們選取的BGRA和RGBA格式都是32位,也就是每一個(gè)像素點(diǎn)是4個(gè)字節(jié)。
真實(shí)圖像數(shù)據(jù)由于內(nèi)存對(duì)齊的原因,大小并不一定是width*height*4個(gè)字節(jié),CoreVideo框架提供了獲取相機(jī)數(shù)據(jù)寬高的方法,我們要計(jì)算出待處理的字節(jié)大小,每4個(gè)字節(jié)做一次循環(huán),把第一位和第三位做一個(gè)調(diào)換,就能無需malloc內(nèi)存,把BGRA轉(zhuǎn)換為RGBA格式。
3、并發(fā)調(diào)度
在技術(shù)挑戰(zhàn)中還提到,每一幀的處理時(shí)間大約只有41ms,需要經(jīng)歷原生相機(jī)生產(chǎn)、數(shù)據(jù)格式轉(zhuǎn)換、數(shù)據(jù)雙向傳輸、ar算法、webgl繪制等這么多流程,如何利用并發(fā)優(yōu)勢(shì),并且保證實(shí)時(shí)幀的時(shí)序不會(huì)發(fā)生錯(cuò)亂呢?
我們?yōu)榱吮WCUI主線程的流暢,要盡可能把更多的環(huán)節(jié)放到子線程執(zhí)行,這個(gè)時(shí)候哪怕寫入緩存這樣一個(gè)輕量的操作放到主線程都可能會(huì)帶來畫面的卡頓。
實(shí)時(shí)幀的處理、AR算法分別放在不同的線程,為了保證實(shí)時(shí)幀時(shí)序,均采用串行隊(duì)列。
采用了多線程之后,NativeBuffer數(shù)據(jù)的存儲(chǔ)和清理需要加上線程安全保護(hù)。
這樣整體利用了多核的優(yōu)勢(shì),并保證了調(diào)用時(shí)序。線程調(diào)度和處理流轉(zhuǎn)如下圖所示:
4、資源管理
理想情況下,原生相機(jī)產(chǎn)生一個(gè)實(shí)時(shí)幀數(shù)據(jù),JS消耗一個(gè),在中高端機(jī)器上,性能能夠滿足需求,整體表現(xiàn)較為平穩(wěn),但是在低端機(jī)器中,線程搶占非常頻繁,當(dāng)主線程和子線程發(fā)生線程搶占的時(shí)候,會(huì)導(dǎo)致供需不匹配,一旦實(shí)時(shí)幀數(shù)據(jù)消耗不及時(shí),內(nèi)存會(huì)產(chǎn)生爆炸式的增長,所以需要限定緩存池的容量,這個(gè)一般可以根據(jù)實(shí)際調(diào)試的情況指定一個(gè)數(shù)值即可。
還有一旦出現(xiàn)內(nèi)存警告或者當(dāng)緩存滿的時(shí)候,需要去清理緩存池,buffer如果正在被使用,就不能去清理,否則可能會(huì)出現(xiàn)白屏的現(xiàn)象,我們給buffer加了一個(gè)是否被消費(fèi)的標(biāo)記,當(dāng)一個(gè)buffer被消費(fèi)后,它不能以常規(guī)的方式清理,需要等待js消費(fèi)完成之后清理,這個(gè)在上面也有介紹。
在頁面退出的時(shí)候,引擎需要監(jiān)聽相關(guān)的事情,確保實(shí)時(shí)幀的監(jiān)聽被停止,否則會(huì)出現(xiàn)多個(gè)js相機(jī)的監(jiān)聽事件并存,一個(gè)數(shù)據(jù)被多次消費(fèi)而引發(fā)異常。文章來源:http://www.zghlxwxcb.cn/news/detail-422272.html
結(jié)語
京東小程序致力于打造卓越的技術(shù)開放平臺(tái),我們?cè)谔嵘阅?、用戶體驗(yàn)上不斷努力,我們也在建設(shè)和完善小程序的各種能力,歡迎大家提供寶貴的建議。文章來源地址http://www.zghlxwxcb.cn/news/detail-422272.html
到了這里,關(guān)于京東小程序接入ARVR的技術(shù)方案和性能調(diào)優(yōu)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!