微信小游戲直播在Android端的跨進(jìn)程渲染推流實(shí)踐

本文由微信開發(fā)團(tuán)隊(duì)工程師“virwu”分享。
1、引言
近期,微信小游戲支持了視頻號一鍵開播,將微信升級到最新版本,打開騰訊系小游戲(如跳一跳、歡樂斗地主等),在右上角菜單就可以看到發(fā)起直播的按鈕一鍵成為游戲主播了(如下圖所示)。

然而微信小游戲出于性能和安全等一系列考慮,運(yùn)行在一個獨(dú)立的進(jìn)程中,在該環(huán)境中不會初始化視頻號直播相關(guān)的模塊。這就意味著小游戲的音視頻數(shù)據(jù)必須跨進(jìn)程傳輸?shù)街鬟M(jìn)程進(jìn)行推流,給我們實(shí)現(xiàn)小游戲直播帶來了一系列挑戰(zhàn)。
(本文同步發(fā)布于:http://www.52im.net/thread-3594-1-1.html)
2、系列文章
本文是系列文章中的第5篇:
《直播系統(tǒng)聊天技術(shù)(一):百萬在線的美拍直播彈幕系統(tǒng)的實(shí)時推送技術(shù)實(shí)踐之路》
《直播系統(tǒng)聊天技術(shù)(二):阿里電商IM消息平臺,在群聊、直播場景下的技術(shù)實(shí)踐》
《直播系統(tǒng)聊天技術(shù)(三):微信直播聊天室單房間1500萬在線的消息架構(gòu)演進(jìn)之路》
《直播系統(tǒng)聊天技術(shù)(四):百度直播的海量用戶實(shí)時消息系統(tǒng)架構(gòu)演進(jìn)實(shí)踐》
《直播系統(tǒng)聊天技術(shù)(五):微信小游戲直播在Android端的跨進(jìn)程渲染推流實(shí)踐》(* 本文)
3、視頻采集推流
3.1 錄屏采集?
小游戲直播本質(zhì)上就是把主播手機(jī)屏幕上的內(nèi)容展示給觀眾,自然而然地我們可以想到采用系統(tǒng)的錄屏接口MediaProjection進(jìn)行視頻數(shù)據(jù)的采集。
這種方案有這些優(yōu)點(diǎn):
1)系統(tǒng)接口,實(shí)現(xiàn)簡單,兼容性和穩(wěn)定性有一定保證;
2)后期可以擴(kuò)展成通用的錄屏直播;
3)對游戲性能影響較小,經(jīng)測試對幀率影響在10%以內(nèi);
4)可以直接在主進(jìn)程進(jìn)行數(shù)據(jù)處理及推流,不用處理小游戲跨進(jìn)程的問題。
但是最終這個方案被否決了,主要出于以下考慮:
1)需要展示系統(tǒng)授權(quán)彈窗;
2)需要謹(jǐn)慎處理切出小游戲后暫停畫面推流的情況,否則可能錄制到主播的其他界面,有隱私風(fēng)險(xiǎn);
3)最關(guān)鍵的一點(diǎn):產(chǎn)品設(shè)計(jì)上需要在小游戲上展示一個評論掛件(如下圖),便于主播查看直播評論以及進(jìn)行互動,錄屏直播會讓觀眾也看到這個組件,影響觀看體驗(yàn)的同時會暴露一些只有主播才能看到的數(shù)據(jù)。

?

轉(zhuǎn)念一想,既然小游戲的渲染完全是由我們控制的,為了更好的直播體驗(yàn),能否將小游戲渲染的內(nèi)容跨進(jìn)程傳輸?shù)街鬟M(jìn)程來進(jìn)行推流呢?
3.2 小游戲渲染架構(gòu)
為了更好地描述我們采用的方案,這里先簡單介紹一下小游戲的渲染架構(gòu):

可以看到圖中左半邊表示在前臺的小游戲進(jìn)程,其中MagicBrush為小游戲渲染引擎,它接收來自于小游戲代碼的渲染指令調(diào)用,將畫面渲染到在屏的SurfaceView提供的Surface上。整個過程主進(jìn)程在后臺不參與。
3.3 小游戲錄屏?xí)r的情況
小游戲之前支持過游戲內(nèi)容的錄制,和直播原理上類似,都需要獲取當(dāng)前小游戲的畫面內(nèi)容。
錄屏啟用時小游戲會切換到如下的模式進(jìn)行渲染:

