
開篇
炙手可熱,望而生畏的音視頻開發(fā)
時(shí)至今日,短視頻App可謂是如日中天,一片興興向榮。隨著短視頻的興起,音視頻開發(fā)也越來越受到重視,但是由于音視頻開發(fā)涉及知識(shí)面比較廣,入門門檻相對(duì)較高,讓許許多多開發(fā)者望而生畏。
為什么寫這一系列博文
雖然網(wǎng)上有很多的博文總結(jié)了音視頻打怪升級(jí)的路線,但是音視頻開發(fā)相關(guān)的知識(shí)都相對(duì)獨(dú)立,有講“音視頻解碼相關(guān)”的,有講“OpenGL相關(guān)”的,也有講“FFmpeg相關(guān)的”,但是對(duì)于新手來說,把所有的知識(shí)銜接串聯(lián)起來,并很好的理解所有的知識(shí),卻是非常困難的。
本人在學(xué)習(xí)音視頻開發(fā)的過程中,深刻體會(huì)到了由于知識(shí)的分散,過渡斷層帶來的種種困惑和痛苦,因此,希望通過自己的理解,可以把音視頻開發(fā)相關(guān)的知識(shí)總結(jié)出來,并形成系列文章,循序漸進(jìn),剖析各個(gè)環(huán)節(jié),一則對(duì)自己所學(xué)做一個(gè)總結(jié)和鞏固,二則希望可以幫助想入門音視頻開發(fā)的開發(fā)者小伙伴們。
【聲 明】
首先,這一系列文章均基于自己的理解和實(shí)踐,可能有不對(duì)的地方,歡迎大家指正。
其次,這是一個(gè)入門系列,涉及的知識(shí)也僅限于夠用,深入的知識(shí)網(wǎng)上也有許許多多的博文供大家學(xué)習(xí)了。
最后,寫文章過程中,會(huì)借鑒參考其他人分享的文章,會(huì)在文章最后列出,感謝這些作者的分享。
碼字不易,轉(zhuǎn)載請(qǐng)注明出處!
| 教程代碼:【Github傳送門】 |
|---|
目錄
一、Android音視頻硬解碼篇:
- 1,音視頻基礎(chǔ)知識(shí)
- 2,音視頻硬解碼流程:封裝基礎(chǔ)解碼框架
- 3,音視頻播放:音視頻同步
- 4,音視頻解封和封裝:生成一個(gè)MP4
二、使用OpenGL渲染視頻畫面篇
- 1,初步了解OpenGL ES
- 2,使用OpenGL渲染視頻畫面
- 3,OpenGL渲染多視頻,實(shí)現(xiàn)畫中畫
- 4,深入了解OpenGL之EGL
- 5,OpenGL FBO數(shù)據(jù)緩沖區(qū)
- 6,Android音視頻硬編碼:生成一個(gè)MP4
三、Android FFmpeg音視頻解碼篇
- 1,F(xiàn)Fmpeg so庫編譯
- 2,Android 引入FFmpeg
- 3,Android FFmpeg視頻解碼播放
- 4,Android FFmpeg+OpenSL ES音頻解碼播放
- 5,Android FFmpeg+OpenGL ES播放視頻
- 6,Android FFmpeg簡(jiǎn)單合成MP4:視屏解封與重新封裝
- 7,Android FFmpeg視頻編碼
本文你可以了解到
作為開篇的文章,我們先來看看音視頻由什么構(gòu)成的,以及一些常見的術(shù)語和概念。
一、視頻是什么?
動(dòng)畫書
不知道大家小時(shí)候是否玩過一種動(dòng)畫小人書,連續(xù)翻動(dòng)的時(shí)候,小人書的畫面就會(huì)變成一個(gè)動(dòng)畫,類似現(xiàn)在的gif格式圖片。

