Android音視頻開發筆記(一)--一些基礎知識和ffmpeg的編譯

筆者是在2015年正式成爲Android App開發工程師,遇上了一波移動互聯網的大潮。第一次正式接觸音視頻相關的內容是2016年在一家圈內知名的無人機公司。當時須要作的功能比較簡單,從無線設備上接收RTP數據包並將搭載的h264視頻數據提取出來解碼、渲染。在那段時間網上能夠參考的資料比較少,走了不少彎路。我正式從事這方面的工做時間比較短,是從2018年下半年開始成爲一名專職的Android音視頻開發工程師,在此以前的一段時間雖然對這方面內容很感興趣,但無奈須要編寫App的一些前端內容沒法專心沉澱下來,因此如今就想分享一些我的在Android移動平臺上音視頻相關的一些經驗,但願在能夠幫助新人的同時可以獲得圈內大神的指點,與你們一塊兒共同進步。前端

談一下須要用到的技術

  1. 編程語言方面:c/c++和Java/kotlin
  2. 輪子:OpenGL ES、OpenSL ES、ffmpeg、x264/openh26四、fdk-aac
  3. tools: NDK、CMake、Android SDK、Gradle、AndroidStudio、git
  • OpenGL ES

    OpenGL:(全寫Open Graphics Library)是個定義了一個跨編程語言、跨平臺的編程接口的規格,它用於三維圖象(二維的亦可)。OpenGL是個專業的圖形程序接口,是一個功能強大,調用方便的底層圖形庫。android

    OpenGL ES: (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,針對手機、PDA和遊戲主機等嵌入式設備而設計。該API由Khronos集團定義推廣,Khronos是一個圖形軟硬件行業協會,該協會主要關注圖形和多媒體方面的開放標準。c++

    Android系統集成OpenGL ES,目前它有固定管線的1.0版本和能夠自定義頂點、像素計算,能夠本身編寫shader的2.0版本和OpenGL3.0版本(3.0和2.0的區別感興趣的同窗能夠自行google)。本系列文章主要使用OpenGL ES 2.0版本的API。git


  • OpenSL ES

    OpenSL ES (Open Sound Library for Embedded Systems)嵌入式音頻加速標準。github

    OpenSL ES™ 是無受權費、跨平臺、針對嵌入式系統精心優化的硬件音頻加速API。它爲嵌入式移動多媒體設備上的本地應用程序開發者提供標準化, 高性能,低響應時間的音頻功能實現方法,並實現軟/硬件音頻性能的直接跨平臺部署,下降執行難度,促進高級音頻市場的發展。算法

    OpenSL ES也被內置在Android系統中,因爲是較低層級的API,屬於C語言API,因此只能在C/C++中調用。OpenSL ES提供了很是強大的音效處理、低延時播放等功能。好比Android手機的耳返功能就是基於它實現的。編程


  • 爲何要進行視頻壓縮編碼

    咱們知道視頻其實是一組圖片按照某種頻率來播放的。因此學習視頻仍是要從圖像開始。咱們都知道白光裏的含有等量的紅光、綠光(這裏並無某種顏色的帽子閃閃發光的意思)和藍光。在現實生活中咱們看到的物體的輪廓和顏色都是因爲光的反射。而手機上的屏幕時能夠本身發光的,原理也是同樣。bash

    好比一塊分辨率爲1280*720的屏幕,他在x軸上有720個像素點,在y軸上有1280個像素點,而每一個像素都是由RGB三種顏色組成。 而RGB的表示方法有兩種,一種是使用0~1.0的浮點型,另外一種是0~255的整形來表示。通常來講咱們都是用整形來表示RGB,每一個顏色通道也就是8個bit(255用二進制表示爲11111111)。因此一張1280x720的Bitmap的size爲:網絡

    1280x720x8x4 ≈ 3.516M
    複製代碼

    這裏乘4是由於還有一個alpha通道。按照通常的視頻1秒鐘有30幀來算的話,每秒的數據體積大概是105M。這麼大的體積對於網絡傳輸來講顯然是不可接受的。編程語言

    固然咱們也可使用yuv來表示圖像數據,雖然比RGB數據體積要小很多,可是用於網絡傳輸的話,體積仍是太大。YUV百度百科

    因此,咱們須要採用一些視頻壓縮編碼算法在保證必定的圖像質量的前提下,壓縮咱們的圖像數據(目前大部分視頻編碼技術都是有損的)。

  • H.264簡介

    說到H.264相信你們應該都據說過ffmpeg吧。ffmpeg全拼爲Fast forwar motion jpeg,其中jpeg全拼爲Joint Photographic Experts Group(聯合圖像專家小組),是一種常見的圖像格式,它由聯合照片專家組開發並命名爲"ISO 10918-1",JPEG僅僅是一種俗稱而已。 固然視頻也有定製標準:Motion Jpeg(簡稱Mpeg)

    Mpeg發展歷史:Mpeg1(VCD) Mepg2(DVD) Mpeg4(AVC) 其中ITU-T(國際電信聯盟電信標準分局)定製的H.26一、H.26二、H.263和H.264是一套單獨的體系。這裏面H.264集中了以往標準的全部優勢,也是目前最流行的編碼格式,因此本系列文章主要的視頻編解碼部分都是針對於H.264的。

    • 編碼流程:

      幀間和幀內預測(Estimation)-->變換(Transform)-->量化(Quantization)-->環路濾波(Loop Filter)-->熵編碼(Entropy Coding)

      1.IBP幀 I幀(Intra Picture):幀內編碼幀。I幀一般是每一個GOP(Group Of Picture)的第一個幀 P幀:向前參考幀 B幀:雙向參考幀

    • H.264都是由連續的NALU(NAL Unit)組成的。

      它的功能分爲兩層VCL(Video Coding Layer)視頻編碼層和NAL(Network Abstraction Layer)網絡提取層。 VCL:它包括核心壓縮引擎和塊,宏塊和片的語法級別定義,設計目標是儘量的獨立於網絡進行高效的編碼。 NAL:負責將VCL產生的比特字符串適配到各類各樣的網絡和多元環境中,覆蓋了全部片(slice)級以上的語法級別。

      一幀圖片通過H.264編碼後,就被編碼成一個或多個片(slice),裝載slice的載體就是NALU

    • 切片(slice)

      切片的主要作那個與是用於宏塊(Macroblck)的載體。片之因此被創造出來,主要是爲了限制誤碼的擴散和傳輸。每一個片都應該是互相獨立被傳輸的,某片的預測(片內預測和片間預測)不能以其餘片中的宏塊做爲參考對象

      一幀圖像能夠包含一個或多個分片(slice),每個分片包含數個宏塊,即每片至少一個宏塊,最多時每片包含整個圖像的宏塊。 分片頭中包含着分片類型、分片中的宏塊類型、分片幀的數量、分片屬於哪一個圖像以及對應的幀的設置和參數等信息。 分片數據中則是宏塊,這裏就是咱們要找的,存儲像素的地方。

    • 宏塊

      宏塊是視頻信息的主要承載者,由於它包含着每個像素的亮度和色度信息。視頻解碼最主要的工做則是提供高效的方式從碼流中獲取宏塊中的像素陣列。

      組成部分:一個宏塊是由一個16x16亮度像素和附加的一個8x8 Cr彩色像素塊組成。每一個圖像中,若干宏塊被排列成片的形式

      宏塊中包含了宏塊類型、預測類型、Code Block Pattern(CPB) 、Quantization Parameter、像素的亮度和色度數據集等信息

    • 切片類型和宏塊類型的關係

      P-slice. Consists of P-macroblocks (each macro block is predicted using one reference frame) and / or I-macroblocks.

      B-slice. Consists of B-macroblocks (each macroblock is predicted using one or two reference frames) and / or I-macroblocks.

      I-slice. Contains only I-macroblocks. Each macroblock is predicted from previously coded blocks of the same slice.

      SP-slice. Consists of P and / or I-macroblocks and lets you switch between encoded streams.

      SI-slice. It consists of a special type of SI-macroblocks and lets you switch between encoded streams.

      I片:只包含I宏塊,I宏塊利用從當前片中已解碼的像素做爲參考進行幀內預測(不能取其它片中的已解碼像素做爲參考進行幀內預測)

      P片:可包含P和I宏塊,P宏塊利用前面已編碼圖像做爲參考圖像進行幀內預測,一個幀內編碼的宏塊可進一步做宏塊的分割:即16x1六、16x八、8x16或8x8亮度像素塊(以及附帶的彩色像素);若是選了8x8的子宏塊,則可再分紅各類子宏塊的分割,其尺寸爲8x八、8x四、4x8或4x4亮度像素塊(以及附帶的彩色像素)。

      B片:可包含B和I宏塊,B宏塊則利用雙向的參考圖像(當前和已編碼圖像幀)進行幀內預測。

      SP片:用於不一樣編碼流之間的切換,包含P和/或I宏塊

      SI片:擴展當次中必須具備的切換,它包含了一種特殊類型的編碼宏塊,叫作SI宏塊,SI也是擴展當次中必備功能

說了這麼多,上個圖看一下h264總體的結構吧

編碼後視頻的每一組圖像(GOP)都給予了傳輸中的序列sps(Sequence Parameter Set)和自己這個幀的圖像參數pps(Picture Paramter Set)

GOP Group Of Picture 圖像組。主要用做形容一個I幀到下一個I幀之間間隔了多少個幀(I幀間隔),增大圖片組能有效的減小編碼後的視頻體積,但同時也會下降視頻質量

那麼咱們平時在開發中如何判斷一個NALU的類型呢?
若是是帶起始碼的數據好比0001的話,咱們就須要獲取這組數據的第5個字節(若是起始碼是001的話就是第4個字節)的低5位。
int parseNaluType(uint8_t *data) {
    int type = -1;
    if (data[0] == 0x00 && data[1] == 0x00 
        && data[2] == 0x00 && data[3] == 0x01) {
        type = data[4] & 0x1f;
    }
    return type;
}
複製代碼

關於H.264暫時就介紹這麼多,後面咱們具體用到的時候會再具體介紹。

  • libx264

    筆者有點懶,這裏就不介紹x264的特色了。簡單點說,ffmpeg提供了x264的接口,咱們能夠在編譯ffmpeg時在配置選項裏打開x264的支持,x264能夠爲咱們提供h264編碼的功能。


使用NDKr16b編譯ffmpeg+libx264

如今網上大部分的編譯腳本都是使用了比較古老的NDK來進行編譯,可是因爲公司項目中其餘部分的.so動態庫文件是使用ndk r16b來編譯的,爲了不一些莫名其妙的崩潰,我在網上找了好久,終於找到了能夠正常使用的編譯腳本點這裏

#!/bin/bash
FF_VERSION="3.4.5" #這裏能夠改爲其餘版本
SOURCE="ffmpeg-$FF_VERSION"
SHELL_PATH=`pwd`
FF_PATH=$SHELL_PATH/$SOURCE
#輸出路徑
PREFIX=$SHELL_PATH/FFmpeg_android
COMP_BUILD=$1
    
#須要編譯的Android API版本
ANDROID_API=19
#須要編譯的NDK路徑,NDK版本需大等於r15c
NDK=/usr/home/android/ndk #這裏換成本身的NDK路徑
...
複製代碼

修改好腳本後記得給權限,它會自動去下載最新版本的x264和指定版本的ffmpeg並開始編譯,最後輸出一個libffmpeg.so文件。

注意:在高版本的ffmpeg configure選項裏已經沒有了ffserver。

結語

這篇文章咱們簡單介紹了一部分Android音視頻開發須要掌握的一些技術和基本概念。咱們還介紹瞭如何在ndk15--17版本編譯ffmpeg+libx264。這爲咱們在以後學習的內容作了一點點鋪墊。在下一期的文章裏,咱們會把ffmpeg強大的命令行工具集成到咱們的Android項目中,咱們還會開始編寫攝像頭部分的代碼,咱們會使用OpenGL ES來渲染咱們的預覽數據。今天就到這裏,預祝你們春節愉快!

相關文章
相關標籤/搜索