可以看到,MagicBrush的輸出目標(biāo)不再是在屏的SurfaceView,而是Renderer產(chǎn)生的一個SurfaceTexture。
這里先介紹一下Renderer的作用:
Renderer是一個獨(dú)立的渲染模塊,表示一個獨(dú)立的GL環(huán)境,它可以創(chuàng)建SurfaceTexture作為輸入,收到SurfaceTexture的onFrameAvailable回調(diào)后通過updateTexImage方法將圖像數(shù)據(jù)轉(zhuǎn)換為類型是GL_TEXTURE_EXTERNAL_OES的紋理參與后續(xù)的渲染過程,并可以將渲染結(jié)果輸出到另一個Surface上。
下面逐步對圖中過程進(jìn)行解釋:
1)MagicBrush接收來自小游戲代碼的渲染指令調(diào)用,將小游戲內(nèi)容渲染到第一個Renderer所創(chuàng)建的SurfaceTexture上;
2)隨后這個Renderer做了兩件事情:
2.1)將得到的小游戲畫面紋理再次渲染到了在屏的Surface上;
2.2)提供紋理ID給到第二個Renderer(這里兩個Renderer通過共享GLContext來實(shí)現(xiàn)共享紋理)。
3)第二個Renderer將第一個Renderer提供的紋理渲染到mp4編碼器提供的輸入SurfaceTexture上,最終編碼器編碼產(chǎn)生mp4錄屏文件。
3.4 改造錄屏方案?
可以看到,錄屏方案中通過一個Renderer負(fù)責(zé)將游戲內(nèi)容上屏,另一個Renderer將同樣的紋理渲染到編碼器上的方式實(shí)現(xiàn)了錄制游戲內(nèi)容,直播其實(shí)類似,是不是只要將編碼器替換為直播的推流模塊就可以了呢?
確實(shí)如此,但還缺少關(guān)鍵的一環(huán):推流模塊運(yùn)行在主進(jìn)程,我們需要實(shí)現(xiàn)跨進(jìn)程傳輸圖像數(shù)據(jù)!如何跨進(jìn)程呢?
說到跨進(jìn)程:可能我們腦海里蹦出的第一反應(yīng)就是Binder、Socket、共享內(nèi)存等等傳統(tǒng)的IPC通信方法。但仔細(xì)一想,系統(tǒng)提供的SurfaceView是非常特殊的一個View組件,它不經(jīng)過傳統(tǒng)的View樹來參與繪制,而是直接經(jīng)由系統(tǒng)的SurfaceFlinger來合成到屏幕上,而SurfaceFlinger運(yùn)行在系統(tǒng)進(jìn)程上,我們繪制在SurfaceView所提供的Surface上的內(nèi)容必然是可以跨進(jìn)程進(jìn)行傳輸?shù)?,而Surface跨進(jìn)程的方法很簡單——它本身就實(shí)現(xiàn)了Parcelable接口,這意味著我們可以用Binder直接跨進(jìn)程傳輸Surface對象。
于是我們有了下面這個初步方案:

可以看到:第3步不再是渲染到mp4編碼器上,而是渲染到主進(jìn)程跨進(jìn)程傳輸過來的Surface上,主進(jìn)程的這個Surface是通過一個Renderer創(chuàng)建的SurfaceTexture包裝而來的,現(xiàn)在小游戲進(jìn)程作為生產(chǎn)者向這個Surface渲染畫面。當(dāng)一幀渲染完畢后,主進(jìn)程的SurfaceTexture就會收到onFrameAvailable回調(diào)通知圖像數(shù)據(jù)已經(jīng)準(zhǔn)備完畢,隨之通過updateTexImage獲取到對應(yīng)的紋理數(shù)據(jù),這里由于直播推流模塊只支持GL_TEXTURE_2D類型的紋理,這里主進(jìn)程Renderer會將GL_TEXTURE_EXTERNAL_OES轉(zhuǎn)換為GL_TEXTURE_2D紋理后給到直播推流編碼器,完成推流過程。
經(jīng)過一番改造:上述方案成功地實(shí)現(xiàn)了將小游戲渲染在屏幕上的同時傳遞給主進(jìn)程進(jìn)行推流,但這真的是最優(yōu)的方案嗎?
思考一番,發(fā)現(xiàn)上述方案中的Renderer過多了,小游戲進(jìn)程中存在兩個,一個用于渲染上屏,一個用于渲染到跨進(jìn)程而來的Surface上,主進(jìn)程中還存在一個用于轉(zhuǎn)換紋理以及調(diào)用推流模塊。如果要同時支持錄屏,還需要在小游戲進(jìn)程再起一個Renderer用于渲染到mp4編碼器,過多的Renderer意味著過多的額外渲染開銷,會影響小游戲運(yùn)行性能。
3.5 跨進(jìn)程渲染方案
縱觀整個流程,其實(shí)只有主進(jìn)程的Renderer是必要的,小游戲所使用的額外Render無非就是想同時滿足渲染上屏和跨進(jìn)程傳輸,讓我們大開腦洞——既然Surface本身就不受進(jìn)程的約束,那我們干脆把小游戲進(jìn)程的在屏Surface傳遞到主進(jìn)程進(jìn)行渲染上屏吧!

最終我們大刀闊斧地砍掉了小游戲進(jìn)程的兩個冗余Renderer,MagicBrush直接渲染到了跨進(jìn)程傳遞而來的Surface上,而主進(jìn)程的Renderer在負(fù)責(zé)紋理類型轉(zhuǎn)換的同時也負(fù)責(zé)將紋理渲染到跨進(jìn)程傳遞而來的小游戲進(jìn)程的在屏Surface上,實(shí)現(xiàn)畫面的渲染上屏。
最終所需要的Renderer數(shù)量從原來的3個減少到了必要的1個,在架構(gòu)更加清晰的同時提升了性能。
后續(xù)需要同時支持錄屏?xí)r,只要稍作改動,將mp4編碼器的輸入SurfaceTexture也跨進(jìn)程傳遞到主進(jìn)程,再新增一個Renderer渲染紋理到它上面就行了(如下圖所示)。

