點播的首屏秒開方案也可以先參考一下直播中的首屏加載優化。
前置 metadata
播放器在網絡點播場景下去請求 MP4 視頻數據,需要先獲取到文件的 metadata,解析出該文件的編碼、幀率等信息后才能開始邊下邊播。如果 MP4 的 metadata 數據塊被編碼在文件尾部,這種情況會導致播放器只有下載完整個文件后才能成功解析并播放這個視頻。對于這種視頻,我們最好能夠在服務端將其重新編碼,將 metadata 數據塊轉移到靠近文件頭部的位置,保證播放器在線請求時能較快播放。比如 FFmpeg 的下列命令就可以支持這個操作:
ffmpeg -i bad.mp4 -movflags faststart good.mp4
優化 FFmpeg 的播放參數
基于 FFmpeg 實現的播放器,在播放視頻時都會調用到一個 avformat_find_stream_info
(libavformat/utils.c) 函數,該函數的作用是讀取一定長度的碼流數據,來分析碼流的基本信息,為視頻中各個媒體流的 AVStream 結構體填充好相應的數據。這個函數中做了查找合適的解碼器、打開解碼器、讀取一定的音視頻幀數據、嘗試解碼音視頻幀等工作,基本上完成了解碼的整個流程。在不清楚視頻數據的格式又要做到較好的兼容性時,這個過程是比較耗時的,從而會影響到播放器首屏秒開。
在外部可以通過設置 probesize
和 analyzeduration
兩個參數來控制該函數讀取的數據量大小和分析時長為比較小的值來降低 avformat_find_stream_info
的耗時,從而優化播放器首屏秒開。但是,需要注意的是這兩個參數設置過小時,可能會造成預讀數據不足,無法解析出碼流信息,從而導致播放失敗、無音頻或無視頻的情況。所以,在服務端對視頻格式進行標準化轉碼,從而確定視頻格式,進而再去推算 avformat_find_stream_info
分析碼流信息所兼容的最小的 probesize
和 analyzeduration
,就能在保證播放成功率的情況下最大限度地區優化首屏秒開。
在我們能控制視頻格式達到標準化后,我們可以直接修改 avformat_find_stream_info
的實現邏輯,針對該視頻格式做優化,進而優化首屏秒開。比如,你可以試試將函數中用到的一個變量 fps_analyze_framecount
初始化為 0 試試效果。
甚至,我們可以進一步直接去掉 avformat_find_stream_info
這個過程,自定義完成解碼環境初始化。參見:VLC優化(1) avformat_find_stream_info 接口延遲降低 和 FFMPEG avformat_find_stream_info 替換。
對 avformat_find_stream_info
代碼的分析,還可以看看這里:FFmpeg源代碼簡單分析:avformat_find_stream_info()。
選擇合適的緩沖策略
在點播場景下,為了減少播放過程中的卡頓,通常會緩沖一定的數據后再解碼播放,這是一種播放策略。
為了加快首屏播放速度,也可以選擇降低首次緩沖的數據量。甚至在第一幀沒有渲染出來的情況下,不做任何緩沖,有數據就直接塞給解碼器解碼播放。
在 iOS 平臺上,使用系統的 AVPlayer 時,屬性 automaticallyWaitsToMinimizeStalling
就是控制播放器緩沖策略的。當該值為 YES 時,AVPlayer 會努力嘗試延遲開始播放,加載足夠的數據來保證整個播放過程中盡量卡頓最少。這個接口在 iOS 10 及以上版本才開放,在 iOS 10 之前的版本,在播放 HLS 這種流媒體視頻時,效果如同 automaticallyWaitsToMinimizeStalling
為 YES,播放基于文件的視頻資源,包括通過網絡傳輸的網絡視頻文件,則效果如同 automaticallyWaitsToMinimizeStalling
為 NO。
使用 HTTPDNS 加快建連
在現在的網絡視頻播放場景中,對視頻資源的訪問通常都要經過 CDN 網絡進行內容分發和調度。如果調度得當,將訪問資源時的節點調度到離得近、速度快的節點,會大大加快首屏播放。
這時候我們可以使用與 CDN 網絡配套的 HTTPDNS 服務。HTTPDNS 使用 HTTP 協議進行域名解析,代替現有基于 UDP 的 DNS 協議,域名解析請求直接發送到相應的 HTTPDNS 服務器,從而繞過運營商的 Local DNS,能夠避免 Local DNS 造成的域名劫持問題和調度不精準問題。
以 iOS 上的 AVPlayer 為例,當使用 HTTPDNS 時,可以用視頻資源 URL 對應的 Host 向 HTTPDNS 請求節點 IP,然后用節點 IP 替換 URL 中的 Host 部分,再在 HTTP Header 里設置原 Host。這樣即可通過 IP 直連的方式訪問 HTTPDNS 返回的較優節點。
示例代碼大致如下:
// 假設原視頻 URL 是:http://www.example.com/abc.mp4
// 假設從 HTTPDNS 服務獲取的 www.example.com 這個 Host 對應的 IP 是:192.168.1.1
// 那么處理后的 URL 是:http://192.168.1.1/abc.mp4
NSMutableDictionary *headers = [NSMutableDictionary dictionary];
[headers setObject:@"www.example.com" forKey:@"Host"];
NSURL *videoURL = [NSURL URLWithString:@"http://192.168.1.1/abc.mp4"];
AVAsset *asset = [AVURLAsset URLAssetWithURL:videoURL options:@{@"AVURLAssetHTTPHeaderFieldsKey": headers}];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
這種方案在使用 HTTPS 時,是會失敗的。因為 HTTPS 在證書驗證的過程,會出現 domain 不匹配導致 SSL/TLS 握手不成功。這時候的方案參考 HTTPS(含SNI)業務場景“IP直連”方案說明 和 iOS HTTPS SNI 業務場景“IP直連”方案說明。
提升 CDN 命中率
通常 CDN 的緩存命中策略是與訪問資源的 URL 有關。如果命中策略是 URL 全匹配,那么就要盡量保證 URL 的變化性較低。比如:盡量不要在 URL 的參數中帶上隨機性的值,這樣會造成 CDN 緩存命中下降,從而導致不斷回源,這樣訪問資源耗時也就增加了。當然這樣就失去了一些靈活性。
CDN 方面其實可以提供一些配置策略,比如:根據域名可配置對其緩存命中策略忽略掉某些參數。這樣就能保證一定的靈活性了。