基於 Web 引擎技術的 Web 內容錄製

最近學習音視頻相關技術看到一個很好的技術分享,實現了基於Chromium的web內容錄製。現在分享出來大家一起學習一下。

B站視頻:聲網Agora Web 引擎高級架構師 高純:Web 互動場景還原——基於 Web 引擎技術的

原文鏈接:https://blog.csdn.net/agora_cloud/article/details/110913869

原文內容:

隨着基於WebRTC技術的Web應用快速成長,記錄web在線教育、視頻會議等場景的互動內容並對其準確還原越來越成爲一項迫切需求。在主流瀏覽器中,通常基礎設施部分已實現了頁面渲染結果的採集及編碼。開發者可以利用瀏覽器提供的API對頁面內容進行錄製。但受限於Web標準以及瀏覽器廠商在專利授權方面的問題,使用Web API實現頁面錄製在易用性和可用性上均較難令人滿意。針對上述問題,聲網Agora Web 引擎高級架構師高純在 RTE 2020 實時互聯網大會上就Web引擎渲染採集原理進行了分享,並就基於Web引擎的服務端錄製技術進行探討。

以下爲演講實錄:

大家好,我這次技術分享的主題是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引擎渲染流程進行特殊優化能夠有效的降低我們的服務在業務高峯時的性能壓力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章