Android NDK開發之旅26 NDK 音視頻相關基礎知識與FFmpeg介紹以及VS下配置

###音視頻基礎知識ios

####視頻播放原理git

咱們先從一個簡單的視頻播放器的原理開始講述,下圖是一個最簡單的視頻播放的過程(不包括視頻加密等等過程):算法

視頻播放原理

這是一個視頻播放的最基本的原理流程圖,從這個圖能夠很總體得看到視頻處理的一些主要步驟,後面咱們會詳細介紹一些這裏提到的基本概念。編程

######注意:咱們利用FFmpeg進行編程的時候幾乎就是基於這個流程圖來進行。好比說,編程的時候咱們會拿到解碼器,解碼讀取數據,繪製到屏幕上面的時候可能還須要把YUV數據轉換爲RGB等等。瀏覽器

咱們常見的封裝視頻的格式有:flv(音視頻分開)、mp四、avi等等。後面咱們會詳細說明。服務器

####爲何視頻須要通過封裝處理呢?微信

由於攝像頭採集到的畫面、以及麥克風採集到的音頻數據是通過壓縮的處理,否則視頻文件就會很大。數據結構

也就是說:函數

  1. 錄像、錄音,實質是一個壓縮採集的圖像或者聲音的過程。這個過程就是視頻編碼壓縮的過程。
  2. 播放視頻、音頻文件實質上就是解壓縮的過程,這個過程又稱爲解碼。

####視頻的封裝格式介紹工具

封裝格式的做用是:視頻碼流和音頻碼流按照必定的格式存儲在一個文件中。

封裝格式分析工具:Elecard Format Analyzer

爲何要音視頻分開存儲呢?由於音視頻的編碼格式各類各樣,同時編碼必然會形成混亂。

常見的視頻封裝格式有:

視頻封裝格式

以兩個格式爲例子,介紹一下原理:

常見視頻封裝格式原理

  1. MPEG2-TS格式是由一個一個數據大小固定的TS-Packet組成,所以能夠支持快進。
  2. FLY格式由FLV HEADER以及一個一個大小不固定的Tag組成。由於FLV格式直接可以用flash(瀏覽器)播放,所以經常使用於視頻直播鄰域。咱們在作RTMP推流的時候,一開始就須要發送頭信息。由於數據單元大小不固定,所以原生的視頻播放器不支持FLV視頻的快進(有些播放器進行了處理能夠快進)。

####視頻編解碼常見格式介紹

視頻的壓縮算法不少,所以編碼格式就會有不少種,下面介紹一些常見的編解碼格式:

#####視頻編解碼格式:

  1. 常見的視頻編碼格式有:H.26四、MPEG二、VP8等(谷歌收購的WebRTC視頻通話就是用VP8)。
  2. 視頻解碼獲得的像素數據YUV、RGB。YUV格式中,Y表明亮度,UV表明色度,人眼對亮度比較敏感,二者比例爲4:1,與生物學的理論有關。

視頻編碼格式

原理分析:

視頻編碼格式原理分析

以H264爲例,H264是由大小不固定的NALU構成。(NALU實質是一種數據結構)。H264裏面有不少子壓縮算法,原理比較複雜,包括了熵編碼,環路濾波,幀內檢測,幀間檢測等知識。H264編碼原理比較複雜,所以H264的壓縮效率是幾百到幾千倍。

######咱們須要學會FFmpeg便可,由於這個庫封裝了H264等格式的處理。

視頻解碼(攝像機獲取)獲得的是視頻像素數據,保存了屏幕上每一個像素點的像素值。常見的像素數據格式有RGB24, RGB32, YUV420P,YUV422P,YUV444P等。壓縮編碼中通常使用的是YUV格式的像素數據,最爲常見的格式爲YUV420P。

YUV視頻格式是沒有通過壓縮的,很大。早期在電視上面用得比較多,好比古老的黑白、彩電。彩電播放早期的黑白視頻實質上是隻播放了Y(亮度)的數據,由於黑白視頻只有Y的數據嘛。