3.6 兼容性與性能
到這里,不禁有點(diǎn)擔(dān)心,跨進(jìn)程傳輸和渲染Surface的這套方案的兼容性會不會有問題呢?
實(shí)際上,雖然并不常見,但是官方文檔里面是有說明可以跨進(jìn)程進(jìn)行繪制的:
SurfaceView combines a surface and a view. SurfaceView's view components are composited by SurfaceFlinger (and not the app), enabling rendering from a separate thread/process and isolation from app UI rendering.
并且Chrome以及Android O以后的系統(tǒng)WebView都有使用跨進(jìn)程渲染的方案。
在我們的兼容性測試中,覆蓋了Android 5.1及以后的各個主流系統(tǒng)版本和機(jī)型,除了Android 5.x機(jī)型上出現(xiàn)了跨進(jìn)程渲染黑屏的問題外,其余均可以正常渲染上屏和推流。
性能方面:我們使用了WebGL水族館的Demo進(jìn)行了性能測試,可以看到對于平均幀率的影響在15%左右,主進(jìn)程的CPU因?yàn)殇秩竞屯屏饔兴?,奇怪的是小游戲進(jìn)程的CPU開銷卻出現(xiàn)了一些下降,這里下降的原因暫時還沒有確認(rèn),懷疑與上屏操作移到主進(jìn)程相關(guān),也不排除是統(tǒng)計(jì)方法的影響。

3.7 小結(jié)一下
為了實(shí)現(xiàn)不錄制主播端的評論掛件,我們從小游戲渲染流程入手,借助于Surface跨進(jìn)程渲染和傳輸圖像的能力,把小游戲渲染上屏的過程移到了主進(jìn)程,并同時生成紋理進(jìn)行推流,在兼容性和性能上達(dá)到了要求。
4、音頻采集推流
4.1 方案選擇
在音頻采集方案中,我們注意到在Android 10及以上系統(tǒng)提供了AudioPlaybackCapture方案允許我們在一定的限制內(nèi)對系統(tǒng)音頻進(jìn)行采集。當(dāng)時預(yù)研的一些結(jié)論如下。
捕獲方 - 進(jìn)行捕獲的條件:
1)Android 10(api 29)及以上;
2)獲取了RECORD_AUDIO權(quán)限;
3)通過MediaProjectionManager.createScreenCaptureIntent()進(jìn)行MediaProjection權(quán)限的申請(和MediaProjection錄屏共用);
4)通過AudioPlaybackCaptureConfiguration.addMatchingUsage()/AudioPlaybackCaptureConfiguration.excludeUsage() 添加/排除要捕獲的MEDIA類型;
5)通過 AudioPlaybackCaptureConfiguration.addMatchingUid() /AudioPlaybackCaptureConfiguration.excludeUid()添加/排除可以捕獲的應(yīng)用的UID。
被捕獲方 - 可以被捕獲的條件:
1)Player的AudioAttributes設(shè)置的Usage為USAGE_UNKNOWN,USAGE_GAME或USAGE_MEDIA(目前絕大部分用的都是默認(rèn)值,可以被捕獲);
2)應(yīng)用的CapturePolicy被設(shè)置為AudioAttributes#ALLOW_CAPTURE_BY_ALL,有三種辦法可以設(shè)置(以最嚴(yán)格的為準(zhǔn),目前微信內(nèi)沒有配置,默認(rèn)為可以捕獲);
3)通過manifest.xml設(shè)置android:allowAudioPlaybackCapture="true",其中,TargetApi為29及以上的應(yīng)用默認(rèn)為true,否則為false;
4)api 29及以上可以通過setAllowedCapturePolicy方法運(yùn)行時設(shè)置;
5)api 29及以上可以通過AudioAttributes針對每一個Player單獨(dú)設(shè)置。
總的來說:Android 10及以上可以使用AudioPlaybackCapture方案進(jìn)行音頻捕獲,但考慮到Android 10這個系統(tǒng)版本限制過高,最終我們選擇了自己來采集并混合小游戲內(nèi)播放的所有音頻。
4.2 跨進(jìn)程音頻數(shù)據(jù)傳輸
現(xiàn)在,老問題又?jǐn)[在了我們眼前:小游戲混合后的音頻數(shù)據(jù)在小游戲進(jìn)程,而我們需要把數(shù)據(jù)傳輸?shù)街鬟M(jìn)程進(jìn)行推流。
與一般的IPC跨進(jìn)程通信用于方法調(diào)用不同:在這個場景下,我們需要頻繁地(40毫秒一次)傳輸較大的數(shù)據(jù)塊(16毫秒內(nèi)的數(shù)據(jù)量在8k左右)。
同時:由于直播的特性,這個跨進(jìn)程傳輸過程的延遲需要盡可能地低,否則就會出現(xiàn)音畫不同步的情況。
為了達(dá)到上述目標(biāo):我們對Binder、LocalSocket、MMKV、SharedMemory、Pipe這幾種IPC方案進(jìn)行了測試。在搭建的測試環(huán)境中,我們在小游戲進(jìn)程模擬真實(shí)的音頻傳輸?shù)倪^程,每隔16毫秒發(fā)送一次序列化后的數(shù)據(jù)對象,數(shù)據(jù)對象大小分為3k/4M/10M三擋,在發(fā)送前儲存時間戳在對象中;在主進(jìn)程中接收到數(shù)據(jù)并完成反序列化為數(shù)據(jù)對象的時刻作為結(jié)束時間,計(jì)算傳輸延遲。
最終得到了如下結(jié)果:

注:其中XIPCInvoker(Binder)和MMKV在傳輸較大數(shù)據(jù)量時耗時過長,不在結(jié)果中展示。
對于各個方案的分析如下(卡頓率表示延遲>2倍平均延遲且>10毫秒的數(shù)據(jù)占總數(shù)的比例):

