開篇
炙手可熱,望而生畏的音視頻開發
時至今日,短視頻App可謂是如日中天,一片興興向榮。隨着短視頻的興起,音視頻開發也越來越受到重視,但是由於音視頻開發涉及知識面比較廣,入門門檻相對較高,讓許許多多開發者望而生畏。
爲什麼寫這一系列博文
雖然網上有很多的博文總結了音視頻打怪升級的路線,但是音視頻開發相關的知識都相對獨立,有講“音視頻解碼相關”的,有講“OpenGL相關”的,也有講“FFmpeg相關的”,但是對於新手來說,把所有的知識銜接串聯起來,並很好的理解所有的知識,卻是非常困難的。
本人在學習音視頻開發的過程中,深刻體會到了由於知識的分散,過渡斷層帶來的種種困惑和痛苦,因此,希望通過自己的理解,可以把音視頻開發相關的知識總結出來,並形成系列文章,循序漸進,剖析各個環節,一則對自己所學做一個總結和鞏固,二則希望可以幫助想入門音視頻開發的開發者小夥伴們。
【聲 明】
首先,這一系列文章均基於自己的理解和實踐,可能有不對的地方,歡迎大家指正。
其次,這是一個入門系列,涉及的知識也僅限於夠用,深入的知識網上也有許許多多的博文供大家學習了。
最後,寫文章過程中,會借鑑參考其他人分享的文章,會在文章最後列出,感謝這些作者的分享。
碼字不易,轉載請註明出處!
教程代碼:【Github傳送門】 |
---|
目錄
一、Android音視頻硬解碼篇:
- 1,音視頻基礎知識
- 2,音視頻硬解碼流程
- 3,音視頻播放:音視頻同步
- 4,音視頻解封和封裝:生成一個MP4
二、使用OpenGL渲染視頻畫面篇
- 1,初步瞭解OpenGL ES
- 2,使用OpenGL渲染視頻畫面
- 3,OpenGL渲染多視屏,實現畫中畫
- 4,深入瞭解OpenGL之EGL
- 5,OpenGL FBO數據緩衝區
- 6,Android音視頻硬編碼:生成一個MP4
三、Android FFmpeg音視頻解碼篇
- 1,FFmpeg so庫編譯
- 2,Android 引入FFmpeg
- 3,Android FFmpeg視頻解碼播放
- 4,Android FFmpeg+OpenSL ES音頻解碼播放
- 5,Android FFmpeg+OpenGL ES播放視頻
- 6,Android FFmpeg簡單合成MP4:視屏解封與重新封裝
- 7,Android FFmpeg視頻編碼
本文你可以瞭解到
作爲開篇的文章,我們先來看看音視頻由什麼構成的,以及一些常見的術語和概念。
一、視頻是什麼?
動畫書
不知道大家小時候是否玩過一種動畫小人書,連續翻動的時候,小人書的畫面就會變成一個動畫,類似現在的gif格式圖片。
本來是一本靜態的小人書,通過翻動以後,就會變成一個有趣的小動畫,如果畫面夠多,翻動速度夠快的話,這其實就是一個小視頻。
而視頻的原理正是如此,由於人類眼睛的特殊結構,畫面快速切換時,畫面會有殘留,感覺起來就是連貫的動作。所以,視頻就是由一系列圖片構成的。
視頻幀
幀,是視頻的一個基本概念,表示一張畫面,如上面的翻頁動畫書中的一頁,就是一幀。一個視頻就是由許許多多幀組成的。
幀率
幀率,即單位時間內幀的數量,單位爲:幀/秒 或fps(frames per second)。如動畫書中,一秒內包含多少張圖片,圖片越多,畫面越順滑,過渡越自然。
幀率的一般以下幾個典型值:
24/25 fps:1秒 24/25 幀,一般的電影幀率。
30/60 fps:1秒 30/60 幀,遊戲的幀率,30幀可以接受,60幀會感覺更加流暢逼真。
85 fps以上人眼基本無法察覺出來了,所以更高的幀率在視頻裏沒有太大意義。
色彩空間
這裏我們只講常用到的兩種色彩空間。
- RGB
RGB的顏色模式應該是我們最熟悉的一種,在現在的電子設備中應用廣泛。通過R G B三種基礎色,可以混合出所有的顏色。
- YUV
這裏着重講一下YUV,這種色彩空間並不是我們熟悉的。這是一種亮度與色度分離的色彩格式。
早期的電視都是黑白的,即只有亮度值,即Y。有了彩色電視以後,加入了UV兩種色度,形成現在的YUV,也叫YCbCr。
Y:亮度,就是灰度值。除了表示亮度信號外,還含有較多的綠色通道量。
U:藍色通道與亮度的差值。
V:紅色通道與亮度的差值。
採用YUV有什麼優勢呢?
人眼對亮度敏感,對色度不敏感,因此減少部分UV的數據量,人眼卻無法感知出來,這樣可以通過壓縮UV的分辨率,在不影響觀感的前提下,減小視頻的體積。
RGB和YUV的換算
Y = 0.299R + 0.587G + 0.114B
U = -0.147R - 0.289G + 0.436B
V = 0.615R - 0.515G - 0.100B
——————————————————
R = Y + 1.14V
G = Y - 0.39U - 0.58V
B = Y + 2.03U
二、音頻是什麼?
音頻數據的承載方式最常用的是脈衝編碼調製,即PCM。
在自然界中,聲音是連續不斷的,是一種模擬信號,那怎樣才能把聲音保存下來呢?那就是把聲音數字化,即轉換爲數字信號。
我們知道聲音是一種波,有自己的振幅和頻率,那麼要保存聲音,就要保存聲音在各個時間點上的振幅。
而數字信號並不能連續保存所有時間點的振幅,事實上,並不需要保存連續的信號,就可以還原到人耳可接受的聲音。
根據奈奎斯特採樣定理:爲了不失真地恢復模擬信號,採樣頻率應該不小於模擬信號頻譜中最高頻率的2倍。
根據以上分析,PCM的採集步驟分爲以下步驟:
模擬信號->採樣->量化->編碼->數字信號
採樣率和採樣位數
採樣率,即採樣的頻率。
上面提到,採樣率要大於原聲波頻率的2倍,人耳能聽到的最高頻率爲20kHz,所以爲了滿足人耳的聽覺要求,採樣率至少爲40kHz,通常爲44.1kHz,更高的通常爲48kHz。
採樣位數,涉及到上面提到的振幅量化。波形振幅在模擬信號上也是連續的樣本值,而在數字信號中,信號一般是不連續的,所以模擬信號量化以後,只能取一個近似的整數值,爲了記錄這些振幅值,採樣器會採用一個固定的位數來記錄這些振幅值,通常有8位、16位、32位。
位數 | 最小值 | 最大值 |
---|---|---|
8 | 0 | 255 |
16 | -32768 | 32767 |
32 | -2147483648 | 2147483647 |
位數越多,記錄的值越準確,還原度越高。
最後就是編碼了。由於數字信號是由0,1組成的,因此,需要將幅度值轉換爲一系列0和1進行存儲,也就是編碼,最後得到的數據就是數字信號:一串0和1組成的數據。
整個過程如下:
[圖片上傳失敗…(image-8c5d9f-1568988187211)]
聲道數
聲道數,是指支持能不同發聲(注意是不同聲音)的音響的個數。
單聲道:1個聲道
雙聲道:2個聲道
立體聲道:默認爲2個聲道
立體聲道(4聲道):4個聲道
碼率
碼率,是指一個數據流中每秒鐘能通過的信息量,單位bps(bit per second)
碼率 = 採樣率 * 採樣位數 * 聲道數
三、爲什麼要編碼
這裏的編碼和上面音頻中提到的編碼不是同個概念,而是指壓縮編碼。
我們知道,在計算機的世界中,一切都是0和1組成的,音頻和視頻數據也不例外。由於音視頻的數據量龐大,如果按照裸流數據存儲的話,那將需要耗費非常大的存儲空間,也不利於傳送。而音視頻中,其實包含了大量0和1的重複數據,因此可以通過一定的算法來壓縮這些0和1的數據。
特別在視頻中,由於畫面是逐漸過渡的,因此整個視頻中,包含了大量畫面/像素的重複,這正好提供了非常大的壓縮空間。
因此,編碼可以大大減小音視頻數據的大小,讓音視頻更容易存儲和傳送。
四、視頻編碼
視頻編碼格式
視頻編碼格式有很多,比如H26x系列和MPEG系列的編碼,這些編碼格式都是爲了適應時代發展而出現的。
其中,H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)國際電傳視訊聯盟主導
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的組織)主導。
當然,他們也有聯合制定的編碼標準,那就是現在主流的編碼格式H264,當然還有下一代更先進的壓縮編碼標準H265。
H264編碼簡介
H264是目前最主流的視頻編碼標準,所以我們後續的文章中主要以該編碼格式爲基準。
H264由ITU和MPEG共同定製,屬於MPEG-4第十部分內容。
由於H264編碼算法十分複雜,不是一時半刻能夠講清楚的,也不在本人目前的能力範圍內,所以這裏只簡單介紹在日常開發中需要瞭解到的概念。實際上,視頻的編碼和解碼部分通常由框架(如Android硬解/FFmpeg)完成,一般的開發者並不會接觸到。
- 視頻幀
我們已經知道,視頻是由一幀一幀畫面構成的,但是在視頻的數據中,並不是真正按照一幀一幀原始數據保存下來的(如果這樣,壓縮編碼就沒有意義了)。
H264會根據一段時間內,畫面的變化情況,選取一幀畫面作爲完整編碼,下一幀只記錄與上一幀完整數據的差別,是一個動態壓縮的過程。
在H264中,三種類型的幀數據分別爲
I幀:幀內編碼幀。就是一個完整幀。
P幀:前向預測編碼幀。是一個非完整幀,通過參考前面的I幀或P幀生成。
B幀:雙向預測內插編碼幀。參考前後圖像幀編碼生成。B幀依賴其前最近的一個I幀或P幀及其後最近的一個P幀。
- 圖像組:GOP和關鍵幀:IDR
全稱:Group of picture。指一組變化不大的視頻幀。
GOP的第一幀成爲關鍵幀:IDR
IDR都是I幀,可以防止一幀解碼出錯,導致後面所有幀解碼出錯的問題。當解碼器在解碼到IDR的時候,會將之前的參考幀清空,重新開始一個新的序列,這樣,即便前面一幀解碼出現重大錯誤,也不會蔓延到後面的數據中。
注:關鍵幀都是I幀,但是I幀不一定是關鍵幀
- DTS與PTS
DTS全稱:Decoding Time Stamp。標示讀入內存中數據流在什麼時候開始送入解碼器中進行解碼。也就是解碼順序的時間戳。
PTS全稱:Presentation Time Stamp。用於標示解碼後的視頻幀什麼時候被顯示出來。
在沒有B幀的情況下,DTS和PTS的輸出順序是一樣的,一旦存在B幀,PTS和DTS則會不同。
- 幀的色彩空間
前面我們介紹了RGB和YUV兩種圖像色彩空間。H264採用的是YUV。
YUV存儲方式分爲兩大類:planar 和 packed。
planar:先存儲所有Y,緊接着存儲所有U,最後是V;
packed:每個像素點的 Y、U、V 連續交叉存儲。
planar如下:
packed如下:
不過pakced存儲方式已經非常少用,大部分視頻都是採用planar存儲方式。
上面說過,由於人眼對色度敏感度低,所以可以通過省略一些色度信息,即亮度共用一些色度信息,進而節省存儲空間。因此,planar又區分了以下幾種格式: YUV444、 YUV422、YUV420。
YUV 4:4:4採樣,每一個Y對應一組UV分量。
YUV 4:2:2採樣,每兩個Y共用一組UV分量。
YUV 4:2:0採樣,每四個Y共用一組UV分量。
其中,最常用的就是YUV420。
- YUV420格式存儲方式
YUV420屬於planar存儲方式,但是又分兩種類型:
YUV420P:三平面存儲。數據組成爲YYYYYYYYUUVV(如I420)或YYYYYYYYVVUU(如YV12)。
YUV420SP:兩平面存儲。分爲兩種類型YYYYYYYYUVUV(如NV12)或YYYYYYYYVUVU(如NV21)
關於H264的編碼算法和數據結構,涉及的知識和篇幅很多(如網絡抽象層NAL、SPS、PPS),本文不再深入細說,網上也有比較多的教程講解,有興趣可以自行深入學習。
五、音頻編碼
音頻編碼格式
原始的PCM音頻數據也是非常大的數據量,因此也需要對其進行壓縮編碼。
和視頻編碼一樣,音頻也有許多的編碼格式,如:WAV、MP3、WMA、APE、FLAC等等,音樂發燒友應該對這些格式非常熟悉,特別是後兩種無損壓縮格式。
但是,我們今天的主角不是他們,而是另外一個叫AAC的壓縮格式。
AAC是新一代的音頻有損壓縮技術,一種高壓縮比的音頻壓縮算法。在MP4視頻中的音頻數據,大多數時候都是採用AAC壓縮格式。
AAC編碼簡介
AAC格式主要分爲兩種:ADIF、ADTS。
ADIF:Audio Data Interchange Format。 音頻數據交換格式。這種格式的特徵是可以確定的找到這個音頻數據的開始,不需進行在音頻數據流中間開始的解碼,即它的解碼必須在明確定義的開始處進行。這種格式常用在磁盤文件中。
ADTS:Audio Data Transport Stream。 音頻數據傳輸流。這種格式的特徵是它是一個有同步字的比特流,解碼可以在這個流中任何位置開始。它的特徵類似於mp3數據流格式。
ADTS可以在任意幀解碼,它每一幀都有頭信息。ADIF只有一個統一的頭,所以必須得到所有的數據後解碼。且這兩種的header的格式也是不同的,目前一般編碼後的都是ADTS格式的音頻流。
ADIF數據格式:
header | raw_data |
---|
ADTS 一幀 數據格式(中間部分,左右省略號爲前後數據幀):
AAC內部結構也不再贅述,可以參考AAC 文件解析及解碼流程
六、音視頻容器
細心的讀者可能已經發現,前面我們介紹的各種音視頻的編碼格式,沒有一種是我們平時使用到的視頻格式,比如:mp4、rmvb、avi、mkv、mov…
沒錯,這些我們熟悉的視頻格式,其實是包裹了音視頻編碼數據的容器,用來把以特定編碼標準編碼的視頻流和音頻流混在一起,成爲一個文件。
例如:mp4支持H264、H265等視頻編碼和AAC、MP3等音頻編碼。
mp4是目前最流行的視頻格式,在移動端,一般將視頻封裝爲mp4格式。
七、硬解碼和軟解碼
硬解和軟解的區別
我們在一些播放器中會看到,有硬解碼和軟解碼兩種播放形式給我們選擇,但是我們大部分時候並不能感覺出他們的區別,對於普通用戶來說,只要能播放就行了。
那麼他們內部究竟有什麼區別呢?
在手機或者PC上,都會有CPU、GPU或者解碼器等硬件。通常,我們的計算都是在CPU上進行的,也就是我們軟件的執行芯片,而GPU主要負責畫面的顯示(是一種硬件加速)。
所謂軟解碼,就是指利用CPU的計算能力來解碼,通常如果CPU的能力不是很強的時候,一則解碼速度會比較慢,二則手機可能出現發熱現象。但是,由於使用統一的算法,兼容性會很好。
硬解碼,指的是利用手機上專門的解碼芯片來加速解碼。通常硬解碼的解碼速度會快很多,但是由於硬解碼由各個廠家實現,質量參差不齊,非常容易出現兼容性問題。
Android平臺的硬解碼
終於來到有關Android的部分了,作爲本文的結尾,也算是爲下一篇文章開一個頭。
MediaCodec 是Android 4.1(api 16)版本引入的編解碼接口,是所有想在Android上開發音視頻的開發人員繞不開的坑。
由於Android碎片化嚴重,雖然經過多年的發展,Android硬解已經有了很大改觀,但實際上各個廠家實現不同, 還是會有一些意想不到的坑。
相對於FFmpeg,Android原生硬解碼還是相對容易入門一些,所以接下來,我將會從MediaCodec入手,講解如何實現視頻的編解碼,以及引入OpenGL實現對視頻的編輯,最後才引入FFmpeg來實現軟解,算是一個比較常規的音視頻開發入門流程吧。