修改Chromium源碼,實現HEVC/H.265 4K視頻播放

作爲 H.264 的後繼產品,HEVC/H.265 是一種高級視頻壓縮標準,能夠節省50%的比特率,帶來驚人的視覺質量。例如,在Converter 的 DivX HEVC 配置文件中編碼的視頻,不僅保持了DivX Plus 配置文件的高質量,文件還更小。即使在擁擠的網絡環境中,HEVC 對於傳輸高質量視頻也至關重要,並且是將 4K內容傳輸到 4K/Ultra HD 顯示器的驅動因素。本文作者分享了其公司推廣 HECV/H.265 的經驗,並介紹了該技術的相關背景。

公司內容生產端最近(2019/10)在推廣 HEVC/H.265 的使用,這種視頻編碼格式對比 H.264 更加先進且節省帶寬,雖然先進但是因爲專利費的問題,在普及度,軟硬件方面的支持都遠不及預期。視頻技術水深,還是先把了解的背景給大家整理羅列一下。

HEVC 軟硬件的支持

瀏覽器

Can I use 測試基本全軍覆沒,除了 iOS 11 以上的移動端 safari 和 chrome。

系統原生支持

Native 支持的情況已經相當不錯,這裏的支持是操作系統提供了 HEVC 編解碼的 api,底層實現會根據硬件條件優先硬解否則軟解,當然移動端機型也會因爲系統定製、專利、性能考量等原因屏蔽支持(猜測)還要用黑名單控制一下。

  • Android:Android 5.0+
os_version >= 5.0 && app_version >= 570 && device_model not in blacklist
  • iOS:iOS 11+
os_vesion >= 11 && minMajorModelVersion >= 8 && device_model not in blacklist
  • macOS:macOS High Sierra+
  • Windows:DirectX Video Acceleration (DXVA),這個不是很確定,需要進一步深入調研,或許會在下一篇文章介紹。

硬件支持

  • Intel :支持較好,在第六代 Skylake 架構 cpu 及以上實現 HEVC 8-bit 編解碼。
  • Nvidia GPU:Nvidia 900 Series GPUs (GeForce GTX 960 或者 950) 以上都支持。
  • AMD GPU :具體的支持度配合查看 Unified Video Decoder;比較完整的支持需要到 UVD 6.0 以上。
  • 蘋果 A :A9 及型號或者以上,A11 及以上支持 10-bit HEVC 編解碼。
  • 華爲麒麟:使用 Mali 的 GPU,2014 年以後都支持硬解,10-bit 超高清視頻 + (HDR) + wide color gamut coverage。
  • 高通驍龍 Qualcomm Snapdragon :805, 810, 820, 835 支持 4K HEVC decoding. 615,410, 208 或以上芯片支持硬解。
  • 聯發科 MediaTek:同樣使用 Mali GPU。

編解碼技術

我們說的 HEVC 是一種編碼技術的標準,真正應用和落地的時候還有大量的優化工作,從全球最權威的 MSU 視頻編碼大賽 2018 的報告中可以看出,中國的軍團獨佔風騷,華爲、騰訊和金山的表現都相當搶眼,可以說是站在了世界一流的梯隊中,當然宋利提出國外有高手並未參賽。不管如何,2019 年已經接近尾聲,相信會有新的一輪比拼和報告出現。

這裏面開源的有:

  • X265,編碼器,主要由 MulticoreWare 公司管理開發,並擁有著作權和商標權,開源協議是 GNU GPL v2 license。
  • openHevc,解碼器,開發語言 C 基於 ffmpeg/libav 框架。
  • libde265,解碼器,發佈在 GitHub 上,採用 LGPL 許可證授權。

這些編碼的 SDK 我們能用嗎?據我瞭解,這裏面都是商家的核心技術,性能都是大幅領先開源編解碼器,這裏列出來做一個對標和選型參考。我瞭解到,字節跳動的視頻架構組也有自研的編解碼器,據說性能也是相當不錯~

競爭格局

說起這個,不得不提起 HEVC 那噁心的專利費,許多內容提供商不願意部署跟進 HEVC 的最主要原因是,當前有三個不同專利聯盟開黑在嗷嗷收錢,每個聯盟背後代表的公司也不一樣。包括 MPEG LA、HEVC Advance、Velos Media,對錯綜複雜的關係感興趣的話,可以看看這篇文章