YUV格式簡介

RGB也有不少種,好比RGB24,不一樣的RGB編碼色彩豐富度不一樣。

RGB格式簡介

#####音視頻編解碼格式:

  1. 常見的音頻編碼格式有:AAC、MP3。
  2. 音頻解碼獲得的是音頻採樣數據,而後喇叭才能播放。常見格式是PCM,實質是一個一個的採樣值。單位時間內震動的數據,包括振幅和頻率。經常使用採樣率44100,人耳朵可以擦覺到的最高採樣率。

常見的音頻編碼格式

######在作視頻直播的時候:音頻經常使用AAC來進行編碼,用FAAC庫來處理;視頻用H264編碼。

音頻編碼格式AAC介紹

音頻採樣數據PCM:保存了音頻中每一個採樣點的值,音頻採樣數據體積很大,通常須要進過壓縮,咱們日常說的「無損」實質上是沒有損失的壓縮的意思。

PCM格式簡介

#####相關播放(編輯)工具

  1. YUV:YUV Player
  2. PCM:Adobe Audition
  3. 查看視頻信息:MediaInfo
  4. 視頻編碼數據:Elecard Format Analyzer
  5. 視頻編碼分析工具:Elecard Stream Eye

有興趣能夠下載玩玩。

###FFmpeg介紹

FFmpeg是開源的C/C++音視頻處理的類庫,這個庫十分優秀,以致於不少大公司都在用。主流的視頻播放器幾乎都使用了FFmpeg。

####FFmpeg的八個函數庫的基本介紹

以下圖所示:

FFmpeg庫簡介

###Visual Studio下FFmpeg的項目配置

####前言

咱們通常是在VS中寫好代碼而後放到Android中的,所以有必要搭建VS的開發環境。

####FFmpeg資源獲取

首先咱們須要去FFmpeg的官網去獲取源碼,由於獲取的步驟比較麻煩,固下個筆記記錄下來,咱們打開ffmpeg.org/

FFmpeg官網.png

點擊官網中大大的Download按鈕,跳轉到下面的界面:

下載Windows版本的資源.png

選擇對應的系統,這裏咱們先介紹Windows版本的,點擊下面的Windows Builds,跳轉到下面的界面:

下載Windows版本的資源.png

咱們推薦使用舊版的FFmpeg庫,由於若是使用新版的話,除了問題很難去百度。筆者的電腦是64位的,因而就點擊All 64-bit Downloads。而後咱們會跳轉到下面這個倉庫頁面:

FFmpeg倉庫.png

其中,咱們須要下載的FFmpeg版本是2.8系列的,咱們推薦使用2.8或者如下的版本。其中,dev是開發版本的庫,shared是一些動態連接庫,static是一些已經編譯好的exe(Windows版本)可執行文件。這三個咱們都須要下載下來。

若是你嫌麻煩的話,我下面直接給出下載地址:

https://ffmpeg.zeranoe.com/builds/win64/dev/2015/ffmpeg-20151105-git-c878082-win64-dev.7z

https://ffmpeg.zeranoe.com/builds/win64/shared/2015/ffmpeg-20151105-git-c878082-win64-shared.7z

https://ffmpeg.zeranoe.com/builds/win64/static/2015/ffmpeg-20151105-git-c878082-win64-static.7z

下面並解壓的效果以下:

效果.png

注意:下面分別用dev、static、shared來表明這三個文件夾。

####在命令行玩一玩static中的可執行文件

咱們打開static文件夾,裏面有個bin目錄,有三個exe文件。這就是咱們即將要玩的東西:

可執行文件.png

爲了簡化操做,咱們不妨把bin目錄添加到環境變量path中。

而後咱們準備一個測試用的視頻,例如筆者準備了一個test.flv視頻文件。

打開命令行,輸入:

ffmpeg -i test.flv test.avi
複製代碼

而後這就完成了一次簡單的視頻格式轉換。相信細心的你也會發現,FFmpeg的官網上面有這麼一幅圖:

官網的例子.png

其實這就是一個最簡單的例子。