可以看到:LocalSocket方案在各個情況下的傳輸延遲表現(xiàn)都極為優(yōu)異。差異的原因主要是因?yàn)槁愣M(jìn)制數(shù)據(jù)在跨進(jìn)程傳輸?shù)街鬟M(jìn)程后,仍需要進(jìn)行一次數(shù)據(jù)拷貝操作來反序列化為數(shù)據(jù)對象,而使用LocalSocket時可以借助于ObjectStream和Serializeable來實(shí)現(xiàn)流式的拷貝,相比與其他方案的一次性接收數(shù)據(jù)后再拷貝節(jié)約了大量的時間(當(dāng)然其他方案也可以設(shè)計(jì)成分塊流式傳輸同時拷貝,但實(shí)現(xiàn)起來有一定成本,不如ObjectStream穩(wěn)定易用)。
我們也對LocalSocket進(jìn)行了兼容性與性能測試,未出現(xiàn)不能傳輸或斷開連接的情況,僅在三星S6上平均延遲超過了10毫秒,其余機(jī)型延遲均在1毫秒左右,可以滿足我們的預(yù)期。
4.3 LocalSocket的安全性
常用的Binder的跨進(jìn)程安全性有系統(tǒng)實(shí)現(xiàn)的鑒權(quán)機(jī)制來保證,LocalSocket作為Unix domain socket的封裝,我們必須考慮它的安全性問題。
論文《The Misuse of Android Unix Domain Sockets and Security Implications》較為詳細(xì)地分析了Android中使用LocalSocket所帶來的安全風(fēng)險(xiǎn)。
PS:論文原文附件下載(請從此鏈接的4.3節(jié)處下載:http://www.52im.net/thread-3594-1-1.html)
總結(jié)論文所述:由于LocalSocket本身缺乏鑒權(quán)機(jī)制,任意一個應(yīng)用都可以進(jìn)行連接,從而截取到數(shù)據(jù)或是向接收端傳遞非法數(shù)據(jù)引發(fā)異常。
針對這個特點(diǎn),我們可以做的防御方法有兩種:
1)隨機(jī)化LocalSocket的命名,例如使用當(dāng)前直播的小游戲的appId和用戶uin等信息計(jì)算md5作為LocalSocket的名字,使得攻擊者無法通過固定或窮舉名字的方法嘗試建立連接;
2)引入鑒權(quán)機(jī)制,在連接成功后發(fā)送特定的隨機(jī)信息來驗(yàn)證對方的真實(shí)性,然后才啟動真正的數(shù)據(jù)傳輸。
4.4 小結(jié)一下
為了兼容Android 10以下的機(jī)型也能直播,我們選擇自己處理小游戲音頻的采集,并通過對比評測,選用了LocalSocket作為跨進(jìn)程音頻數(shù)據(jù)傳輸?shù)姆桨?,在延遲上滿足了直播的需求。
同時,通過一些對抗措施,可以有效規(guī)避LocalSocket的安全風(fēng)險(xiǎn)。
5、多進(jìn)程帶來的問題
回頭來看,雖然整個方案看起來比較通順,但是在實(shí)現(xiàn)的過程中還是由于多進(jìn)程的原因踩過不少坑,下面就分享其中兩個比較主要的。
5.1 glFinish造成渲染推流幀率嚴(yán)重下降
在剛實(shí)現(xiàn)跨進(jìn)程渲染推流的方案后,我們進(jìn)行了一輪性能與兼容性測試,在測試中發(fā)現(xiàn),部分中低端機(jī)型上幀率下降非常嚴(yán)重(如下圖所示)。

