隨著基于WebRTC技術的Web應用快速成長,記錄web在線教育、視頻會議等場景的互動內容并對其準確還原越來越成為一項迫切需求,在主流瀏覽器中,通常基礎設施部分已實作了頁面渲染結果的采集及編碼,開發者可以利用瀏覽器提供的API對頁面內容進行錄制,但受限于Web標準以及瀏覽器廠商在專利授權方面的問題,使用Web API實作頁面錄制在易用性和可用性上均較難令人滿意,針對上述問題,聲網Agora Web 引擎高級架構師高純在 RTE 2020 實時互聯網大會上就Web引擎渲染采集原理進行了分享,并就基于Web引擎的服務端錄制技術進行探討,
??點擊「閱讀原文」可觀看視頻回放,獲取 PPT
以下為演講實錄:
大家好,我這次技術分享的主題是Web互動場景還原—基于Web引擎技術的Web內容錄制,
我叫高純,是來自聲網的Web引擎高級架構師,接下來我將會為大家介紹以下的內容:包括應用背景、瀏覽器內容采集、服務端Web錄制引擎,服務端Web錄制引擎性能優化,最后會進行一個總結,
先看一下應用的背景,我們知道最近幾年隨著RTC行業的火熱,基于Web RTC技術的Web應用快速成長,記錄Web在線教學、視頻會議等場景的互動內容并對其進行準確的還原越來越成為一項迫切的需求,
在主流的瀏覽器當中,其基礎設施部分已經實作了對頁面渲染結果的采集及編碼程序,開發者可以利用瀏覽器提供的Web API對頁面內容進行采集以及錄制,但是受限于Web標準以及瀏覽器廠商在專利授權方面的問題,要使用Web API實作頁面錄制,在易用性和可用性方面還難以滿足業務需求,
瀏覽器內容采集
我們先來看一下瀏覽器內容采集,所謂Web錄制實際上就是對頁面內容采集及編碼并存盤的程序,在主流的瀏覽器當中它的作業基本上都是在一個多行程的架構之上,頁面的渲染會在一個或者多個的Render Process里面處理,最終的結果會在Browser Process里面來進行呈現,我們要做的事情就是在Renderer Process里面渲染的內容包括視頻、音頻及其他影片來進行采集并且錄制成檔案,這個程序就是頁面內容采集,
目前在chrome瀏覽器上面我們可以使用兩種方法來進行頁面的采集,一種方法是通過chrome extension,目前chrome extension API提供了一條叫做chrome.tabcapture的物件,這個物件當中的capture API可以用來進行頁面內容的采集,結合HTML5標準當中的其他模塊,比如說像LocalMediaStream、MediaRecorder以及Blob這些組件,我們可以對頁面內容進行采集及錄制,
它的大概流程如這個示例代碼所展示的一樣,在使用chrome.tabcapture API的時候,我們需要給它傳入一個是否允許video的采集,以及是否允許audio的采集,并且還要傳入一些video相關的解析度、采集幀率等資訊,然后這條API在執行的時候會通過一個回呼,把它采集到的結果以LocalMediaStream 的形式回傳,我們在回呼函式中拿到LocalMediaStream的物件之后,可以利用這個Stream物件來創建一個Media Recorder,同時給這個Media Recorder來指定我們視頻編碼的碼率、音頻編碼的碼率,以及我們視頻編碼的格式,
目前在chrome上面,它所支持的視頻編碼格式主要有H264、VP8、VP9,音頻它只支持Opus格式,在檔案格式方面他目前只支持WebM的媒體格式,通過呼叫MediaRecorder.start()方法,我們可以發起錄制程序,在MediaRecorder的ondataavailable回呼當中,他會把采集到的編碼處理之后的視頻資料回傳,
我們拿到這個回傳的資料之后可以把它交給一個blob來進行存盤,在錄制結束之后,我們可以利用這個blob來創造一個超鏈接,把這個檔案下載下來,這就是用chrome extension來進行采集的一個程序,
這個程序的主要問題有什么呢,主要有以下幾點,一個是他的音頻編碼僅支持opus格式,opus實際上支持的媒體格式是比較有限的,像在TS這種流媒體檔案中它是不被支持,視頻編碼方面他提供的可選引數非常有限,視頻編碼的性能相對比較弱,另外由于他僅支持webm格式的錄制,且沒有往這個檔案當中寫入進度資訊,錄制出來的檔案在播放的時候是沒有辦法去拖動的,另外由于是在錄制完畢的時候才會生成整個檔案,它的可用性是比較差的,一旦在錄制程序中出現了故障,服務出現了崩潰,這意味著我們之前所錄制的所有內容都會丟失,
我們來看另外一種頁面內容的采集方式,是利用chrome devtools API來進行采集,chrome devtools API它是第三方應用來驅動chrome作業的一個API,它支持chrome以headless模式來作業,第三方的應用和chrome之間進行通信是使用chrome devtools protocol協議,但是它只能采集chrome可視的資料,音頻是沒有辦法采集的,它目前提供了三條API來做頁面內容的采集,包括StartScreenCast()、StopScreenCast()、以及CaptureScreenstot(),Capture Screenstot可對頁面進行截圖,只能采集單幀的資料存盤到image檔案,我們主要使用的是StartScreenCast、StopScreenCast這兩條API,它的方法大概是以下幾個步驟:
首先我們要啟動chrome,啟動chrome的時候需要給它加上Headlees引數,令chrome以Headless模式啟動,所謂Headless模式就是以一個服務行程在后臺運行,他是沒有用戶界面的,在啟動它的時候,我們需要給它帶上remote debugging port除錯埠的引數,同時要指定它渲染結果的視窗大小,
在啟動Headless chrome之后我們可以利用一個node應用,在應用中使用google提供的叫做chrome remote interface的插件,利用這個插件我們就可以和Headless chrome進行通信來獲取它采集到的資料,
具體的呼叫程序,首先會創建一個chrome remote interface的物件,并且拿到這個物件當中的Page物件,我們通過呼叫Page當中的startScreenCast的方法,來給它傳入相應的編碼格式,然后能夠獲取到它的每一單幀的資料,請求每一幀的資料方法叫做ScreenCastFrame,通過這個示例代碼可以看到,瀏覽器在編碼的時候,它采用的是影像的編碼方式,目前chrome支持兩種方式,一種是png一種是jpeg,我們拿到了這個png/jpeg的資料之后需要對這個影像進行解碼,利用我們寫的插件來對它進行視頻的重新編碼,然后保存為檔案,這個是利用chrome devtools API來采集的整個程序,
用chrome devtools API采集主要的問題是什么,它主要有三種問題,一個是整個程序他會有png/jpeg資料的編碼、解碼,以及視頻的編碼程序,它整個開銷是很大的,另外它不支持音頻的采集,然后在node應用和chrome應用之間它是使用websocket來進行資料傳輸的,它的傳輸性能是比較差的,
在介紹完兩種主要的chrome進行頁面資料采集的方法之后,我們來給大家介紹一下,chrome是怎么進行頁面渲染的,它的流程大概是怎么樣,然后chrome內部又提供了哪些介面來讓我們進行頁面采集和錄制,
我們知道chrome瀏覽器或者web引擎在決議了HTML檔案之后會創建DOM樹,DOM Tree中的每一個節點它都對應到HTML檔案當中的一個節點,對DOM tree應用CSS屬性之后,會生成相應的layout tree,layout tree中的每一個節點就是layout object,它除了能夠表達它在檔案結構當中的位置,它還能表達它在排版程序中的所有屬性,
在頁面繪制的程序中,web引擎它不會對整個layout去進行完整的繪制,它會對layout tree進行一個分層,對每一層進行獨立的繪制,最后通過合成器把不同的繪制結果合成出來,
這個分層的程序其實是有一些規則的,它主要包含哪些規則呢?
首先我們的DOM Tree的根節點以及和它相關的這些節點,會作為一個layer來進行繪制,有一些特殊的節點比如包含一些位置資訊應用了relative, absolute或者transform屬性的這些節點,也會作為獨立的層來進行繪制渲染,對于一些應用了CSS filter的節點也會作為獨立層渲染,
假如說有一些節點會產生溢位會產生overflow它也會進行獨立的繪制,另外在DOM Tree中,對于一些特殊的節點,比如說像video element 或者canvas element來進行2D或者3D內容繪制的時候,這些節點也會作為獨立的層來進行繪制,
在分層之后,我們的web engine會對render layer 或者paint layer來創建相應的Graphics Layer,由所有Graphics Layer組成的樹就叫做Graphics Layer Tree,每一個Graphics Layer當中會維護一個Graphics context,這個Graphics context就是這一層內容繪制的一個目標,每一個Graphics layer還會維護一個叫paint controller,這個paint controller會來控制我們每一層具體的繪制,
除了Graphics layer tree之外我們的layout tree 在預繪制的程序當中還會生成相應的Property Trees,所謂的Property其實是指四種型別的屬性,它包含了像Transfer,一些位置的變換;或者是clipping,對內容進行裁剪;包括effect,一些特效,比如像透明度或者mask資訊;然后還有scroll資訊,就是當我的內容需要進行滾動的時候它也會有自己的一些資訊,這些屬性都會存盤在這個Property Trees里面,
當我們的DOM檔案結構發生變化的時候,或者CSS屬性發生變化的,或者是Compositing發生變化的時候它會產生相應的invalidation,一旦invalidation發生,layout Tree會找到它發生變化的相應的節點,然后會利用它相應的Graphics layer來進行繪制,這個圖是發生變化的區域,
繪制的程序實際上就是把layout Tree當中的layout Object屬性轉換成繪制指令的程序,這個繪制指令是通過術語display items來進行表達的,
有了這個Graphics layer Tree和Property Trees,之后我們可以對Graphics layer tree中的每一層來使用Compositer來進行合成,由于Compositer的輸入有自己的格式,我們需要對Graphics layer tree和Property Trees進行轉換,需要把Graphics layer tree轉成layer list,把blink Property Trees轉成CC Properties Trees,拿到這兩個結構之后瀏覽器接下來就可以進行合成,
所謂的合成就如上面這張圖所顯示的,它是把瀏覽器當中的不同的部分,以及瀏覽器的界面進行層疊,最終顯示出我們可以看到的最終效果的程序,但是在瀏覽器當中實際的合成程序是分成兩個部分的,有兩個階段,
在第一階段是在瀏覽器的渲染執行緒來實作的,它通過剛才我們拿到的layer List和CC Properties Trees來形成光柵化,如果是軟體光柵化會生成位圖,如果是硬體光柵化會形成GPU當中的紋理,
拿到這些結果之后來進行層疊處理,最終Compositer會輸出一個叫做Compositer Frame的資料,Compositer Frame并不是實際的最終渲染的結果,它不是一個位圖或者一個紋理,它實際上也是一系列的繪制指令,這些指令在光柵化之后才會產生實際的位圖或者是紋理,
在第二個階段我們的瀏覽器會利用dispaly Compositer 把我們上一階段的合成結果和瀏覽器的UI部分,比如說像地址欄、標題、標簽頁的按鈕來進行合成,最終輸出到物理設備上面,這整個渲染程序就完成了,在render行程和browser(瀏覽器)行程之間它的資料傳輸是通過shared Memory的形式來傳輸的,
通過了解上面的這個程序其實我們就可以很明顯的發現,如果我們需要對chrome當中的頁面進行采集的話我們需要的是什么,我們需要的其實就是Compositer Frame光柵化之后的bitmap 或者是texture,通過對這個資料的編碼以及經過muxer存盤檔案,我們的錄制就可以完成了,
接下來我們來看一看chrome這個專案當中提供了哪些介面來讓我們對音視頻資料進行采集,在chrome當中由于它的渲染行程是跑在sandbox(沙盒)里面的,在sandbox(沙盒)行程當中是沒有辦法對系統呼叫,它對系統API的呼叫是受限的,由于我們的錄制需要去訪問本地檔案,所以我們整個的采集程序是在Browser行程來實作的,
chrome在Browser執行緒提供了ClientFrameSinkVideoCapturer這個類,這個類會向渲染執行緒發起相應的采集請求,客戶程式只需要通過當前tab頁對應的CreateVideoCapturer來實體化這個類,
同時實作FrameSinkVideoConsumer這個介面,通過這個介面當中的OnFrameCapturer的回呼來獲取它回傳的每一幀的Video Frame資料,
在Render行程大概的程序是怎么樣的呢(如上圖所示),由于時間有限我不做太多的介紹,只描述一下簡單的程序,在類FrameSinkVideoCapturer當中,當它接收到Browser端發來的采集請求,就會去Schedule一次采集的請求,然后會把這個請求轉發給它聚合的CompositorFrameSinkVideoSupport物件,這個物件拿到請求之后,會把這個請求放在佇列里面,
由于CompositorFrameSinkVideoSupport物件,它實作了SurfaceClient的介面,可以從Compositer當中拿到輸出的Surface,一旦有新的Surface產生它就會得到通知,它會利用實作的CompositerFrameSink介面,通過其中的didAllocateSharedBitmap方法來創建相應的共享記憶體,然后把Surface當中的資料寫到共享記憶體當中,同時會把這個共享記憶體的ID回傳給Browser行程,Browser行程拿到這個ID之后會從Shared Memory里面把資料給解出來,這個采集程序就完成了,
那么對于音頻的處理呢(如上圖所示),chrome在進行音頻播放的時候它會把頁面當中所有的Audio Media Streams直接交給系統的Audio framework來進行混音和播放,它的混音的程序是由Audio framework來做的,如果我們需要對所有的Audio media Streams進行采集的話我們需要自己來實作混音的程序,混音之后的Audio 資料我們把它交給encoder進行編碼,最終存盤到檔案里面來,
同樣在chrome當中它也提供了相應的介面,讓我們來進行音頻資料的采集,這個介面主要有AudioLoopbackStreamCreator以及Audio input stream這兩個類,
我們通過創建AudioLoopbackStreamController來啟動一個音頻采集程序,在啟動之后它會通過AudioLoopbackStreamCreator去創建一個AudioLoopbackStream,同時它會去創建相應的執行緒,這個執行緒會通過Socket不斷的從Render 行程來獲取采集到的音頻資料,一旦這個Audio frames達到一定量之后足夠多,它會去出發相應的callback,這個callback回呼會最終把資料交給錄制程式,
所謂的AudioLoopbackStream是chrome當中一個特殊的資料,它稱為回環音頻流,它會把chrome當中所有輸出的Audio Streams進行合成,然后把合成的結果轉換成一個輸入流,
具體在Render端是怎么實作混音的程序我們在這個地方就不展開描述了,
服務端 Web 錄制引擎
接下來我們聊一聊在服務端進行WEB錄制它所需要的一些需求,主要有幾點,
一個是無UI模式,我們在服務端進行頁面內容的采集,它通常是跑在一個無桌面的linux的服務環境下,同時它采集的格式需要滿足實作流媒體格式,因為傳統的媒體格式往往是存盤單個的檔案,一旦服務端發生了故障,它之前所錄制視頻內容很有可能會丟失,同時錄制引擎也需要去指定一些錄制引數,比如說頁面渲染的解析度,最大的視頻錄制幀率,音頻采樣率、音頻編碼率,包括流媒體檔案切片的時長等等,由于我們的Web Engine是一個開放的平臺,理論上它是可以運行所有的Web應用的,有一些Web應用可能性能開銷會非常大,所以在服務端Web應用錄制的程序當中我們需要對整個應用的開銷進行監測,對于可能產生系統資源過度使用的HTML5組件來進行一些控制,同時對引擎內部運行的一些狀態進行監測,發生錯誤要能進行上報,
我們聲網在實作相應的Web云錄制引擎當中針對這四點也做了大量的作業,主要是Headless模式我們是基于Chromium Headless模式實作的,它不需要像Xvfb這樣的虛擬X Server環境作為頁面的渲染目標,
另外由于我們整個錄制程序是一個無互動的程序,我們需要允許引擎能夠令這個音視頻內容在無互動的情況下進行自動播放,
另外很重要一點是我們的錄制引擎是不提供Web API的,不需要Web應用主動發起錄制的請求,頁面的錄制結果是一個所見即所得的程序,Web引擎當中渲染了什么內容,它就會做出相應的錄制,
對于錄制格式方面我們主要支持TS和M4A兩種流媒體格式,同時會輸出相應的M3U8的檔案串列,在編碼器方面我們可以選用Openh264、X264、聲網自研的a264編碼器來對錄制內容進行編碼,
我們整個錄制程序是一個動態幀率的編碼程序,只有當頁面發生變化的時候,輸出新的video frame的時候我們才會對它進行采集和錄制,
在音頻部分我們使用的是剛才介紹過的AudioLoopbackStream來進行混音,對混音的資料進行采集再進行編碼,音頻的編碼我們使用AAC格式,因為像ts這種流媒體格式當中opus編碼格式是不受支持的,AAC格式通常在更多的媒體檔案型別下能得到更好的支持,
我們提供的引數主要有這些(如上圖),視頻輸出的路徑包括日志的路徑,同時可以指定音頻的采樣率是41000或者48000赫茲,錄制的聲道數,錄制的音頻的碼率,視頻編碼的碼率,以及視頻的錄制幀率,我們頁面渲染的尺寸高度或者寬度,流媒體HRS檔案它切片的時長等等都可以進行設定,
同時也會對H5的一些性能開銷比較大的組件進行一些控制,比如說像WebGL、Web Assembly ,同時對于一些比較大的尺寸的視頻,對它的播放進行一些控制,
在性能和安全性方面,其實剛才也提到了,主要會對Web GL、Web Assembly包括高解析度的視頻進行播放的時候會進行一些開關,然后我們引擎本身也會對CPU、記憶體、帶寬等等系統資源進行自我監測和上報,對于一些檔案操作,比如說像檔案下載,包括對file://scheme訪問會進行限制,在URL加載例外的時候會對例外以及例外的原因會進行一個上報,對頁面音視頻的采集程序以及編碼的狀態也會進行上報,這些內容會上報到我們服務端的應用框架當中,服務端應用框架收到這些上報資訊之后會做相應的處理,
引擎對外發出的一些通知,主要有錄制的開始結束,包括檔案切片的開始以及完畢,有音視頻編碼器的初始化成功或者失敗,還有采集到第一幀音頻的時候或者第一幀視頻的時候都會發出相應的通知,還會在音頻編碼失敗的時候和視頻編碼失敗的時候發出一些通知,錄制引擎還會周期性的去監測我們距離上一幀采集到音頻或者視頻的時間間隔,
當URL訪問出現例外的時候會對URL例外的原因進行上報,最后Recording Prof,會對CPU、記憶體、帶寬使用率進行通知,
剛才介紹了服務端Web錄制引擎的一些特征,包括聲網在實作Web錄制引擎當中做的一些事情,
服務端Web錄制引擎性能優化
接下來我們聊一聊Web錄制引擎性能優化,Web錄制引擎它的整個的程序本質上是一個從視頻解碼、音頻解碼到頁面渲染、頁面合成再到視頻音頻編碼的程序,它整個程序的開銷實際上是非常大的,
我們目前主要有幾點對Web錄制引擎進行優化,第一點就是chrome本身它在使用OpenH264的時候,出于平臺兼容性的考慮,它沒有啟用AVX2指令來做CPU的優化,AVX2指令是在intel haswell平臺之后推出的AVX指令的擴展,它擴展了原來AVX的指令計算資料的位寬,從128位擴展到了256位,能夠有更好的向量運算的性能,
另外一個優化點就是使用GPU來做編解碼的加速,我們知道limux平臺上面通常它的顯示驅動或者設備,性能一般不是太好,或者有各種問題,出于這個原因chromium把linux平臺的硬體加速的視頻編解碼列入了黑名單,也就是說它只用軟體的方式來進行音視頻的編解碼,
由于我們的整個系統是運行在服務端,這個平臺是確定的,在保證這個平臺的圖形的驅動穩定的情況下,我們可以去把這個視頻編解碼從黑名單中移除來啟用它的硬體加速,同時我們在對視頻進行編碼的時候可使用ffmpeg加VAAPI來進行硬體加速,
第三個就是對頁面渲染性能本身的一個優化,chrome headness一些特殊的條件下,可以在limux服務器上去使用OpenGL進行硬體加速,但是它要求我們的整個服務端的環境必須是在桌面系統上,也就是基于X11的OpenGL去進行硬體加速,
由于我們的錄制引擎通常是部署在multi user模式下的linux服務器上面,它是沒有桌面環境的,在這個時候我們可以在chromium當中使用ozone圖形中間層,結合DRM后端來實作脫離X11的OpenGL硬體加速,ozone是ChromiumOS上面默認所采用的圖形子系統,它是一個中間層,它的后端可以有各種不同的實作,比如說有基于X11,Wayland, 或者基于GBM/DRM的,基于DRM的實作它可以脫離桌面環境,這樣我們可以在headness multi user模式的來enable硬體加速,
最后的一個優化程序是對web引擎渲染流程流程本身的一個優化,剛才我講到整個的Web錄制程序實際上是音視頻解碼—頁面渲染—合成—編碼的程序,性能開銷非常的大,
對于整體的渲染流程我們可以來進行一些優化,比如說業務高峰時期在實時處理的時候,我們接收到視頻資料之后可以不進行解碼也不進行播放,直接把接收到的視頻流進行轉儲,存盤為相應的視頻檔案,
在頁面當中視頻的區域我們使用空白來進行占位,同時對區域的位置進行記錄,以及對這個視頻的播放狀態,是播放還是暫停、停止來進行一個記錄,
我們知道頁面當中如果沒有視頻的渲染的話,通常它的FPS都是比較低的,這就意味著我們從這個瀏覽器當中采集出來的video frame的幀率會很低,甚至是靜止,這樣它的編碼開銷就很小,我們把沒有視頻部分的page進行編碼進行錄制來生成檔案之后就能得到頁面部分的檔案,以及從Media stream(流媒體)轉儲的視頻檔案以及視頻的一些狀態資訊,有了這三個資訊之后,我們可以在業務低谷時間進行視頻的合成,來有效的降低我們在業務高峰時段的服務器的壓力,以上就是服務端的錄制引擎的性能優化,
最后,我們來進行一個總結,主要有以下幾點,
當前主流的瀏覽器對Web錄制的支持并不能滿足我們的業務需求,
我們所需要的業務能力和可用性可以通過定制chromium瀏覽器來實作,
Web云錄制作為一種新的技術和業務形態,它面對性能和安全性的雙重挑戰,
我們針對目標硬體平臺來進行CPU和GPU的優化能夠比較有效的緩解性能的問題,
通過對Web引擎渲染流程進行特殊優化能夠有效的降低我們的服務在業務高峰時的性能壓力,
以上就是我的分享,非常感謝,
回顧更多演講,可訪問:rteconf.com/look-back
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/232591.html
標籤:其他
下一篇:Vue 3 refs 是什么鬼