本來是一本靜態(tài)的小人書,通過翻動(dòng)以后,就會(huì)變成一個(gè)有趣的小動(dòng)畫,如果畫面夠多,翻動(dòng)速度夠快的話,這其實(shí)就是一個(gè)小視頻。
而視頻的原理正是如此,由于人類眼睛的特殊結(jié)構(gòu),畫面快速切換時(shí),畫面會(huì)有殘留,感覺起來就是連貫的動(dòng)作。所以,視頻就是由一系列圖片構(gòu)成的。
視頻幀
幀,是視頻的一個(gè)基本概念,表示一張畫面,如上面的翻頁動(dòng)畫書中的一頁,就是一幀。一個(gè)視頻就是由許許多多幀組成的。
幀率
幀率,即單位時(shí)間內(nèi)幀的數(shù)量,單位為:幀/秒 或fps(frames per second)。如動(dòng)畫書中,一秒內(nèi)包含多少張圖片,圖片越多,畫面越順滑,過渡越自然。
幀率的一般以下幾個(gè)典型值:
24/25 fps:1秒 24/25 幀,一般的電影幀率。
30/60 fps:1秒 30/60 幀,游戲的幀率,30幀可以接受,60幀會(huì)感覺更加流暢逼真。
85 fps以上人眼基本無法察覺出來了,所以更高的幀率在視頻里沒有太大意義。
色彩空間
這里我們只講常用到的兩種色彩空間。
- RGB
RGB的顏色模式應(yīng)該是我們最熟悉的一種,在現(xiàn)在的電子設(shè)備中應(yīng)用廣泛。通過R G B三種基礎(chǔ)色,可以混合出所有的顏色。
- YUV
這里著重講一下YUV,這種色彩空間并不是我們熟悉的。這是一種亮度與色度分離的色彩格式。
早期的電視都是黑白的,即只有亮度值,即Y。有了彩色電視以后,加入了UV兩種色度,形成現(xiàn)在的YUV,也叫YCbCr。
Y:亮度,就是灰度值。除了表示亮度信號(hào)外,還含有較多的綠色通道量。
U:藍(lán)色通道與亮度的差值。
V:紅色通道與亮度的差值。
采用YUV有什么優(yōu)勢(shì)呢?
人眼對(duì)亮度敏感,對(duì)色度不敏感,因此減少部分UV的數(shù)據(jù)量,人眼卻無法感知出來,這樣可以通過壓縮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
二、音頻是什么?
音頻數(shù)據(jù)的承載方式最常用的是脈沖編碼調(diào)制,即PCM。
在自然界中,聲音是連續(xù)不斷的,是一種模擬信號(hào),那怎樣才能把聲音保存下來呢?那就是把聲音數(shù)字化,即轉(zhuǎn)換為數(shù)字信號(hào)。
我們知道聲音是一種波,有自己的振幅和頻率,那么要保存聲音,就要保存聲音在各個(gè)時(shí)間點(diǎn)上的振幅。
而數(shù)字信號(hào)并不能連續(xù)保存所有時(shí)間點(diǎn)的振幅,事實(shí)上,并不需要保存連續(xù)的信號(hào),就可以還原到人耳可接受的聲音。
根據(jù)奈奎斯特采樣定理:為了不失真地恢復(fù)模擬信號(hào),采樣頻率應(yīng)該不小于模擬信號(hào)頻譜中最高頻率的2倍。
根據(jù)以上分析,PCM的采集步驟分為以下步驟:
模擬信號(hào)->采樣->量化->編碼->數(shù)字信號(hào)
采樣率和采樣位數(shù)
采樣率,即采樣的頻率。
上面提到,采樣率要大于原聲波頻率的2倍,人耳能聽到的最高頻率為20kHz,所以為了滿足人耳的聽覺要求,采樣率至少為40kHz,通常為44.1kHz,更高的通常為48kHz。
采樣位數(shù),涉及到上面提到的振幅量化。波形振幅在模擬信號(hào)上也是連續(xù)的樣本值,而在數(shù)字信號(hào)中,信號(hào)一般是不連續(xù)的,所以模擬信號(hào)量化以后,只能取一個(gè)近似的整數(shù)值,為了記錄這些振幅值,采樣器會(huì)采用一個(gè)固定的位數(shù)來記錄這些振幅值,通常有8位、16位、32位。
| 位數(shù) | 最小值 | 最大值 |
|---|---|---|
| 8 | 0 | 255 |
| 16 | -32768 | 32767 |
| 32 | -2147483648 | 2147483647 |
位數(shù)越多,記錄的值越準(zhǔn)確,還原度越高。
最后就是編碼了。由于數(shù)字信號(hào)是由0,1組成的,因此,需要將幅度值轉(zhuǎn)換為一系列0和1進(jìn)行存儲(chǔ),也就是編碼,最后得到的數(shù)據(jù)就是數(shù)字信號(hào):一串0和1組成的數(shù)據(jù)。
整個(gè)過程如下:

聲道數(shù)
聲道數(shù),是指支持能不同發(fā)聲(注意是不同聲音)的音響的個(gè)數(shù)。
單聲道:1個(gè)聲道
雙聲道:2個(gè)聲道
立體聲道:默認(rèn)為2個(gè)聲道
立體聲道(4聲道):4個(gè)聲道
碼率
碼率,是指一個(gè)數(shù)據(jù)流中每秒鐘能通過的信息量,單位bps(bit per second)
碼率 = 采樣率 * 采樣位數(shù) * 聲道數(shù)
三、為什么要編碼
這里的編碼和上面音頻中提到的編碼不是同個(gè)概念,而是指壓縮編碼。
我們知道,在計(jì)算機(jī)的世界中,一切都是0和1組成的,音頻和視頻數(shù)據(jù)也不例外。由于音視頻的數(shù)據(jù)量龐大,如果按照裸流數(shù)據(jù)存儲(chǔ)的話,那將需要耗費(fèi)非常大的存儲(chǔ)空間,也不利于傳送。而音視頻中,其實(shí)包含了大量0和1的重復(fù)數(shù)據(jù),因此可以通過一定的算法來壓縮這些0和1的數(shù)據(jù)。
特別在視頻中,由于畫面是逐漸過渡的,因此整個(gè)視頻中,包含了大量畫面/像素的重復(fù),這正好提供了非常大的壓縮空間。
因此,編碼可以大大減小音視頻數(shù)據(jù)的大小,讓音視頻更容易存儲(chǔ)和傳送。
四、視頻編碼
視頻編碼格式
視頻編碼格式有很多,比如H26x系列和MPEG系列的編碼,這些編碼格式都是為了適應(yīng)時(shí)代發(fā)展而出現(xiàn)的。
其中,H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)國際電傳視訊聯(lián)盟主導(dǎo)
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的組織)主導(dǎo)。
當(dāng)然,他們也有聯(lián)合制定的編碼標(biāo)準(zhǔn),那就是現(xiàn)在主流的編碼格式H264,當(dāng)然還有下一代更先進(jìn)的壓縮編碼標(biāo)準(zhǔn)H265。
H264編碼簡(jiǎn)介
H264是目前最主流的視頻編碼標(biāo)準(zhǔn),所以我們后續(xù)的文章中主要以該編碼格式為基準(zhǔn)。
H264由ITU和MPEG共同定制,屬于MPEG-4第十部分內(nèi)容。
由于H264編碼算法十分復(fù)雜,不是一時(shí)半刻能夠講清楚的,也不在本人目前的能力范圍內(nèi),所以這里只簡(jiǎn)單介紹在日常開發(fā)中需要了解到的概念。實(shí)際上,視頻的編碼和解碼部分通常由框架(如Android硬解/FFmpeg)完成,一般的開發(fā)者并不會(huì)接觸到。
- 視頻幀
我們已經(jīng)知道,視頻是由一幀一幀畫面構(gòu)成的,但是在視頻的數(shù)據(jù)中,并不是真正按照一幀一幀原始數(shù)據(jù)保存下來的(如果這樣,壓縮編碼就沒有意義了)。
H264會(huì)根據(jù)一段時(shí)間內(nèi),畫面的變化情況,選取一幀畫面作為完整編碼,下一幀只記錄與上一幀完整數(shù)據(jù)的差別,是一個(gè)動(dòng)態(tài)壓縮的過程。
在H264中,三種類型的幀數(shù)據(jù)分別為
I幀:幀內(nèi)編碼幀。就是一個(gè)完整幀。
P幀:前向預(yù)測(cè)編碼幀。是一個(gè)非完整幀,通過參考前面的I幀或P幀生成。
B幀:雙向預(yù)測(cè)內(nèi)插編碼幀。參考前后圖像幀編碼生成。B幀依賴其前最近的一個(gè)I幀或P幀及其后最近的一個(gè)P幀。
- 圖像組:GOP和關(guān)鍵幀:IDR
全稱:Group of picture。指一組變化不大的視頻幀。
GOP的第一幀成為關(guān)鍵幀:IDR
IDR都是I幀,可以防止一幀解碼出錯(cuò),導(dǎo)致后面所有幀解碼出錯(cuò)的問題。當(dāng)解碼器在解碼到IDR的時(shí)候,會(huì)將之前的參考幀清空,重新開始一個(gè)新的序列,這樣,即便前面一幀解碼出現(xiàn)重大錯(cuò)誤,也不會(huì)蔓延到后面的數(shù)據(jù)中。
注:關(guān)鍵幀都是I幀,但是I幀不一定是關(guān)鍵幀
- DTS與PTS
DTS全稱:Decoding Time Stamp。標(biāo)示讀入內(nèi)存中數(shù)據(jù)流在什么時(shí)候開始送入解碼器中進(jìn)行解碼。也就是解碼順序的時(shí)間戳。
PTS全稱:Presentation Time Stamp。用于標(biāo)示解碼后的視頻幀什么時(shí)候被顯示出來。
在沒有B幀的情況下,DTS和PTS的輸出順序是一樣的,一旦存在B幀,PTS和DTS則會(huì)不同。
- 幀的色彩空間
前面我們介紹了RGB和YUV兩種圖像色彩空間。H264采用的是YUV。
YUV存儲(chǔ)方式分為兩大類:planar 和 packed。
planar:先存儲(chǔ)所有Y,緊接著存儲(chǔ)所有U,最后是V;
packed:每個(gè)像素點(diǎn)的 Y、U、V 連續(xù)交叉存儲(chǔ)。
planar如下:

packed如下:

不過pakced存儲(chǔ)方式已經(jīng)非常少用,大部分視頻都是采用planar存儲(chǔ)方式。
上面說過,由于人眼對(duì)色度敏感度低,所以可以通過省略一些色度信息,即亮度共用一些色度信息,進(jìn)而節(jié)省存儲(chǔ)空間。因此,planar又區(qū)分了以下幾種格式: YUV444、 YUV422、YUV420。
YUV 4:4:4采樣,每一個(gè)Y對(duì)應(yīng)一組UV分量。

YUV 4:2:2采樣,每?jī)蓚€(gè)Y共用一組UV分量。

YUV 4:2:0采樣,每四個(gè)Y共用一組UV分量。

其中,最常用的就是YUV420。
- YUV420格式存儲(chǔ)方式
YUV420屬于planar存儲(chǔ)方式,但是又分兩種類型:
YUV420P:三平面存儲(chǔ)。數(shù)據(jù)組成為YYYYYYYYUUVV(如I420)或YYYYYYYYVVUU(如YV12)。
YUV420SP:兩平面存儲(chǔ)。分為兩種類型YYYYYYYYUVUV(如NV12)或YYYYYYYYVUVU(如NV21)
關(guān)于H264的編碼算法和數(shù)據(jù)結(jié)構(gòu),涉及的知識(shí)和篇幅很多(如網(wǎng)絡(luò)抽象層NAL、SPS、PPS),本文不再深入細(xì)說,網(wǎng)上也有比較多的教程講解,有興趣可以自行深入學(xué)習(xí)。
五、音頻編碼
音頻編碼格式
原始的PCM音頻數(shù)據(jù)也是非常大的數(shù)據(jù)量,因此也需要對(duì)其進(jìn)行壓縮編碼。
和視頻編碼一樣,音頻也有許多的編碼格式,如:WAV、MP3、WMA、APE、FLAC等等,音樂發(fā)燒友應(yīng)該對(duì)這些格式非常熟悉,特別是后兩種無損壓縮格式。
但是,我們今天的主角不是他們,而是另外一個(gè)叫AAC的壓縮格式。
AAC是新一代的音頻有損壓縮技術(shù),一種高壓縮比的音頻壓縮算法。在MP4視頻中的音頻數(shù)據(jù),大多數(shù)時(shí)候都是采用AAC壓縮格式。
AAC編碼簡(jiǎn)介
AAC格式主要分為兩種:ADIF、ADTS。
ADIF:Audio Data Interchange Format。 音頻數(shù)據(jù)交換格式。這種格式的特征是可以確定的找到這個(gè)音頻數(shù)據(jù)的開始,不需進(jìn)行在音頻數(shù)據(jù)流中間開始的解碼,即它的解碼必須在明確定義的開始處進(jìn)行。這種格式常用在磁盤文件中。
ADTS:Audio Data Transport Stream。 音頻數(shù)據(jù)傳輸流。這種格式的特征是它是一個(gè)有同步字的比特流,解碼可以在這個(gè)流中任何位置開始。它的特征類似于mp3數(shù)據(jù)流格式。
ADTS可以在任意幀解碼,它每一幀都有頭信息。ADIF只有一個(gè)統(tǒng)一的頭,所以必須得到所有的數(shù)據(jù)后解碼。且這兩種的header的格式也是不同的,目前一般編碼后的都是ADTS格式的音頻流。
ADIF數(shù)據(jù)格式:
| header | raw_data |
|---|
ADTS 一幀 數(shù)據(jù)格式(中間部分,左右省略號(hào)為前后數(shù)據(jù)幀):