復(fù)現(xiàn)后查看小游戲進(jìn)程渲染的幀率(即小游戲進(jìn)程繪制到跨進(jìn)程而來的Surface上的幀率)發(fā)現(xiàn)可以達(dá)到不開直播時的幀率。
而我們所用的測試軟件PerfDog所記錄的是在屏Surface的繪制幀率,這就說明性能下降不是直播開銷過高引起的小游戲代碼執(zhí)行效率下降,而是主進(jìn)程上屏Renderer效率太低。
于是我們對主進(jìn)程直播時運(yùn)行效率進(jìn)行了Profile,發(fā)現(xiàn)耗時函數(shù)為glFinish。
并且有兩次調(diào)用:
1)第一次調(diào)用是Renderer將外部紋理轉(zhuǎn)2D紋理時,耗時會達(dá)到100多毫秒;
2)第二次調(diào)用是騰訊云直播SDK內(nèi)部,耗時10毫秒以內(nèi)。
如果將第一次調(diào)用去掉,直播SDK內(nèi)部的這次則會耗時100多毫秒。
為了弄清為什么這個GL指令耗時這么久,我們先看看它的描述:
glFinish does not return until the effects of all previously called GL commands are complete.
描述很簡單:它會阻塞直到之前調(diào)用的所有GL指令全部完成。
這么看來是之前的GL指令太多了?但是GL指令隊(duì)列是以線程為維度隔離的,在主進(jìn)程的Renderer線程中,glFinish前只會執(zhí)行紋理類型轉(zhuǎn)換的非常少量的GL指令,和騰訊云的同學(xué)了解到推流接口也不會在本線程執(zhí)行很多GL指令,如此少量的GL指令怎么會使glFinish阻塞這么久呢?等等,大量GL指令?小游戲進(jìn)程此時不就正在執(zhí)行大量GL指令嗎,難道是小游戲進(jìn)程的大量GL指令導(dǎo)致了主進(jìn)程的glFinsih耗時過長?
這樣的猜測不無道理:雖然GL指令隊(duì)列是按線程隔離的,但處理指令的GPU只有一個,一個進(jìn)程的GL指令過多導(dǎo)致另一個進(jìn)程在需要glFinish時阻塞過久。Google了一圈沒找到有相關(guān)的描述,需要自己驗(yàn)證這個猜測。
重新觀察一遍上面的測試數(shù)據(jù):發(fā)現(xiàn)直播前能達(dá)到60幀的情況下,直播后也能達(dá)到60幀左右,這是不是就說明在小游戲的GPU負(fù)載較低時glFinish的耗時也會下降呢?
在性能下降嚴(yán)重的機(jī)型上:控制其他變量不變嘗試運(yùn)行低負(fù)載的小游戲,發(fā)現(xiàn)glFinsih的耗時成功下降到了10毫秒左右,這就印證了上面的猜測——確實(shí)是小游戲進(jìn)程正在執(zhí)行的大量GL指令阻塞了主進(jìn)程glFinish的執(zhí)行。
該如何解決呢?小游戲進(jìn)程的高負(fù)載無法改變,那能讓小游戲在一幀渲染完成以后停住等主進(jìn)程的glFinish完成后再渲染下一幀嗎?
這里經(jīng)過了各種嘗試:OpenGL的glFence同步機(jī)制無法跨進(jìn)程使用;由于GL指令是異步執(zhí)行的,通過跨進(jìn)程通信加鎖鎖住小游戲的GL線程并不能保證主進(jìn)程執(zhí)行g(shù)lFinish時小游戲進(jìn)程的指令已經(jīng)執(zhí)行完,而能保證這點(diǎn)只有通過給小游戲進(jìn)程加上glFinish,但這會使得雙緩沖機(jī)制失效,導(dǎo)致小游戲渲染幀率的大幅下降。
既然glFinish所帶來的阻塞無法避免,那我們回到問題的開始:為什么需要glFinish?由于雙緩沖機(jī)制的存在,一般來說并不需要glFinish來等待之前的繪制完成,否則雙緩沖就失去了意義。兩次glFinish中,第一次紋理處理的調(diào)用可以直接去掉,第二次騰訊云SDK的調(diào)用經(jīng)過溝通,發(fā)現(xiàn)是為了解決一個歷史問題引入的,可以嘗試去掉。在騰訊云同學(xué)的幫助下,去掉glFinish后,渲染的幀率終于和小游戲輸出的幀率一致,經(jīng)過兼容性和性能測試,沒有發(fā)現(xiàn)去掉glFinish帶來的問題。
這個問題最終的解法很簡單:但分析問題原因的過程實(shí)際上做了非常多的實(shí)驗(yàn),同一個應(yīng)用中一個高GPU負(fù)載的進(jìn)程會影響到另一個進(jìn)程的glFinish耗時的這種場景確實(shí)也非常少見,能參考的資料不多。這個過程也讓我深刻體會到了glFinish使得雙緩沖機(jī)制失效所帶來的性能影響是巨大的,在使用OpenGL進(jìn)行渲染繪制時對于glFinish的使用應(yīng)當(dāng)非常謹(jǐn)慎。
5.2 后臺進(jìn)程優(yōu)先級問題
在測試過程中:我們發(fā)現(xiàn)無論以多少的幀率向直播SDK發(fā)送畫面,觀眾端看到的畫面幀率始終只有16幀左右,排除后臺原因后,發(fā)現(xiàn)是編碼器編碼的幀率不足導(dǎo)致的。經(jīng)騰訊云同學(xué)測試同進(jìn)程內(nèi)編碼的幀率是可以達(dá)到設(shè)定的30幀的,那么說明還是多進(jìn)程帶來的問題,這里編碼是一個非常重的操作,需要消耗比較多的CPU資源,所以我們首先懷疑的就是后臺進(jìn)程優(yōu)先級的問題。
為了確認(rèn)問題:
1)我們找來了已經(jīng)root的手機(jī),通過chrt命令提高編碼線程的優(yōu)先級,觀眾端幀率立馬上到了25幀;
2)另一方面,經(jīng)測試如果在小游戲進(jìn)程上顯示一個主進(jìn)程的浮窗(使主進(jìn)程具有前臺優(yōu)先級),幀率可以上到30幀。
綜上:可以確認(rèn)幀率下降就是由于后臺進(jìn)程(以及其擁有的線程)的優(yōu)先級過低導(dǎo)致的。
提高線程優(yōu)先級的做法在微信里比較常見,例如:小程序的JS線程以及小游戲的渲染線程都會在運(yùn)行時通過android.os.Process.setThreadPriority方法設(shè)置線程的優(yōu)先級。騰訊云SDK的同學(xué)很快提供了接口供我們設(shè)置線程優(yōu)先級,但當(dāng)我們真正運(yùn)行起來時,卻發(fā)現(xiàn)編碼的幀率僅從16幀提高到了18幀左右,是哪里出問題了呢?
前面提到:我們通過chrt命令設(shè)置線程優(yōu)先級是有效的,但android.os.Process.setThreadPriority這個方法設(shè)置的線程優(yōu)先級對應(yīng)的是renice這個命令設(shè)置的nice值。仔細(xì)閱讀chrt的manual后,發(fā)現(xiàn)之前測試時的理解有誤,之前直接用chrt -p [pid] [priority]的命令設(shè)置優(yōu)先級,卻沒有設(shè)置調(diào)度策略這個參數(shù),導(dǎo)致該線程的調(diào)度策略從Linux默認(rèn)的SCHED_OTHER改為了命令缺省設(shè)置的SCHED_RR,而SCHED_RR是一種“實(shí)時策略”,導(dǎo)致線程的調(diào)度優(yōu)先級變得非常高。
實(shí)際上:通過renice(也就是android.os.Process.setThreadPriority)設(shè)置的線程優(yōu)先級,對于后臺進(jìn)程所擁有線程來說沒有太大的幫助。
其實(shí)早有人解釋過這一點(diǎn):
To address this, Android also uses Linux cgroups in a simple way to create more strict foreground vs. background scheduling. The foreground/default cgroup allows thread scheduling as normal. The background cgroup however applies a limit of only some small percent of the total CPU time being available to all threads in that cgroup. Thus if that percentage is 5% and you have 10 background threads all wanting to run and one foreground thread, the 10 background threads together can only take at most 5% of the available CPU cycles from the foreground. (Of course if no foreground thread wants to run, the background threads can use all of the available CPU cycles.)
關(guān)于線程優(yōu)先級的設(shè)置,感興趣的同學(xué)可以看看另一位大佬的文章:《Android的離奇陷阱 — 設(shè)置線程優(yōu)先級導(dǎo)致的微信卡頓慘案》。
最終:為了提高編碼幀率并防止后臺主進(jìn)程被殺,我們最終還是決定直播時在主進(jìn)程創(chuàng)建一個前臺Service。
6、總結(jié)與展望
多進(jìn)程是一把雙刃劍,在給我們帶來隔離性和性能優(yōu)勢的同時也帶來了跨進(jìn)程通信這一難題,所幸借助系統(tǒng)Surface的能力和多種多樣的跨進(jìn)程方案可以較好地解決小游戲直播中所遇到的問題。
當(dāng)然:解決跨進(jìn)程問題最好的方案是避免跨進(jìn)程,我們也考慮了將視頻號直播的推流模塊運(yùn)行在小游戲進(jìn)程的方案,但出于改造成本的考慮而沒有選擇這一方案。
同時:這次對于SurfaceView跨進(jìn)程渲染的實(shí)踐也對其他業(yè)務(wù)有一定參考價值——對于一些內(nèi)存壓力較大或是安全風(fēng)險(xiǎn)較高,又需要進(jìn)行SurfaceView渲染繪制的場景,可以把邏輯放到獨(dú)立的進(jìn)程,再通過跨進(jìn)程渲染的方式繪制到主進(jìn)程的View上,在獲得獨(dú)立進(jìn)程優(yōu)勢的同時又避免了進(jìn)程間跳轉(zhuǎn)所帶來的體驗(yàn)的割裂。
附錄1:有關(guān)直播技術(shù)的文章匯總
《移動端實(shí)時音視頻直播技術(shù)詳解(一):開篇》
《移動端實(shí)時音視頻直播技術(shù)詳解(二):采集》
《移動端實(shí)時音視頻直播技術(shù)詳解(三):處理》
《移動端實(shí)時音視頻直播技術(shù)詳解(四):編碼和封裝》
《移動端實(shí)時音視頻直播技術(shù)詳解(五):推流和傳輸》
《移動端實(shí)時音視頻直播技術(shù)詳解(六):延遲優(yōu)化》
《理論聯(lián)系實(shí)際:實(shí)現(xiàn)一個簡單地基于html]5的實(shí)時視頻直播》
《實(shí)時視頻直播客戶端技術(shù)盤點(diǎn):Native、html]5、WebRTC、微信小程序》
《Android直播入門實(shí)踐:動手搭建一套簡單的直播系統(tǒng)》
《淘寶直播技術(shù)干貨:高清、低延時的實(shí)時視頻直播技術(shù)解密》
《技術(shù)干貨:實(shí)時視頻直播首屏耗時400ms內(nèi)的優(yōu)化實(shí)踐》
《新浪微博技術(shù)分享:微博實(shí)時直播答題的百萬高并發(fā)架構(gòu)實(shí)踐》
《實(shí)時音頻的混音在視頻直播中的技術(shù)原理和實(shí)踐總結(jié)》
《七牛云技術(shù)分享:使用QUIC協(xié)議實(shí)現(xiàn)實(shí)時視頻直播0卡頓!》
《近期大熱的實(shí)時直播答題系統(tǒng)的實(shí)現(xiàn)思路與技術(shù)難點(diǎn)分享》
《P2P技術(shù)如何將實(shí)時視頻直播帶寬降低75%?》
《網(wǎng)易云信實(shí)時視頻直播在TCP數(shù)據(jù)傳輸層的一些優(yōu)化思路》
《首次披露:快手是如何做到百萬觀眾同場看直播仍能秒開且不卡頓的?》
《淺談實(shí)時音視頻直播中直接影響用戶體驗(yàn)的幾項(xiàng)關(guān)鍵技術(shù)指標(biāo)》
《技術(shù)揭秘:支持百萬級粉絲互動的Facebook實(shí)時視頻直播》
《移動端實(shí)時視頻直播技術(shù)實(shí)踐:如何做到實(shí)時秒開、流暢不卡》
《實(shí)現(xiàn)延遲低于500毫秒的1080P實(shí)時音視頻直播的實(shí)踐分享》
《淺談開發(fā)實(shí)時視頻直播平臺的技術(shù)要點(diǎn)》
《直播系統(tǒng)聊天技術(shù)(一):百萬在線的美拍直播彈幕系統(tǒng)的實(shí)時推送技術(shù)實(shí)踐之路》
《直播系統(tǒng)聊天技術(shù)(二)阿里電商IM消息平臺,在群聊、直播場景下的技術(shù)實(shí)踐》
《直播系統(tǒng)聊天技術(shù)(三):微信直播聊天室單房間1500萬在線的消息架構(gòu)演進(jìn)之路》
《直播系統(tǒng)聊天技術(shù)(四):百度直播的海量用戶實(shí)時消息系統(tǒng)架構(gòu)演進(jìn)實(shí)踐》
《直播系統(tǒng)聊天技術(shù)(五):微信小游戲直播在Android端的跨進(jìn)程渲染推流實(shí)踐》
《海量實(shí)時消息的視頻直播系統(tǒng)架構(gòu)演進(jìn)之路(視頻+PPT)[附件下載]》
《YY直播在移動弱網(wǎng)環(huán)境下的深度優(yōu)化實(shí)踐分享(視頻+PPT)[附件下載]》
《從0到1:萬人在線的實(shí)時音視頻直播技術(shù)實(shí)踐分享(視頻+PPT) [附件下載]》
《在線音視頻直播室服務(wù)端架構(gòu)最佳實(shí)踐(視頻+PPT) [附件下載]》
>>?更多同類文章 ……
附錄2:微信團(tuán)隊(duì)分享的技術(shù)文章匯總
《微信朋友圈千億訪問量背后的技術(shù)挑戰(zhàn)和實(shí)踐總結(jié)》
《微信團(tuán)隊(duì)分享:微信移動端的全文檢索多音字問題解決方案》
《微信團(tuán)隊(duì)分享:iOS版微信的高性能通用key-value組件技術(shù)實(shí)踐》
《微信團(tuán)隊(duì)分享:iOS版微信是如何防止特殊字符導(dǎo)致的炸群、app崩潰的?》
《微信團(tuán)隊(duì)原創(chuàng)分享:iOS版微信的內(nèi)存監(jiān)控系統(tǒng)技術(shù)實(shí)踐》
《iOS后臺喚醒實(shí)戰(zhàn):微信收款到賬語音提醒技術(shù)總結(jié)》
《微信團(tuán)隊(duì)分享:視頻圖像的超分辨率技術(shù)原理和應(yīng)用場景》
《微信團(tuán)隊(duì)分享:微信每日億次實(shí)時音視頻聊天背后的技術(shù)解密》
《微信團(tuán)隊(duì)分享:微信Android版小視頻編碼填過的那些坑》
《微信手機(jī)端的本地?cái)?shù)據(jù)全文檢索優(yōu)化之路》
《企業(yè)微信客戶端中組織架構(gòu)數(shù)據(jù)的同步更新方案優(yōu)化實(shí)戰(zhàn)》
《微信團(tuán)隊(duì)披露:微信界面卡死超級bug“15。。。?!钡膩睚埲ッ}》
《月活8.89億的超級IM微信是如何進(jìn)行Android端兼容測試的》
《一篇文章get微信開源移動端數(shù)據(jù)庫組件WCDB的一切!》
《微信客戶端團(tuán)隊(duì)負(fù)責(zé)人技術(shù)訪談:如何著手客戶端性能監(jiān)控和優(yōu)化》
《微信后臺基于時間序的海量數(shù)據(jù)冷熱分級架構(gòu)設(shè)計(jì)實(shí)踐》
《微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信的臃腫之困與模塊化實(shí)踐之路》
《微信后臺團(tuán)隊(duì):微信后臺異步消息隊(duì)列的優(yōu)化升級實(shí)踐分享》
《微信團(tuán)隊(duì)原創(chuàng)分享:微信客戶端SQLite數(shù)據(jù)庫損壞修復(fù)實(shí)踐》
《微信Mars:微信內(nèi)部正在使用的網(wǎng)絡(luò)層封裝庫,即將開源》
《如約而至:微信自用的移動端IM網(wǎng)絡(luò)層跨平臺組件庫Mars已正式開源》
《開源libco庫:單機(jī)千萬連接、支撐微信8億用戶的后臺框架基石 [源碼下載]》
《微信新一代通信安全解決方案:基于TLS1.3的MMTLS詳解》
《微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信后臺?;顚?shí)戰(zhàn)分享(進(jìn)程?;钇?》
《微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信后臺保活實(shí)戰(zhàn)分享(網(wǎng)絡(luò)?;钇?》
《Android版微信從300KB到30MB的技術(shù)演進(jìn)(PPT講稿) [附件下載]》
《微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信從300KB到30MB的技術(shù)演進(jìn)》
《微信技術(shù)總監(jiān)談架構(gòu):微信之道——大道至簡(演講全文)》
《微信技術(shù)總監(jiān)談架構(gòu):微信之道——大道至簡(PPT講稿) [附件下載]》
《如何解讀《微信技術(shù)總監(jiān)談架構(gòu):微信之道——大道至簡》》
《微信海量用戶背后的后臺系統(tǒng)存儲架構(gòu)(視頻+PPT) [附件下載]》
《微信異步化改造實(shí)踐:8億月活、單機(jī)千萬連接背后的后臺解決方案》
《微信朋友圈海量技術(shù)之道PPT [附件下載]》
《微信對網(wǎng)絡(luò)影響的技術(shù)試驗(yàn)及分析(論文全文)》
《一份微信后臺技術(shù)架構(gòu)的總結(jié)性筆記》
《架構(gòu)之道:3個程序員成就微信朋友圈日均10億發(fā)布量[有視頻]》
《快速裂變:見證微信強(qiáng)大后臺架構(gòu)從0到1的演進(jìn)歷程(一)》
《快速裂變:見證微信強(qiáng)大后臺架構(gòu)從0到1的演進(jìn)歷程(二)》
《微信團(tuán)隊(duì)原創(chuàng)分享:Android內(nèi)存泄漏監(jiān)控和優(yōu)化技巧總結(jié)》
《全面總結(jié)iOS版微信升級iOS9遇到的各種“坑”》
《微信團(tuán)隊(duì)原創(chuàng)資源混淆工具:讓你的APK立減1M》
《微信團(tuán)隊(duì)原創(chuàng)Android資源混淆工具:AndResGuard [有源碼]》
《Android版微信安裝包“減肥”實(shí)戰(zhàn)記錄》
《iOS版微信安裝包“減肥”實(shí)戰(zhàn)記錄》
《移動端IM實(shí)踐:iOS版微信界面卡頓監(jiān)測方案》
《微信“紅包照片”背后的技術(shù)難題》
《移動端IM實(shí)踐:iOS版微信小視頻功能技術(shù)方案實(shí)錄》
《移動端IM實(shí)踐:Android版微信如何大幅提升交互性能(一)》
《移動端IM實(shí)踐:Android版微信如何大幅提升交互性能(二)》
《移動端IM實(shí)踐:實(shí)現(xiàn)Android版微信的智能心跳機(jī)制》
《移動端IM實(shí)踐:Whatsapp、Line、微信的心跳策略分析》
《移動端IM實(shí)踐:谷歌消息推送服務(wù)(GCM)研究(來自微信)》
《移動端IM實(shí)踐:iOS版微信的多設(shè)備字體適配方案探討》
《IPv6技術(shù)詳解:基本概念、應(yīng)用現(xiàn)狀、技術(shù)實(shí)踐(上篇)》
《IPv6技術(shù)詳解:基本概念、應(yīng)用現(xiàn)狀、技術(shù)實(shí)踐(下篇)》
《微信多媒體團(tuán)隊(duì)訪談:音視頻開發(fā)的學(xué)習(xí)、微信的音視頻技術(shù)和挑戰(zhàn)等》
《騰訊技術(shù)分享:微信小程序音視頻技術(shù)背后的故事》
《騰訊資深架構(gòu)師干貨總結(jié):一文讀懂大型分布式系統(tǒng)設(shè)計(jì)的方方面面》
《微信多媒體團(tuán)隊(duì)梁俊斌訪談:聊一聊我所了解的音視頻技術(shù)》
《騰訊音視頻實(shí)驗(yàn)室:使用AI黑科技實(shí)現(xiàn)超低碼率的高清實(shí)時視頻聊天》
《騰訊技術(shù)分享:微信小程序音視頻與WebRTC互通的技術(shù)思路和實(shí)踐》
《手把手教你讀取Android版微信和手Q的聊天記錄(僅作技術(shù)研究學(xué)習(xí))》
《微信技術(shù)分享:微信的海量IM聊天消息序列號生成實(shí)踐(算法原理篇)》
《微信技術(shù)分享:微信的海量IM聊天消息序列號生成實(shí)踐(容災(zāi)方案篇)》
《騰訊技術(shù)分享:GIF動圖技術(shù)詳解及手機(jī)QQ動態(tài)表情壓縮技術(shù)實(shí)踐》
《微信團(tuán)隊(duì)分享:Kotlin漸被認(rèn)可,Android版微信的技術(shù)嘗鮮之旅》
《社交軟件紅包技術(shù)解密(二):解密微信搖一搖紅包從0到1的技術(shù)演進(jìn)》
《社交軟件紅包技術(shù)解密(三):微信搖一搖紅包雨背后的技術(shù)細(xì)節(jié)》
《社交軟件紅包技術(shù)解密(四):微信紅包系統(tǒng)是如何應(yīng)對高并發(fā)的》
《社交軟件紅包技術(shù)解密(五):微信紅包系統(tǒng)是如何實(shí)現(xiàn)高可用性的》
《社交軟件紅包技術(shù)解密(六):微信紅包系統(tǒng)的存儲層架構(gòu)演進(jìn)實(shí)踐》
《社交軟件紅包技術(shù)解密(十一):解密微信紅包隨機(jī)算法(含代碼實(shí)現(xiàn))》
《微信團(tuán)隊(duì)分享:極致優(yōu)化,iOS版微信編譯速度3倍提升的實(shí)踐總結(jié)》
《IM“掃一掃”功能很好做?看看微信“掃一掃識物”的完整技術(shù)實(shí)現(xiàn)》
《微信團(tuán)隊(duì)分享:微信支付代碼重構(gòu)帶來的移動端軟件架構(gòu)上的思考》
《IM開發(fā)寶典:史上最全,微信各種功能參數(shù)和邏輯規(guī)則資料匯總》
《微信團(tuán)隊(duì)分享:微信直播聊天室單房間1500萬在線的消息架構(gòu)演進(jìn)之路》
>>?更多同類文章 ……
(本文同步發(fā)布于:http://www.52im.net/thread-3594-1-1.html)