隨着基於WebRTC技術的Web應用快速成長,記錄web在線教育、視頻會議等場景的互動內容並對其準確還原愈來愈成爲一項迫切需求。在主流瀏覽器中,一般基礎設施部分已實現了頁面渲染結果的採集及編碼。開發者能夠利用瀏覽器提供的API對頁面內容進行錄製。但受限於Web標準以及瀏覽器廠商在專利受權方面的問題,使用Web API實現頁面錄製在易用性和可用性上均較難使人滿意。針對上述問題,聲網Agora Web 引擎高級架構師高純在 RTE 2020 實時互聯網大會上就Web引擎渲染採集原理進行了分享,並就基於Web引擎的服務端錄製技術進行探討。node
▶️點擊「閱讀原文」可觀看視頻回放,獲取 PPTlinux
如下爲演講實錄:web
你們好,我此次技術分享的主題是Web互動場景還原—基於Web引擎技術的Web內容錄製。chrome
我叫高純,是來自聲網的Web引擎高級架構師,接下來我將會爲你們介紹如下的內容:包括應用背景、瀏覽器內容採集、服務端Web錄製引擎,服務端Web錄製引擎性能優化,最後會進行一個總結。canvas
先看一下應用的背景,咱們知道最近幾年隨着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上面,它所支持的視頻編碼格式主要有H26四、VP八、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的文件列表,在編碼器方面咱們能夠選用Openh26四、X26四、聲網自研的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