簡單說,又貴又麻煩風險不可控(互聯網企業最怕的就是養肥再殺的這一招),所以必然導致發展緩慢,替代品出現。

重點來了,蘋果是 HEVC 專利擁有者,所以一直高調支持 HEVC;Google 不在開黑聯盟裏面,沒什麼專利,底下 YouTube 就是頭大肥羊,是堅決不用 HEVC 的,目前 Google 用自研的 VP9;2015 年成立了一個新聯盟叫 AOM(Alliance for Open Media) 開放媒體聯盟,主要研發 AV1 的編碼格式用於替代 HEVC 和 VP9,並且完全免費!機智的蘋果一邊收 HEVC 的費用,一邊過來站隊,還混了個 AOM 管理成員的職位,大佬畢竟還是大佬。

瀏覽器支持 HEVC 解碼現有方案

在 Web 前端技術標準之上的方案,最近幾年,各個前端團隊也都經過深入探索和總結。其中,淘寶前端團隊的方案比較有代表性,大體的思路是:

  • JS 實現解封裝。
  • ffmpeg 模塊轉換成 WebAssemblly,用來實現高效音視頻解碼。
  • 引入 WebWorker 改善解碼模塊性能。
  • 分離的視頻解碼後用 canvas 繪製,音頻用 audio 播放。
  • 優缺點:性能相對好,但是和 C 實現的還有較大差距,如果替換成更好的解碼器會更快(比如,金山點播的 web 實現,收費),工作量大,無法硬件加速,要支持 4K 還是比較難。

具體大家可以看淘寶的這篇文章,各家的技術會調整某些流程或局部優化,這裏缺少深入調研就不多做介紹了。而我目前要解決的場景,主要問題就是性能和穩定性,所以我嘗試尋找一個在 c++ 層面的實現。

當時第一個考慮的其實是 Electron,Electron 集成了 Node.js,因此可以使用 c++ addon 的能力,經過一番調查,發現仍然不是最佳方案。雖然 c++ 代碼能提供最佳的解碼性能,但是 addon 裏無法直接調用渲染接口(涉及到 addon 的機制,即使能實現也非常 Hack),需要把數據回調到 JS 堆讓後通過調用 canvas 實現渲染。這種方案相比上面理論上有提升,仍然意猶未盡。想了解更多 chromium 關於 video 的實現參考 chromium 的官方文章 Audio / Video Playback,這裏面借用它的圖片直接敲重點:

  • 架構設計上支持 任何音視頻編碼。
  • 文章解釋了設計的一些初衷和思考,包括爲了避免版權放棄了一些相對成熟的實現方案。

所以我就在想,直接修改 chromium 源碼,參考 video 對 h.264 的實現機制完成 HEVC 的播放需求,相當於連硬件解碼的能力也打通了(調研嘗試中),於是另一種實現方案呼之欲出。

修改 Chromium 實現真 4K HEVC 播放

修改 Chromium 源碼的方案,原來 chromium 裏面已經實現了 HEVC 解碼邏輯,只是加了開關禁止了相關代碼,一切比想象的簡單。最大的門檻其實是網絡(無奈),不包含歷史數據的源碼(–no-history),零零總總就有接近 2GB,翻牆軟件不穩定的入坑需謹慎,嘗試過中間斷掉需要重新開始的。還好公司網絡給力,30MB+/s,我用的是 windows 機器,如何安裝看這篇 Checking out and Building Chromium for Windows

注:macOS 裏面我也嘗試過,比 windows 簡單多了,具體看 Checking out and building Chromium for Mac

拿到源碼過了一遍 video 相關的官方文檔,總體瞭解對後面定位和排查問題有很大幫助:

如果上網搜索一下實現方案,引用最多的就是 henrypp/chromium,他列出了相對完整的修改代碼,可惜版本比較老,一步一步改完後編譯出來的版本仍然不能播放 HEVC/H.265 的 MP4 視頻。心情反倒輕鬆了,哪有這麼容易的事,開啓 debug 編譯,準備用 visual studio 調試看看哪一步漏了。

在 src 目錄裏面,用這個命令生產構建工程。

gn gen --ide=vs2019 --filters=//chrome;//media/* --no-deps out\debug --
args="is_component_build = true enable_nacl = false is_debug = true blink_symbol_level = 0"