下面咱們再來搞一個是視頻轉GIF,在命令行輸入下面的語句:

ffmpeg -ss 0 -t 11 -i test.flv -s 1366x768 -b:v 1500k test.gif
複製代碼

意思就是把test.flv轉換爲test.gif文件,其中須要指定轉換的時間範圍,分辨率大小,比特率。

######碼率(比特率),單位時間每一幀畫面以及音頻的大小,也叫做比特每秒,單位時間內播放連續的媒體例如壓縮後的音頻或者視頻的比特數量。碼率越高,音視頻越清晰。

######把視頻轉gif是頗有意義的,例如微信中的小視頻,咱們沒有點開的時候,其實播放的是一個gif文件(可能使用的是libgif這個NDK庫),用戶點擊打開的時候,纔會從服務器下載真正的視頻文件。這樣作大大下降了服務器的壓力。

最後咱們看一個播放器的例子,輸入下面的命令,就會打開一個播放器,因此說若是你想研究一個Android平臺的播放器,那麼須要研究ffplay相關的代碼:

ffplay test.flv
複製代碼

####FFmpeg的VS項目配置

咱們首先建立一個空項目,而後把dev中的include、lib兩個目錄拷貝到項目的根路徑下的源代碼目錄中(默認是與項目名同樣)。

而後在項目屬性中配置附加庫目錄:

包含目錄.png

而後配置附加庫目錄:

附加庫目錄.png

而後配置有哪些附加庫(附加依賴項),以下面所示:

avcodec.lib
avdevice.lib
avfilter.lib
avformat.lib
avutil.lib
postproc.lib
swresample.lib
swscale.lib
複製代碼

附加依賴項配置.png

最後咱們建立一個測試用的CPP文件:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>

using namespace std;

//因爲FFmpeg是C和C++混編的,所以使用extern是爲了解決兼容問題
extern "C"{
#include "libavcodec/avcodec.h"
}

void main(){
	//輸出FFmpeg的配置信息,檢查是否配置好
	cout << avcodec_configuration() << endl;
	system("pause");
}
複製代碼

而後你會發現編譯不過,由於咱們用的FFmpeg庫是64位的,所以須要把咱們的平臺改成64位的:

配置平臺爲64位.png

而後編譯經過,輸出的結果以下:

配置結果.png

#####題外話——關於extern關鍵字的基本解釋

extern能夠置於變量或者函數前,以標示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其餘模塊中尋找其定義。此外extern也可用來進行連接指定。也就是說extern有兩個做用:

  1. 第一個,當它與"C"一塊兒連用時,如: extern "C" void fun(int a, int b);則告訴編譯器在編譯fun這個函數名時按着C的規則去翻譯相應的函數名而不是C++的,C++的規則在翻譯這個函數名時會把fun這個名字變得面目全非,多是fun@aBc_int_int#%$也多是別的,這要看編譯器的"脾氣"了(不一樣的編譯器採用的方法不同),爲何這麼作呢,由於C++支持函數的重載啊,在這裏不去過多的論述這個問題,若是你有興趣能夠去網上搜索,相信你能夠獲得滿意的解釋!
  2. 第二,當extern不與"C"在一塊兒修飾變量或函數時,如在頭文件中: extern int g_Int; 它的做用就是聲明函數或全局變量的做用範圍的關鍵字,其聲明的函數和變量能夠在本模塊活其餘模塊中使用,記住它是一個聲明不是定義!也就是說B模塊(編譯單元)要是引用模塊(編譯單元)A中定義的全局變量或函數時,它只要包含A模塊的頭文件便可,在編譯階段,模塊B雖然找不到該函數或變量,但它不會報錯,它會在鏈接時從模塊A生成的目標代碼中找到此函數。

若是以爲個人文字對你有所幫助的話,歡迎關注個人公衆號:

公衆號:Android開發進階

個人羣歡迎你們進來探討各類技術與非技術的話題,有興趣的朋友們加我私人微信huannan88,我拉你進羣交(♂)流(♀)

相關文章
相關標籤/搜索