Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 499, comments - 13, trackbacks - 0, articles - 1

          本文由微信開發(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ù)實(shí)踐之路

          直播系統(tǒng)聊天技術(shù)(二):阿里電商IM消息平臺,在群聊、直播場景下的技術(shù)實(shí)踐

          直播系統(tǒng)聊天技術(shù)(三):微信直播聊天室單房間1500萬在線的消息架構(gòu)演進(jìn)之路

          直播系統(tǒng)聊天技術(shù)(四):百度直播的海量用戶實(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í)會暴露一些只有主播才能看到的數(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)容。

          錄屏啟用時(shí)小游戲會切換到如下的模式進(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ù)模鳶urface跨進(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)了將小游戲渲染在屏幕上的同時(shí)傳遞給主進(jìn)程進(jìn)行推流,但這真的是最優(yōu)的方案嗎?

          思考一番,發(fā)現(xiàn)上述方案中的Renderer過多了,小游戲進(jìn)程中存在兩個,一個用于渲染上屏,一個用于渲染到跨進(jìn)程而來的Surface上,主進(jìn)程中還存在一個用于轉(zhuǎn)換紋理以及調(diào)用推流模塊。如果要同時(shí)支持錄屏,還需要在小游戲進(jìn)程再起一個Renderer用于渲染到mp4編碼器,過多的Renderer意味著過多的額外渲染開銷,會影響小游戲運(yùn)行性能。

          3.5 跨進(jìn)程渲染方案

          縱觀整個流程,其實(shí)只有主進(jìn)程的Renderer是必要的,小游戲所使用的額外Render無非就是想同時(shí)滿足渲染上屏和跨進(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)換的同時(shí)也負(fù)責(zé)將紋理渲染到跨進(jìn)程傳遞而來的小游戲進(jìn)程的在屏Surface上,實(shí)現(xiàn)畫面的渲染上屏。

          最終所需要的Renderer數(shù)量從原來的3個減少到了必要的1個,在架構(gòu)更加清晰的同時(shí)提升了性能。

          后續(xù)需要同時(shí)支持錄屏?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)殇秩竞屯屏饔兴撸婀值氖切∮螒蜻M(jìn)程的CPU開銷卻出現(xiàn)了一些下降,這里下降的原因暫時(shí)還沒有確認(rèn),懷疑與上屏操作移到主進(jìn)程相關(guān),也不排除是統(tǒng)計(jì)方法的影響。

          3.7 小結(jié)一下

          為了實(shí)現(xiàn)不錄制主播端的評論掛件,我們從小游戲渲染流程入手,借助于Surface跨進(jìn)程渲染和傳輸圖像的能力,把小游戲渲染上屏的過程移到了主進(jìn)程,并同時(shí)生成紋理進(jìn)行推流,在兼容性和性能上達(dá)到了要求。

          4、音頻采集推流

          4.1 方案選擇

          在音頻采集方案中,我們注意到在Android 10及以上系統(tǒng)提供了AudioPlaybackCapture方案允許我們在一定的限制內(nèi)對系統(tǒng)音頻進(jìn)行采集。當(dāng)時(shí)預(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í)設(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左右)。

          同時(shí):由于直播的特性,這個跨進(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ā)送前儲存時(shí)間戳在對象中;在主進(jìn)程中接收到數(shù)據(jù)并完成反序列化為數(shù)據(jù)對象的時(shí)刻作為結(jié)束時(shí)間,計(jì)算傳輸延遲。

          最終得到了如下結(jié)果:

          注:其中XIPCInvoker(Binder)和MMKV在傳輸較大數(shù)據(jù)量時(shí)耗時(shí)過長,不在結(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時(shí)可以借助于ObjectStream和Serializeable來實(shí)現(xiàn)流式的拷貝,相比與其他方案的一次性接收數(shù)據(jù)后再拷貝節(jié)約了大量的時(shí)間(當(dāng)然其他方案也可以設(shè)計(jì)成分塊流式傳輸同時(shí)拷貝,但實(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ù)姆桨福谘舆t上滿足了直播的需求。

          同時(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á)到不開直播時(shí)的幀率。

          而我們所用的測試軟件PerfDog所記錄的是在屏Surface的繪制幀率,這就說明性能下降不是直播開銷過高引起的小游戲代碼執(zhí)行效率下降,而是主進(jìn)程上屏Renderer效率太低。

          于是我們對主進(jìn)程直播時(shí)運(yùn)行效率進(jìn)行了Profile,發(fā)現(xiàn)耗時(shí)函數(shù)為glFinish。

          并且有兩次調(diào)用:

          • 1)第一次調(diào)用是Renderer將外部紋理轉(zhuǎn)2D紋理時(shí),耗時(shí)會達(dá)到100多毫秒;
          • 2)第二次調(diào)用是騰訊云直播SDK內(nèi)部,耗時(shí)10毫秒以內(nèi)。

          如果將第一次調(diào)用去掉,直播SDK內(nèi)部的這次則會耗時(shí)100多毫秒。

          為了弄清為什么這個GL指令耗時(shí)這么久,我們先看看它的描述:

          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)程此時(shí)不就正在執(zhí)行大量GL指令嗎,難道是小游戲進(jìn)程的大量GL指令導(dǎo)致了主進(jìn)程的glFinsih耗時(shí)過長?

          這樣的猜測不無道理:雖然GL指令隊(duì)列是按線程隔離的,但處理指令的GPU只有一個,一個進(jìn)程的GL指令過多導(dǎo)致另一個進(jìn)程在需要glFinish時(shí)阻塞過久。Google了一圈沒找到有相關(guān)的描述,需要自己驗(yàn)證這個猜測。

          重新觀察一遍上面的測試數(shù)據(jù):發(fā)現(xiàn)直播前能達(dá)到60幀的情況下,直播后也能達(dá)到60幀左右,這是不是就說明在小游戲的GPU負(fù)載較低時(shí)glFinish的耗時(shí)也會下降呢?

          在性能下降嚴(yán)重的機(jī)型上:控制其他變量不變嘗試運(yùn)行低負(fù)載的小游戲,發(fā)現(xiàn)glFinsih的耗時(shí)成功下降到了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時(shí)小游戲進(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í)的這種場景確實(shí)也非常少見,能參考的資料不多。這個過程也讓我深刻體會到了glFinish使得雙緩沖機(jī)制失效所帶來的性能影響是巨大的,在使用OpenGL進(jìn)行渲染繪制時(shí)對于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)行時(shí)通過android.os.Process.setThreadPriority方法設(shè)置線程的優(yōu)先級。騰訊云SDK的同學(xué)很快提供了接口供我們設(shè)置線程優(yōu)先級,但當(dāng)我們真正運(yùn)行起來時(shí),卻發(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)之前測試時(shí)的理解有誤,之前直接用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í)時(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)程被殺,我們最終還是決定直播時(shí)在主進(jìn)程創(chuàng)建一個前臺Service。

          6、總結(jié)與展望

          多進(jìn)程是一把雙刃劍,在給我們帶來隔離性和性能優(yōu)勢的同時(shí)也帶來了跨進(jìn)程通信這一難題,所幸借助系統(tǒng)Surface的能力和多種多樣的跨進(jìn)程方案可以較好地解決小游戲直播中所遇到的問題。

          當(dāng)然:解決跨進(jìn)程問題最好的方案是避免跨進(jìn)程,我們也考慮了將視頻號直播的推流模塊運(yùn)行在小游戲進(jìn)程的方案,但出于改造成本的考慮而沒有選擇這一方案。

          同時(shí):這次對于SurfaceView跨進(jìn)程渲染的實(shí)踐也對其他業(yè)務(wù)有一定參考價(jià)值——對于一些內(nèi)存壓力較大或是安全風(fēng)險(xiǎn)較高,又需要進(jìn)行SurfaceView渲染繪制的場景,可以把邏輯放到獨(dú)立的進(jìn)程,再通過跨進(jìn)程渲染的方式繪制到主進(jìn)程的View上,在獲得獨(dú)立進(jìn)程優(yōu)勢的同時(shí)又避免了進(jìn)程間跳轉(zhuǎn)所帶來的體驗(yàn)的割裂。

          附錄1:有關(guān)直播技術(shù)的文章匯總

          移動端實(shí)時(shí)音視頻直播技術(shù)詳解(一):開篇

          移動端實(shí)時(shí)音視頻直播技術(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í)時(shí)視頻直播客戶端技術(shù)盤點(diǎn):Native、html]5、WebRTC、微信小程序

          Android直播入門實(shí)踐:動手搭建一套簡單的直播系統(tǒng)

          淘寶直播技術(shù)干貨:高清、低延時(shí)的實(shí)時(shí)視頻直播技術(shù)解密

          技術(shù)干貨:實(shí)時(shí)視頻直播首屏耗時(shí)400ms內(nèi)的優(yōu)化實(shí)踐

          新浪微博技術(shù)分享:微博實(shí)時(shí)直播答題的百萬高并發(fā)架構(gòu)實(shí)踐

          實(shí)時(shí)音頻的混音在視頻直播中的技術(shù)原理和實(shí)踐總結(jié)

          七牛云技術(shù)分享:使用QUIC協(xié)議實(shí)現(xiàn)實(shí)時(shí)視頻直播0卡頓!

          近期大熱的實(shí)時(shí)直播答題系統(tǒng)的實(shí)現(xiàn)思路與技術(shù)難點(diǎn)分享

          P2P技術(shù)如何將實(shí)時(shí)視頻直播帶寬降低75%?

          網(wǎng)易云信實(shí)時(shí)視頻直播在TCP數(shù)據(jù)傳輸層的一些優(yōu)化思路

          首次披露:快手是如何做到百萬觀眾同場看直播仍能秒開且不卡頓的?

          淺談實(shí)時(shí)音視頻直播中直接影響用戶體驗(yàn)的幾項(xiàng)關(guān)鍵技術(shù)指標(biāo)

          技術(shù)揭秘:支持百萬級粉絲互動的Facebook實(shí)時(shí)視頻直播

          移動端實(shí)時(shí)視頻直播技術(shù)實(shí)踐:如何做到實(shí)時(shí)秒開、流暢不卡

          實(shí)現(xiàn)延遲低于500毫秒的1080P實(shí)時(shí)音視頻直播的實(shí)踐分享

          淺談開發(fā)實(shí)時(shí)視頻直播平臺的技術(shù)要點(diǎn)

          直播系統(tǒng)聊天技術(shù)(一):百萬在線的美拍直播彈幕系統(tǒng)的實(shí)時(shí)推送技術(shù)實(shí)踐之路

          直播系統(tǒng)聊天技術(shù)(二)阿里電商IM消息平臺,在群聊、直播場景下的技術(shù)實(shí)踐

          直播系統(tǒng)聊天技術(shù)(三):微信直播聊天室單房間1500萬在線的消息架構(gòu)演進(jìn)之路

          直播系統(tǒng)聊天技術(shù)(四):百度直播的海量用戶實(shí)時(shí)消息系統(tǒng)架構(gòu)演進(jìn)實(shí)踐

          直播系統(tǒng)聊天技術(shù)(五):微信小游戲直播在Android端的跨進(jìn)程渲染推流實(shí)踐

          海量實(shí)時(shí)消息的視頻直播系統(tǒng)架構(gòu)演進(jìn)之路(視頻+PPT)[附件下載]

          YY直播在移動弱網(wǎng)環(huán)境下的深度優(yōu)化實(shí)踐分享(視頻+PPT)[附件下載]

          從0到1:萬人在線的實(shí)時(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í)音視頻聊天背后的技術(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í)間序的海量數(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í)視頻聊天

          騰訊技術(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



          作者:Jack Jiang (點(diǎn)擊作者姓名進(jìn)入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時(shí)通訊開發(fā)交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時(shí)是【原創(chuàng)Java Swing外觀工程BeautyEye】【輕量級移動端即時(shí)通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處(也可前往 我的52im.net 找到我)。


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 怀来县| 哈密市| 北川| 龙井市| 安丘市| 大余县| 赤水市| 绵阳市| 永新县| 安乡县| 句容市| 麟游县| 车致| 双江| 昌黎县| 潼关县| 连州市| 泸溪县| 沅陵县| 舟山市| 淮北市| 大名县| 高陵县| 广水市| 阿尔山市| 房山区| 郧西县| 奈曼旗| 平舆县| 区。| 宁化县| 萨迦县| 武邑县| 通江县| 仙居县| 邵武市| 武义县| 林甸县| 沂南县| 淮北市| 泗水县|