AAC內(nèi)部結(jié)構(gòu)也不再贅述,可以參考AAC 文件解析及解碼流程
六、音視頻容器
細(xì)心的讀者可能已經(jīng)發(fā)現(xiàn),前面我們介紹的各種音視頻的編碼格式,沒有一種是我們平時(shí)使用到的視頻格式,比如:mp4、rmvb、avi、mkv、mov...
沒錯(cuò),這些我們熟悉的視頻格式,其實(shí)是包裹了音視頻編碼數(shù)據(jù)的容器,用來把以特定編碼標(biāo)準(zhǔn)編碼的視頻流和音頻流混在一起,成為一個(gè)文件。
例如:mp4支持H264、H265等視頻編碼和AAC、MP3等音頻編碼。
mp4是目前最流行的視頻格式,在移動(dòng)端,一般將視頻封裝為mp4格式。
七、硬解碼和軟解碼
硬解和軟解的區(qū)別
我們?cè)谝恍┎シ牌髦袝?huì)看到,有硬解碼和軟解碼兩種播放形式給我們選擇,但是我們大部分時(shí)候并不能感覺出他們的區(qū)別,對(duì)于普通用戶來說,只要能播放就行了。
那么他們內(nèi)部究竟有什么區(qū)別呢?
在手機(jī)或者PC上,都會(huì)有CPU、GPU或者解碼器等硬件。通常,我們的計(jì)算都是在CPU上進(jìn)行的,也就是我們軟件的執(zhí)行芯片,而GPU主要負(fù)責(zé)畫面的顯示(是一種硬件加速)。
所謂軟解碼,就是指利用CPU的計(jì)算能力來解碼,通常如果CPU的能力不是很強(qiáng)的時(shí)候,一則解碼速度會(huì)比較慢,二則手機(jī)可能出現(xiàn)發(fā)熱現(xiàn)象。但是,由于使用統(tǒng)一的算法,兼容性會(huì)很好。
硬解碼,指的是利用手機(jī)上專門的解碼芯片來加速解碼。通常硬解碼的解碼速度會(huì)快很多,但是由于硬解碼由各個(gè)廠家實(shí)現(xiàn),質(zhì)量參差不齊,非常容易出現(xiàn)兼容性問題。
Android平臺(tái)的硬解碼
終于來到有關(guān)Android的部分了,作為本文的結(jié)尾,也算是為下一篇文章開一個(gè)頭。
MediaCodec 是Android 4.1(api 16)版本引入的編解碼接口,是所有想在Android上開發(fā)音視頻的開發(fā)人員繞不開的坑。
由于Android碎片化嚴(yán)重,雖然經(jīng)過多年的發(fā)展,Android硬解已經(jīng)有了很大改觀,但實(shí)際上各個(gè)廠家實(shí)現(xiàn)不同, 還是會(huì)有一些意想不到的坑。
相對(duì)于FFmpeg,Android原生硬解碼還是相對(duì)容易入門一些,所以接下來,我將會(huì)從MediaCodec入手,講解如何實(shí)現(xiàn)視頻的編解碼,以及引入OpenGL實(shí)現(xiàn)對(duì)視頻的編輯,最后才引入FFmpeg來實(shí)現(xiàn)軟解,算是一個(gè)比較常規(guī)的音視頻開發(fā)入門流程吧。