–ide=vs2019,指明生產 visual studio 2019 的 solution,這樣方便我們使用 VS 強大的 debug 工具,但實際上如果在項目裏點擊構建,內部還是用 gn 的配置。

–filters=//chrome;//media/,//chrome 是入口,必須包含,因爲不關心入口裏面的具體項目,所以沒有用 //chrome/。我們重點調式 media 相關的源碼。

打開 out/debug/all.solution 之後,把 chrome 項目設置爲啓動項目,點擊調試。

因爲 chromium 的進程模型,每一個 tab 都在單獨的子進程裏面,並且一般有多個線程,所以需要手動附加到所有的線程。這裏一個比較笨的方法是,關掉正常的 Chrome 瀏覽器,然後把能附加的 chrome.exe 全都加上就可以了。

調試的過程也是一個推理和學習的過程,還是相當有趣的,調試和構建一個 GB 級別以上的項目技能點亮。這裏的過程就不一一列舉了,直接放出修改源碼(這裏面有一部分和 henrypp 重合直接整合列出來了)。

注 1:構建過程可以隨時中斷,下一次編譯就接着上一次的進度,而且 VS 裏面的構建和官方命令行 autoninja -C out/debug chrome 是等價互通的。

注 2:debug 編譯結果需要佔用~30GB 的空間,主要用來存放 debug 相關的.pdb 文件和編譯結果,第一次編譯的時候比較慢,後續再修改就會相對比較快。

源碼修改

這裏不是文章的重點,但還是放出來給喜歡折騰的人嘗試一下。提醒一下,HEVC 是需要專利授權的,Google 也對 chromium 的修改分發有一定的限制,大家修改作何用途自行評估。

筆者編譯出來的版本是 79.0.3928.0

修改這個文件,找到下面條件判斷並修改代碼:

  • src/third_party/ffmpeg/ffmpeg_generated.gni
if ((is_mac) || (is_win) || (use_linux_config)) {
  ffmpeg_c_sources += [
    "libavcodec/autorename_libavcodec_hpeldsp.c",
    "libavcodec/autorename_libavcodec_videodsp.c",
    "libavcodec/autorename_libavcodec_vp3dsp.c",
    "libavcodec/autorename_libavcodec_vp8dsp.c",
    "libavcodec/h264pred.c",
    "libavcodec/vp3.c",
    "libavcodec/vp3_parser.c",
    "libavcodec/vp56rac.c",
    "libavcodec/vp8.c",
    "libavcodec/vp8_parser.c",
  ]
  ffmpeg_c_sources += [
    "libavcodec/bswapdsp.c",
    "libavcodec/autorename_libavcodec_hevcdec.c",
    "libavcodec/hevc_cabac.c",
    "libavcodec/hevc_data.c",
    "libavcodec/hevc_filter.c",
    "libavcodec/hevc_mvs.c",
    "libavcodec/hevc_parse.c",
    "libavcodec/hevc_parser.c",
    "libavcodec/hevc_ps.c",
    "libavcodec/hevc_refs.c",
    "libavcodec/hevc_sei.c",
    "libavcodec/hevcdsp.c",
    "libavcodec/hevcpred.c",
    "libavcodec/x86/bswapdsp_init.c",
    "libavcodec/x86/hevcdsp_init.c",
    "libavformat/autorename_libavformat_hevc.c",
    "libavformat/hevcdec.c",
  ]
  ffmpeg_asm_sources += [
    "libavcodec/x86/bswapdsp.asm",
    "libavcodec/x86/hevc_deblock.asm",
    "libavcodec/x86/hevc_idct.asm",
    "libavcodec/x86/hevc_mc.asm",
    "libavcodec/x86/hevc_add_res.asm",
    "libavcodec/x86/hevc_sao.asm",
    "libavcodec/x86/hevc_sao_10bit.asm",
  ]
}

複製並重命名這個文件裏的兩個文件 src/third_party/ffmpeg:

  • libavcodec/hevcdec.c to libavcodec/autorename_libavcodec_hevcdec.c
  • libavformat/hevc.c to libavformat/autorename_libavformat_hevc.c

修改這兩個文件,YOUR_BRAND 就是 chromium,YOUR_ARCH 像我用 Windows 就是 x64:

  • src/third_party/ffmpeg/chromium/config/YOUR_BRAND/win/YOUR_ARCH/config.asm
  • src/third_party/ffmpeg/chromium/config/YOUR_BRAND/win/YOUR_ARCH/config.h

