作者| 阿里文娛無線開發專家 黃陂
優酷外掛字幕的目標是實現對全平臺和全機型的覆蓋,同時能支持 ass 之外的多種字幕格 式、支持字幕互動等多種創新玩法。本文將分享我們對外掛字幕的探索與實踐。
一、字幕的簡單科普
在開始之前,先科普一些字幕知識。一般而言,字幕分為硬字幕、軟字幕和外掛字幕。
硬字幕,或者叫內嵌字幕是視頻生產時就直接疊加到視頻畫面上的,字幕不可取消、不可編輯更改,不支持多語言及國際化。軟字幕是將字幕與視頻打包在一起,形成獨立於音頻和視 頻 track 流的單獨一路 subtitle track 流,整體打包在視頻文件中。
我們今天介紹的外掛字幕,是將字幕做成一個獨立文件,這些字幕文件可以有多種格式, 獨立下發及展示。優點是不破壞視頻畫面,可隨時根據需要更換字幕語言,並且可隨時編輯字 幕內容並重新生產發佈;缺點是由於字幕文件單獨存在,可能會有字幕缺失、字幕跟音視頻不 同步等問題;另外,為了確保端上播放必須有字幕,整個鏈路需要有複雜的硬字幕兜底處理邏輯。
二、優酷視頻的字幕現狀
1. 優酷字幕鏈路
目前優酷使用的外掛字幕採用的是 ass 格式,優酷字幕生產鏈路大致如下:
1)單獨生產一路字幕 ass 流,並隨同對應視頻 vid 一起入庫;
2)將字幕壓入視頻流,主要出於兩種考慮:主客端側有各種降級邏輯,為了保證用戶在端 側播放上字幕必須出現,所以必須有一路包含硬字幕的視頻流;OTT 等端還不能支持外掛字幕。
2. 優酷主客端的實現
如上圖所示,優酷的播放器內核支持多個播放實例,每個實例在播放鏈路上由三個主要部分構成:
1)數據源處理模塊(Sourcer Module):負責下載和解複用(demux);
2)解碼模塊(Decoder Module):對音頻、視頻等流的數據包進行解碼,得到圖像和聲音等 raw 數據;
3)消費者模塊(Consumer Module):同步處理,並做渲染。 目前優酷的外掛字幕是在優酷私有播放器播放內核裡實現的。為了對接優酷私有播放器架構,在內核里加了單獨一路 subtitle 的 pipeline。主要也分為以下三個模塊:
1)subtitle sourcer:負責字幕文件的下載;
2)subtitle decoder:負責字幕的解析;
3)subtitle consumer:根據上層傳下來的字幕配置信息,首先渲染成圖片,然後與當前時鐘做同步處理,並輸出給 openrender 去做合成和渲染。
優點:
1)能有效複用內核中公共模塊,比如時鐘獲取、利用 ffmpeg 實現對字幕的下載和解析等功能;
2)處理字幕特效比較靈活、高效。
缺點:
1)與優酷私有播放器播放內核緊密綁定,系統播放器無法支持外掛字幕;
2)與 openrender 耦合較緊,OTT 端無法支持外掛字幕;
3)目前只支持 ass 格式,可擴展性不好;
4)鏈路長,並且與播放內核 AliPlayer 耦合太緊,線上問題不好排查,維護成本高。
三、新架構實現
1.外掛字幕獨立化重構目標:
不區分播放器類型(系統播放器、私有播放器)、實現對全平臺(Android 主客、iOS 主客、 OTT 設備、mac 端等)和全機型的覆蓋,同時能支持 ass 之外的多種字幕格式、支持字幕互動等創新玩法。
2.模塊圖
新架構主要分為以下三個模塊:
1)Player SDK 除了之前與播放的處理邏輯外,加了字幕的三個模塊,包括:
a)subtitle cmd:與字幕引擎的交互模塊,主要是給字幕引擎下發命令。
b)subtitle msg handler:消息處理器,主要用來接收字幕引擎上拋的消息,包括每一幀對應 的字幕信息,以及出錯或其他消息上報,用戶上層做處理和埋點統計。
c)subtitle render:渲染模塊,主要用來渲染字幕信息。
2)Subtitle Engine
整個外掛字幕處理引擎,負責 subtitle 的核心處理邏輯,覆蓋除字幕渲染之外的所有功能。 後面會詳細介紹 Subtitle Engine。
3)Player Kernel
渲染分兩種實現方式:
a)上層渲染:把字幕信息回調給上層去做渲染,整個外掛字幕鏈路跟 Player Kernel 沒有任 何關係。這種場景下 Player Kernel 可以不用考慮在內;
b)openrender 渲染:需要先在 Subtitle Engine 側渲染成圖片,然後從與 openrender 對接的 middle ware 層獲取當前 pts,做同步之後輸出給 openrender 去做合成和渲染。這種場景下,字 幕的渲染在 openrender 裡面。
3. Subtitle Engine 詳圖
SubtitleEngine:整個外掛字幕處理引擎,負責 subtitle 的核心處理邏輯,覆蓋除字幕渲染之 外的所有功能。整個 Subtitle Engine 分為以下 4 大塊:
1)Msg Router 消息路由,主要負責:
a)從 Player SDK 到 Engine 的命令消息下發,包括所有的字幕消息控制邏輯,比如設置subtitle url、開始播放、暫停、恢復、停止、設置壓流廣告等等。
b)從 Engine 到 Player SDK 的數據消息上報,包括字幕信息的上報,錯誤消息、埋點信息 等的上報。
封裝這樣一個 Msg Router,我們就可以只實現幾個簡單的 API 就能實現所有功能及其擴展。 這樣能有效避免之前播放內核擴展的痛點,加一個 api 就得整個播放鏈路一路加下去,要修改 的地方非常多,不利於擴展。
2)Common:主要是一些公共功能的封裝,包括開關、配置,埋點信息,錯誤信息上報, tlog 等。
3)Sourcer:主要功能是點播、直播字幕文件的下載、緩存、解析。從上到下主要分為以下幾個模塊:
a)downloader:用於下載在線字幕文件;
b)cache:用於緩存處理邏輯。某一部劇的 ass 文件一般不會經常更新,我們在下載到本地 之後可以先緩存下來,下次用戶播同樣的劇可以直接使用本地的 ass 文件。當然,我們會有一 個時效及更新機制,會定期清掉本地的緩存,讓用戶去重新下載。本地的緩存文件也需要考慮 完整性檢測機制,來保證文件的完好無損;
c)sniffer:考慮到我們要支持多種外掛字幕格式,需要一個嗅探器去探測字幕格式;
d)parser:字幕解析模塊。不同格式需要有不同的 parser 實現;
e)track:對解析後字幕信息的封裝,包括字幕文本,開始時間 pts,持續時長,字體配置信息等。得到這樣一個 track 信息,consumer 就能獲取後去消費了。
4)Consumer:主要功能是實現與當前播放進度的同步輸出,各種與業務相關的邏輯都在 這裡實現。比如同步、seek、跳片頭、壓流廣告、倍速等等。
a)driver:Consumer 內部通過 driver 來驅動整個流程,driver 提供 PTS 與更新頻率來驅動Consumer 與 Sourcer 交互,獲取當前 PTS 下的字幕數據。pts 的獲取根據渲染方式的不同有兩種方式:從上層獲取(見箭頭(5));從 openrender 獲取(見箭頭(6))。
b)provider:根據從 driver 裡傳過來的當前 pts,去 sourcer 解析生成的 track 中獲取對應的 字幕行信息。
c)line data manager:基於行的字幕數據管理模塊。因單條字幕獲取頻率在 10~100 次/s,line data manager 內部通過緩存、預解析策略,降低與整體字幕數據的交互頻率,優化了單行字幕 信息獲取開銷。
d)subtitle line data:從 line data manager 我們獲取到對應的字幕行信息,包括字幕文本, 開始時間,持續時長,字體設置等信息。
e)兩種渲染方式:
方式一:openrender。我們需要 render to image,這一步只是通過 openrender 渲染時才需要。 我們先把 subtitle line data 渲染成一張圖片,然後輸出給 openrender 去做渲染(見箭頭(9));
方式二:上層渲染,我們在 consumer 流程中 driver->provider->line data manager->subtitle line data 獲取到字幕行信息,然後轉發給 msg router,再上拋給 Player SDK 去做渲染處理。
綜上,整個外掛字幕的處理流程就完成了。除了 option 之前通過 openrender 渲染是與私有 播放器內核掛鉤之外,其他模塊、包括通過上層渲染的所有鏈路都與播放器類型無關了。
四、展望
在新架構下外掛字幕功能的展望:外掛字幕能在新架構下快速鋪量,並且能有更多創新玩 法提供給我們的用戶。
1)快速鋪量:我們設計新架構不依賴於播放器類型、並且功能獨立,這有利於外掛字幕功 能在不同設備上的鋪量,也有利於後續的功能獨立開發和升級;
2)字體、顏色、大小調整;
3)非視頻畫面顯示:不依賴於視頻播放,聽劇模式下在鎖屏界面顯示字幕;
4)隨處顯示能力:可顯示在視頻畫面裡面,也可以顯示在視頻畫面外;
5)互動能力:字幕區域可拖動,可單擊,可滑動取詞,可互動;
6)各種字幕特效,可以做成類似於表情包,並且可以做成會員權益,給會員客戶帶來更大的價值。