做爲移動開發者,大多數時候會須要接觸到音視頻相關的開發,而其實嚴格意義上我也並非專職的音視頻開發工程師,只是在 2016 由於業務須要接觸到音視頻相關的領域,而開源的 GSYVideoPlayer
剛好火起來以後,爲了解決系列問題成了「半桶水」的音視頻開發工程師。後端
在維護 GSYVideoPlayer
的這幾年裏,我發現不少開發者對於音視頻領域相關的基本概念仍是不清楚,因此我也常常能夠收穫這樣的 issue:緩存
「爲什 麼xxx 能夠播而 GSY 不能播?」bash
「我兩個視頻都是 mp4 爲何其中一個播放不了?」網絡
「爲何緩衝過的視頻 seek 完還要從新請求數據?」ide
「爲何播放有黑邊?」佈局
「····」ui
而這些其實都是音視頻開發過程當中的常識性問題,因此本篇將經過基本概念、常見問題、應用場景來科普音視頻開發的基礎知識。阿里雲
首先,以下圖所示是一個 .MOV
的視頻文件,能夠看到更多信息欄裏編碼器有 AAC
、HEVC
,而這個就是視頻的音頻編碼和視頻編碼,而 MOV
其實就是封裝協議,這其實就是咱們接下來要介紹的基本概念。編碼
通常狀況下,視頻流從加載都準備播放是須要通過解協議、解封裝、解編碼這樣的過程,其中協議指的就是流媒體協議;封裝是的是視頻的封裝格式;編碼又分爲視頻編碼和音頻編碼。spa
協議通常有 HTTP
、RTSP
、RTMP
等,咱們就最多見的就是 HTTP
網絡協議,而 RTSP
和 RTMP
通常用於直播流或支持帶有控制信令的常見,好比遠程監控。
視頻封裝協議指的是咱們常見的 MP4
、AVI
、RMVB
、MKV
、TS
、FLV
等常見後綴格式,它們所表示的就是多媒體的封裝協議,就是在傳輸過程當中把音頻和視頻打包都一塊兒的封裝,因此播放前是須要把這部份內容解開,提取出對應音頻編碼和視頻編碼。
因此若是之後有人問你,你是視頻編碼是什麼,專業的你不能再回答 「個人視頻編碼是 MP4」 這樣的回覆喲。
音頻編碼指的是音頻數據的編碼方式,常見的如:MP3
、 PCM
、WAV
、AAC
、AC-3
等,由於音頻的原始數據大小通常不適合直接傳入,好比原始大小通常能夠按照採樣率 * 聲道數 * 樣本格式
去計算,假設前面那個 MOV
的音頻採樣率是 44100 、樣本格式是 16 bit 、單聲道、24 秒,那麼它原始音頻大小應該是
44100 * 16 * 1 * 24 / 8 ≈ 2MB
而實際將音頻信息提取出來的大小,以下圖大概只有 200 多K,這就是音頻編碼的做用。
因此通常都會音頻傳輸會採用各類編碼格式進行壓縮和去冗餘,其中好比 WAV
/PCM
編碼的音頻質量比較好,可是體積會比較大;MP3
有損壓縮能在音頻質量還能夠的狀況下壓縮音頻的體積;AAC
也是有損壓縮,可是又有分有 LC-AAC
、HE-AAC
等。
視頻編碼指的就是畫面圖像的編碼壓縮方式,通常有 H263
、H264
、HEVC
(H265
)、MPEG-2
、MPEG-4
等,其中H264
是目前比較常見的編碼方式。
一般狀況下咱們理解的畫面是 RGB 組合出來,而目前視頻領域可能更多使用 YUV 格式,其中 Y 表示的是亮度(灰度),而 U 和 V表示的是色度(飽和度)。
YUV 是對 RGB 的特殊處理和疊加來獲取顏色,好比 YUV420 能夠理解對色度以 2:1 的抽樣率進行存儲,而後亮度透過色度來顯示畫面,更多 YUV 的這裏就不展開討論,而爲何使用 YUV 其中有一點因素就是爲了兼容之前的黑白電視。
爲何不直接用原始 YUV ?這裏假設上面的 MOV 視頻直接使用 YUV420 的格式,那麼一幀的大小就會是:
1080 * 1920 * 1 + 1080 * 1920 * 0.5 = 2.9MB
若是在這個基礎上,算上幀率(30)和一個視頻的時長(一小時),那一部視頻原始大小就會是天文數字,這樣的狀況明顯不符合網絡傳輸,因此纔有了視頻編碼用於壓縮圖像。
在視頻壓縮裏,又有幾個概念須要知道,好比:
因此 I 幀是很關鍵的存在,壓縮 I 幀就能夠很容易壓制掉空間的大小,而壓縮 P/B 幀能夠壓縮掉時間上的冗餘信息 。因此在視頻 seek 的時候,I 幀很關鍵,若是視頻 seek 以後發生往前的跳動,那極可能就是你的視頻壓縮得太厲害了。
二、還有一個叫 IDR 幀的概念,由於 H264 採用的是多幀預測,致使 I 幀不能做爲獨立的觀察條件,因此多出一個叫 IDR 幀的特殊 I 幀用於參考,IDR 幀最關鍵的概念就是:在解碼器過程當中一旦收到 IDR 幀,就會當即清空參考幀緩衝區,並將IDR幀做爲被參考幀。
三、在視頻解碼裏還有一個叫 DTS(Decoding Time Stamp) 和 PTS(Presentation Time Stamp)的存在,DTS主要用於視頻的解碼,PTS主要用於在解碼階段對視頻進行同步和輸出。
由於視頻包裏面數據解碼不是連續的,而是須要經過解碼數據源獲得的 DTS,才 決定以包應該在何時被解碼,而獲得的PTS 決定了解碼後的圖片何時被繪製。
首先說一個常常被問的問題:ffmpeg 全稱是 Fast Forward Mpeg ,因此讀法爲 (ef,ef,'em,peg)
,通常狀況下 ffmpeg 使用的是軟解碼,也便是純 CPU 解碼;而使用平臺的 MediaCodec
播放的是硬解碼,也就是支持 GPU 協助。
問題1:「爲何同一個視頻機器A能夠播機器B不能夠?」
這個問題很大可能就是使用了 MediaCodec
的硬解碼播放,不一樣手機和系統版本,對於硬解碼的支持是不同的。
問題2:「爲何都是 ffmpeg 播放,vlc 能夠播放,ijkplayer 卻不行?」
這是由於 ffmpeg 是支持根據配置打包的,由於不少時候你並不須要那麼多,好比在 configure
文件中打開和關閉某些格式的支持來達到按需打包的目的,因此一樣是 ffmpeg 不一樣項目打包支持的程度可能都不一樣。
支持wav
--enable-libwavpack
--enable-muxer=wav
--enable-demuxer=wav
--enable-decoder=wavpack
--enable-encoder=wavpack
--enable-decoder=wav
--enable-encoder=wav
--enable-encoder=pcm_s16le
--enable-decoder=pcm_s16le
--enable-encoder=pcm_u8
--enable-decoder=pcm_u8
--enable-muxer=pcm_u8
--enable-demuxer=pcm_u8
支持mp2
--enable-encoder=mp2
--enable-decoder=mp2
--enable-muxer=mp2
--enable-decoder=mp2float
--enable-encoder=mp2fixed
支持 h265
--enable-decoder=hevc
複製代碼
問題3:「爲何個人視頻緩衝了,在 seek 以後還須要從新請求?」
這就須要解釋緩存和緩衝的區別:
緩衝:就像在倒垃圾的時候,不可能一有垃圾立刻跑去垃圾堆倒,而是先把垃圾倒到垃圾桶,垃圾桶滿了再一塊兒倒到垃堆。由於緩衝是在內存中,不可在內存中把整個視頻都緩衝進去,因此通常狀況下你看到的緩衝都是一段一段的臨時數據,一個緩衝塊是處於不斷地加載又不斷清除的過程。
緩存: 緩存的解釋就簡單多了,就是把視頻在播放的時候一邊下載到本地,這樣在緩存區域內的數據就不須要發生二次請求。
問題4:「爲何個人視頻在拖拽以後會出現跳動?」
其實前面已經解釋過了,這和視頻的關鍵幀有關係,同時也和 ffmpeg 選擇的兼容策略有關係,好比使用 -accurate_seek
可讓位於跳轉點和 position 之間的額外部分將被解碼而且丟棄,對應 ijk 中就是 enable-accurate-seek
的配置。
問題5:「爲何個人視頻會出現音視頻不一樣步?」
首選肯定你的播放器使用的音視頻同步協議是什麼,好比 ijkplayer 是使用音頻做爲同步時鐘,若是在 ijkplayer 裏 在出現音視頻不一樣步,那麼極可能就是視頻的碼率或者幀率過高,能夠嘗試使用使用 framedrop
丟幀或者開啓硬解碼支持。
問題6:「爲何個人視頻會出現大小和方向不對?」
通常狀況下視頻信息裏是帶有旋轉角度的,好比 Android 手機上錄製的視頻就可能帶有旋轉角度,因此在佈局和繪製時須要把旋轉角度如: 270,90 這樣的角度考慮上。
另外視頻在獲取大小還會有 Width Height Ratio
的信息也就是寬高比,例如這個信息在 ijkplayer 上是以 videoSarNum / videoSarDen
獲得的,只有把寬高比和視頻的寬高一塊兒計算,才能獲取到真正的展現寬高。
問題7:「爲何個人視頻會出現黑邊?」
這個問題其實就是常識性問題,面對不一樣尺寸不一樣分辨率的平臺,視頻顯示是根據你給定的 Surface
大小去進行顯示,因此你能夠選擇使用拉伸、裁剪、適應高度、適應寬度等不一樣佈局模式去配置你的繪製控件,這樣就能夠達到你須要的控制黑邊的場景。
諸如此類的問題還有 「如何獲取某個時間戳的圖像」、「如何同時播放幾個視頻」、「如何實現播放濾鏡」、「如何實現倍速播放」 等問題,這裏就不一一展開,感興趣的能夠去 GSYVideoPlayer
的 issue 或者搜索相關的 ffmpeg 實現。
最後講一下音視頻開發的使用場景,爲何要說這個呢?
由於不少時候開發者可能覺得「不就是接個播放器 SDK 放個 Url 的功夫嗎?」 其實還真不是,作過音視頻開發的應該都深有體會。
一、首先在作音視頻開發時,要肯定好本身須要支持的封裝協議、視頻編碼、音頻編碼格式,由於編碼格式千萬種,通常狀況下你不可能全都支持,因此首先要在需求內肯定好須要支持的格式範圍。
二、若是存在用戶自主上傳視頻的場景,最好還要在服務端提供轉格式與轉碼率等功能。由於在服務端判斷視頻格式並轉碼能夠規範編碼統一,這可以減小客戶端端由於編解碼失敗沒法播放的問題;另外提供同一視頻不一樣碼率的連接,能夠在不一樣手機型號和系統上可以擁有更好的播放體驗,減小前面說過的由於碼率過高出現音視頻不一樣步或者卡頓的問題。
相似功能在阿里雲和騰訊雲都支持。
三、在網絡播放中存在不少場景,好比播放過程當中網絡環境出現變化,是從 4G 轉化爲 Wifi 仍是從 Wifi 轉到了 4G 的場景 ,這裏面涉及到兩個點:第一是網絡環境發生改變,那麼本來的拉流通道其實已經斷開,這時候須要從新啓動一個新的鏈接來替換舊的播放內核,才能實現繼續播放;第二就是 Wifi 到 4G 之間的環境發生改變時,須要給用戶提示並肯定是否執行後續操做。
四、還有好比當視頻畫面須要從列表切換到詳情頁,須要從本來的容器切換到另一個可渲染容器時,須要在播放內核不暫停的狀況下去設置不一樣的 Surface
來達到切換的目的;片頭廣告的播放與視頻內容的預加載須要兩個不一樣的請求處理等等的場景。
視頻播放場景涉及先後端的數據交流,還有用戶環境場景的變換和業務需求的迭代,若是你老闆和那你說:
「就按照 bilibili 通常作一個視頻播放就行了」
相信我,那你的坑纔剛剛開始。