把下面幾個的值配成 1:

#define CONFIG_HEVC_DECODER 1
#define CONFIG_HEVC_DEMUXER 1
#define CONFIG_HEVC_PARSER 1

在這個文件添加一項:

  • src/third_party/ffmpeg/chromium/config/YOUR_BRAND/win/YOUR_ARCH/libavcodec/codec_list.c
&ff_hevc_decoder

同樣的添加一項:

  • src/third_party/ffmpeg/chromium/config/YOUR_BRAND/win/YOUR_ARCH/libavcodec/parser_list.c
&ff_hevc_parser

同樣的添加一項:

  • src/third_party/ffmpeg/chromium/config/YOUR_BRAND/win/YOUR_ARCH/libavformat/demuxer_list.c
&ff_hevc_demuxer
  • 修改文件 /build/config/features.gni
# Note: this flag is used by WebRTC which is DEPSed into Chrome. Moving it
# out of //build will require using the build_overrides directory.
- proprietary_codecs = is_chrome_branded || is_chromecast
+ proprietary_codecs = true
  • 修改文件 /media/media_options.gni
# Enable HEVC/H265 demuxing. Actual decoding must be provided by the
# platform. Enabled by default for Chromecast.
- enable_platform_hevc = proprietary_codecs && is_chromecast
+ enable_platform_hevc = true

去掉這個文件裏的一個判斷:

  • /media/BUILD.gn
if (proprietary_codecs && media_use_ffmpeg) {
- assert(
- ffmpeg_branding != "Chromium",
- "proprietary codecs and ffmpeg_branding set to Chromium are incompatible")
+ # assert(
+ # ffmpeg_branding != "Chromium",
+ # "proprietary codecs and ffmpeg_branding set to Chromium are incompatible")
}

修改文件:

  • /media/base/supported_types.cc
bool IsDefaultSupportedVideoType(const VideoType& type) {
//...
switch (type.codec) {
    // ....
    case kCodecH264:
  + case kCodecHEVC:
    case kCodecVP8:
    case kCodecTheora:
        return true;
    case kUnknownVideoCodec:
    case kCodecVC1:
    case kCodecMPEG2:
  - case kCodecHEVC:
    case kCodecDolbyVision:
        return false;
    // ...
}
  • 修改完成,在 src 文件夾裏執行如下命令編譯 release 版本。
gn gen out/release --args="is_component_build = false enable_nacl = false is_debug = false symbol_level = 0 blink_symbol_level = 0"
autoninja -C out/release chrome

大概喝 20 杯咖啡時間,就開始在瀏覽器裏欣賞 4K 高清 HEVC/H.265 的視頻了。畢竟是 C++ 實現,性能比 Web 端實現的軟解方案不知道高到哪去了。筆者機器 ryzen 3600 + 垃圾 Radeom 5700 顯卡表現:

4K 性能表現

不準確測試幀率測試:

內存和 CPU 佔用:

後續補充

GPU 加速測試

直接在 chromium 源碼修改一個最強大之處是,後續可以調用 GPU 加速,因爲筆者電腦顯卡不支持,目前在準備硬件,後續測試後更新性能表現。

實現 m3u8|flv & HEVC

這個難度較高,需要更多的技術投入和開發。

最後

修改 chromium 實現的方式並不具有普適性,但是對部分 B 端項目確實能收到奇效,性能表現足夠好用。

立足現在,展望未來,目前 HEVC 有專利問題,VP9 孤掌難鳴,AV1 又姍姍來遲,視頻編碼的新格局將會在下一代編碼技術中競爭共存。其實 HEVC 的下一代技術 VVC/H.266 以及 AV1 的 下一代 AV2 也被提上日程。之前 AVC 一家獨大的時代已經一去不復返,視頻編碼技術背後是一個非常大的競爭格局 (視頻多大的市場!),尤其具有代表性的是擁有硬件手機 Android + Chrome + Youtube 的 Google VS Apple,後者擁有硬件手機 + iOS + Safari + ?內容生態,加上各個視頻雲廠商、內容平臺、互聯網和短視頻等大佬,視頻編解碼技術後續發展甚至關係到互聯網格局和權力的更替!讓我們拭目以待。

原文鏈接: https://segmentfault.com/a/1190000020711813

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