我是 Dale Curtis,Chromium 媒體播放工程團隊的負責工程師。我的團隊負責處理影片播放的網路 API,例如 MSE 和 WebCodecs,以及涉及音訊和影像解多工、解碼和轉譯的平台專屬內部元件。
本文將逐步說明 Chromium 的影片算繪架構。雖然可擴充性相關的部分細節可能只適用於 Chromium,但本文討論的大多數概念和設計也適用於其他轉譯引擎,甚至是原生播放應用程式。
多年以來,Chromium 的播放架構已大幅變更。雖然我們一開始並未採用本系列第一篇文章所述的成功金字塔概念,但最終還是採取了類似的步驟:可靠性、效能,然後是可擴充性。
在一開始,影片算繪作業相當簡單,只需一個 for 迴圈,選擇要將哪些軟體解碼的影片影格傳送至合成器。多年以來,這項做法都相當可靠,但隨著網站複雜度增加,為了提升效能和效率,我們必須改變架構。許多改善項目都需要使用特定作業系統的原始碼,因此我們的架構也必須更具延展性,才能支援所有 Chromium 平台。
影片算繪可分為兩個步驟:選擇要傳送的內容,以及有效傳送該資訊。為了方便閱讀,我會先介紹如何有效地提交內容,再深入探討 Chromium 如何選擇要提交的內容。
部分條款和版面配置
由於本文著重於算繪,因此我只會簡要介紹管道中的解析和解碼部分。
在重視安全性的現代世界中,解碼和解多工處理需要相當謹慎的處理。二進位檔案剖析器是豐富的目標環境,而媒體播放則充滿二進位檔案剖析。因此,媒體剖析器中的安全性問題非常常見。
Chromium 採用多層防護做法,降低使用者遭遇安全性問題的風險。實際上,這表示解多工和軟體解碼作業一律會在低權限程序中執行,而硬體解碼作業則會在權限足以與系統 GPU 通訊的程序中執行。
Chromium 的跨處理程序通訊機制稱為 Mojo。雖然我們不會在本文中深入探討 Mojo 的細節,但 Mojo 是 Chromium 可擴充媒體管道的基礎,也是程序之間的抽象層。在我們介紹播放管道時,請務必留意這一點,因為這會影響跨程序元件複雜的協調作業,這些元件會互動以接收、解多工、解碼,最後顯示媒體。
太多位元
如要瞭解目前的影片算繪管道,就必須瞭解影片的特殊之處:頻寬。以 60 張影格/秒的速度播放 3840 x 2160 (4K) 解析度的內容,會使用 9 到 12 Gbit/秒的記憶體頻寬。雖然現代系統的最高頻寬可能達到每秒數百 GB,但影片播放仍占據相當大的頻寬。如未妥善處理,GPU 和 CPU 記憶體之間的複製或傳輸作業,可能會使總頻寬輕易倍增。
任何以效率為優先的現代影片播放引擎,其目標都是盡可能減少解碼器和最終算繪步驟之間的頻寬。因此,影片算繪作業已從 Chromium 的主要算繪管道中分離。具體來說,從主要轉譯管道的角度來看,影片只是具有透明度的固定大小洞。Chromium 使用「介面」這個概念達成這項目標,每個影片都會直接與 Viz 對話。
由於行動運算的普及性,效能和效率已成為當前世代的重要重點。因此,在硬體層級,解碼和轉譯的耦合度比以往更高,導致影片看起來就像是帶有不透明度的洞,甚至連作業系統本身也是如此!平台層解碼器通常只提供不透明緩衝區,Chromium 會以重疊的形式將其傳遞至平台層合成系統。
每個平台都有自己的疊加層,平台解碼 API 會與之配合運作。Windows 有直接合成和Media Foundation 轉換,macOS 有CoreAnimation Layers 和VideoToolbox,Android 有SurfaceView 和MediaCodec,Linux 則有VASurfaces 和 VA-API。Chromium 對這些概念的抽象化處理,分別由 OverlayProcessor 和 mojo::VideoDecoder 介面處理。
在某些情況下,這些緩衝區可以對應至系統記憶體,因此不必設為不透明,且在存取前不會耗用任何頻寬。Chromium 稱這些為 GpuMemoryBuffers。在 Windows 上,這些元素由 DXGI 緩衝區提供支援;在 macOS 上,由 IOSurfaces 提供支援;在 Android 上,由 AHardwareBuffers 提供支援;在 Linux 上,由 DMA 緩衝區提供支援。雖然影片播放通常不需要這項存取權,但這些緩衝區對於影片擷取作業非常重要,可確保擷取裝置和最終編碼器之間的頻寬降到最低。
由於 GPU 通常負責解碼和顯示,因此使用這些 (通常也是不透明的) 緩衝區,可確保高頻寬的影片資料不會實際離開 GPU。如先前所述,將資料保留在 GPU 上對於效率而言至關重要,尤其是在高解析度和影格率的情況下。
我們越能善用 OS 基本元素 (例如疊加層和 GPU 緩衝區),就越能減少不必要地交換視訊位元組所需的頻寬。將所有從解碼到算繪的過程集中在同一個位置,可大幅提升電力效率。舉例來說,當 Chromium 在 macOS 上啟用疊加層時,全螢幕影片播放期間的耗電量就會減少一半!在 Windows、Android 和 ChromeOS 等其他平台上,即使在非全螢幕模式下,我們也可以使用疊加層,幾乎所有地方都能節省高達 50% 的效能。
轉譯
我們已介紹最佳的提交機制,接下來讓我們討論 Chromium 如何選擇要提交的內容。Chromium 的播放堆疊使用以「拉」為基礎的架構,也就是說,堆疊中的每個元件都會以階層順序,向其下方的元件要求輸入內容。堆疊頂端是音訊和影像影格算繪作業,再往下則是解碼、解串流,最後是輸入/輸出。每個轉譯的音訊影格都會推進時鐘,並在與呈現間隔結合時,用於選擇要轉譯的影片影格。
在每次呈現間隔 (螢幕的每次重新整理) 時,系統會要求影片轉譯器透過附加至前述 SurfaceLayer 的 CompositorFrameSink 提供影片影格。如果內容的影格速率低於顯示速率,就會重複顯示相同的影格;如果影格速率高於顯示速率,則部分影格不會顯示。
要讓觀眾滿意,音訊和視訊的同步方式還可以做得更好。 如要進一步瞭解如何在 Chromium 中實現最佳的影片流暢度,請參閱 Project Butter。說明如何將影片算繪分解為理想的序列,代表每個影格應顯示的次數。例如:「每個顯示間隔 1 個影格 ([1],60 Hz 的 60 fps)」、「每 2 個間隔 1 個影格 ([2],60 Hz 的 30 fps)」,或是更複雜的模式,例如 [2:3:2:3:2] (60 Hz 的 25 fps),涵蓋多個獨立的畫格和顯示間隔。影片算繪器越接近這個理想模式,使用者就越有可能認為播放內容流暢。
雖然大多數 Chromium 平台會逐格渲染,但並非所有平台都會。我們的可擴充架構也支援批次渲染。批次轉譯是一種效率技巧,可讓作業系統層級的轉譯器事先得知多個影格,並在應用程式提供的時程表上處理這些影格的釋出作業。
未來就在現在?
我們著重於說明 Chromium 如何善用 OS 基本元素,提供一流的播放體驗。但如果網站想要提供比基本影片播放功能更進階的服務呢?我們能否提供與 Chromium 相同的強大原始元素,以迎接下一代網頁內容?
我們認為答案是肯定的! 可擴充性是我們目前對網路平台的核心看法。我們一直與其他瀏覽器和開發人員合作,開發 WebGPU 和 WebCodecs 等新技術,讓網頁開發人員在與作業系統互動時,能使用與 Chromium 相同的原始碼。WebGPU 可支援GPU 緩衝區,而 WebCodecs 則可提供與上述覆蓋和 GPU 緩衝區系統相容的平台解碼和編碼原始碼。
串流結尾
感謝閱讀!希望您已進一步瞭解現代播放系統,以及 Chromium 如何每天提供數百萬小時的觀看時間。如想進一步瞭解編解碼器和新式網路影片,建議您參閱 Sid Bala 撰寫的 H.264 是神奇的、Erica Beaves 撰寫的 新式影片播放器的運作方式,以及 Cyril Concolato 撰寫的 運用獲獎技術製作得獎節目。
由 Una Kravets 繪製的插圖 (很漂亮!)。