上次我們搭建了nginx流媒體服務(wù)器,接下來就是研究安卓端是如何直播推流到nginx服務(wù)器,之前我們了解到視頻流和音頻流,那么直播也必然繞不開這兩個流,手機(jī)端的直播可想而知,視頻流使用攝像頭獲取,音頻流使用麥克風(fēng)獲取。然而攝像頭和麥克風(fēng)直接獲取的裸數(shù)據(jù)的體積實(shí)在是太大了,如果要想進(jìn)行網(wǎng)絡(luò)傳輸,必須進(jìn)行壓縮,即編碼
一、視頻編碼:使用h264
h264是目前使用最廣泛的視頻編碼,由于高壓縮比、高圖像質(zhì)量等優(yōu)勢,使得其在具有高壓縮比的同時還擁有高質(zhì)量流暢的圖像,在網(wǎng)絡(luò)傳輸過程中所需要的帶寬更少,也更加經(jīng)濟(jì)
編碼規(guī)則--h264擁有一套獨(dú)特的編碼
1.I幀 幀內(nèi)編碼幀
可以理解為這一幀畫面的完整保留(實(shí)際是進(jìn)行了切片和壓縮);解碼時只需要本幀數(shù)據(jù)就可以完成(因?yàn)榘暾嬅妫?/p>
I幀特點(diǎn):
JPEG壓縮編碼
它是一個全幀壓縮編碼幀。它將全幀圖像信息進(jìn)行JPEG壓縮編碼及傳輸;
解碼時僅用I幀的數(shù)據(jù)就可重構(gòu)完整圖像;
I幀描述了圖像背景和運(yùn)動主體的詳情;
I幀是P幀和B幀的參考幀(其質(zhì)量直接影響到同組中以后各幀的質(zhì)量);
I幀是幀組GOP的基礎(chǔ)幀(第一幀),在一組中只有一個I幀
I幀不需要考慮運(yùn)動矢量;
I幀所占數(shù)據(jù)的信息量比較大。
2.P幀 前向預(yù)測編碼幀
以I幀為參考幀,在I幀中找出P幀“某點(diǎn)”的預(yù)測值和運(yùn)動矢量, 取預(yù)測差值和運(yùn)動矢量一起傳送。在接收端根據(jù)運(yùn)動矢量從I幀中找出P幀“某點(diǎn)”的預(yù)測值并與差值相加以得到P幀“某點(diǎn)”樣值,從而可得到完整的P幀。
P幀特點(diǎn):
P幀是I幀后面相隔1~2幀的編碼幀;
P幀采用運(yùn)動補(bǔ)償?shù)姆椒▊魉退c前面的I或P幀的差值及運(yùn)動矢量(預(yù)測誤差);
解碼時必須將I幀中的預(yù)測值與預(yù)測誤差求和后才能重構(gòu)完整的P幀圖像;
P幀屬于前向預(yù)測的幀間編碼。它只參考前面最靠近它的I幀或P幀;
由于P幀是參考幀,它可能造成解碼錯誤的擴(kuò)散;
由于是差值傳送,P幀的壓縮比較高
3.B幀 雙向預(yù)測內(nèi)插編碼幀
B幀記錄的是本幀(I幀或P幀)與前后幀(P幀)的差別(具體比較復(fù)雜,有4種情況,要解碼B幀,不僅要取得之前的緩存畫面(I幀或P幀),還要解碼之后的畫面(P幀),通過前后畫面的與本幀數(shù)據(jù)的疊加取得最終的畫面。B幀壓縮率高,但是解碼時CPU會比較累。
B幀特點(diǎn)
B幀是由前面的I或P幀和后面的P幀來進(jìn)行預(yù)測的;
B幀傳送的是它與前面的I或P幀和后面的P幀之間的預(yù)測誤差及運(yùn)動矢量;
B幀是雙向預(yù)測編碼幀;
B幀壓縮比最高,因?yàn)樗环从潮麉⒖紟g運(yùn)動主體的變化情況,預(yù)測比較準(zhǔn)確;
B幀不是參考幀,不會造成解碼錯誤的擴(kuò)散
I、B、P各幀是根據(jù)壓縮算法的需要,是人為定義的,它們都是實(shí)實(shí)在在的物理幀。一般來說,I幀的壓縮率是7(跟JPG差不多),P幀是20,B幀可以達(dá)到50。可見使用B幀能節(jié)省大量空間,節(jié)省出來的空間可以用來保存多一些I幀,這樣在相同碼率下,可以提供更好的畫質(zhì)。
4.宏塊(對一幀畫面進(jìn)行切片后,片中包含的數(shù)據(jù))是視頻信息的主要承載者,因?yàn)樗恳粋€像素的亮度和色度信息。視頻解碼最主要的工作則是提供高效的方式從碼流中獲得宏塊中的像素陣列。組成部分:一個宏塊由一個16×16亮度像素和附加的一個8×8 Cb和一個 8×8 Cr 彩色像素塊組成 。每個圖象中,若干宏塊被排列成片的形式。
I幀、P幀、B幀的概念比較抽象,可以用下面的列子作為理解
一個蘋果在屏幕中間做自由落體運(yùn)動,在中間時的畫面如下:
這個畫面是運(yùn)動的起始點(diǎn),可以把它當(dāng)作I幀。另一個畫面是蘋果落到屏幕下邊緣時,如下圖:
這是蘋果運(yùn)動過程中,我們某個時間點(diǎn)記錄的畫面,可以把它當(dāng)作P幀,而I幀和P幀中間過程的畫面,我們把它們成為B幀,由于B幀不存在真正的畫面,只有以前一畫面(I幀或P幀,P幀也有畫面信息)和后一畫面(P幀)模擬,進(jìn)行畫面的預(yù)測,所以B幀所含的數(shù)據(jù)量很小,但是B幀越多,預(yù)測也就越多,會導(dǎo)致運(yùn)動的不準(zhǔn)確性,可以理解為B幀會使得這運(yùn)動變?yōu)閯蛩龠\(yùn)動,很顯然上面兩張圖中間的速度并不是勻速的,所以P幀特點(diǎn)中有一項(xiàng)為:P幀是I幀后面相隔1~2幀的編碼幀,即B幀在兩個畫面幀之間(I幀和P幀之間,P幀和P幀之間)只有1 ~ 2幀或沒有。
我們對裸數(shù)據(jù)進(jìn)行h264編碼需要用到一個工具x264,接下來介紹如何編譯它
1.首先下載x264
網(wǎng)址:https://www.videolan.org/developers/x264.html
可以用git,也可以直接下載后上傳到服務(wù)器
直接下載下來的文件是bz2文件,需要使用bzip2,執(zhí)行以下命令安裝bzip2
yum -y install bzip2.x86_64
執(zhí)行解壓命令
tar xvfj x264-master.tar.bz2
解壓完畢后的文件內(nèi)容如下
在該目錄下,編寫以下shell腳本,需要使用ndk下的編譯器編譯,ndk的安裝可以參考我以前的文章:http://www.lxweimin.com/p/5850b1f0e024
#!/bin/bash
#改為自己對應(yīng)的ndk目錄
NDK_ROOT=/lib/ndk/android-ndk-r14b
SYSROOT=$NDK_ROOT/platforms/android-9/arch-arm/
TOOLCHAIN=$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
function build_one
{
./configure \
--prefix=$PREFIX \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--sysroot=$SYSROOT \
--host=arm-linux \
--enable-pic \
--enable-static \
--disable-asm \
--disable-shared \
--disable-cli
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
build_one
給shell腳本執(zhí)行權(quán)限后,執(zhí)行該腳本
等待編譯結(jié)束
將include和lib拷貝出來,以備待會放入as工程中
二、音頻編碼:使用aac
使用FAAC編碼工具,同樣的要編譯它
官網(wǎng):http://faac.sourceforge.net/
下載后在Linux中解壓
在該目錄下,編寫以下shell腳本,同樣需要使用ndk中的編譯器
#!/bin/sh
CPU=$1
NDK_ROOT=/lib/ndk/android-ndk-r14b
export PLATFORM=$NDK_ROOT/platforms/android-9/arch-arm
export PREBUILT=$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin
export PREFIX="$(pwd)/android/arm"
export CROSS_COMPILE=$PREBUILT/arm-linux-androideabi-
export CFLAGS="-DANDROID -fPIC -ffunction-sections -funwind-tables -fstack-protector -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fomit-frame-pointer -fstrict-aliasing -funswitch-loops -finline-limit=300"
export CPPFLAGS="$CFLAGS"
export CFLAGS="$CFLAGS"
export CXXFLAGS="$CFLAGS"
export CXX="${CROSS_COMPILE}g++ --sysroot=${PLATFORM}"
export LDFLAGS="$LDFLAGS"
export CC="${CROSS_COMPILE}gcc --sysroot=${PLATFORM}"
export NM="${CROSS_COMPILE}nm"
export STRIP="${CROSS_COMPILE}strip"
export RANLIB="${CROSS_COMPILE}ranlib"
export AR="${CROSS_COMPILE}ar"
mkdir -p ./android/arm/include
mkdir -p ./android/arm/lib
./configure --program-prefix=$PREFIX --without-mp4v2 --host=arm-linux
make
cp ./libfaac/.libs/*.a $PREFIX/lib
cp ./libfaac/.libs/*.so $PREFIX/lib
cp ./include/*.h $PREFIX/include
賦予shell腳本執(zhí)行權(quán)限后執(zhí)行,將生成的include和lib拷貝出來,以備待會放入as工程中
三、推流:使用rtmpdump
官網(wǎng)地址:http://rtmpdump.mplayerhq.hu/
從官網(wǎng)下載下來后解壓(不要下windows和android的,android中只有so文件,如果要使用,需要將頭文件導(dǎo)入as工程)
一會我們將該目錄下的librtmp文件夾復(fù)制到as工程中,使用cmake編譯它
四、創(chuàng)建AS工程:集成各個工具
1.創(chuàng)建ndk工程,在manifest中賦予權(quán)限
<uses-permission android:name="android.permission.INTERNET"/>
<!--錄音權(quán)限-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--攝像頭權(quán)限-->
<uses-permission android:name="android.permission.CAMERA"/>
2.將faac和x264中的頭文件和靜態(tài)庫復(fù)制進(jìn)工程中:
在CMakeLists中導(dǎo)入它們
cmake_minimum_required(VERSION 3.4.1)
#將libs的路徑設(shè)置到變量中
set(lib_dir ${CMAKE_SOURCE_DIR}/../../../libs)
#include頭文件
include_directories(${CMAKE_SOURCE_DIR}/../../../libs/include)
#x264
add_library(
x264
STATIC
IMPORTED)
SET_TARGET_PROPERTIES(
x264
PROPERTIES IMPORTED_LOCATION
${lib_dir}/lib/libx264.a)
#faac
add_library(
faac
STATIC
IMPORTED)
SET_TARGET_PROPERTIES(
faac
PROPERTIES IMPORTED_LOCATION
${lib_dir}/lib/libfaac.a)
add_library(
native-lib
SHARED
native-lib.cpp)
find_library(
log-lib
log)
target_link_libraries(
native-lib
x264
faac
${log-lib})
在gradle的defaultConfig中配置只編譯armeabi平臺,其他平臺需要重新使用ndk中的相應(yīng)編譯器編譯
ndk {
abiFilters "armeabi"
}
3.復(fù)制rtmpdump到工程下:
在librtmp中創(chuàng)建新的CMakeLists.txt,內(nèi)容如下:
#編譯時關(guān)閉openssl
#編譯時關(guān)閉openssl
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_CRYPTO")
file(GLOB rtmp_source *.c)
add_library(
rtmp
STATIC
${rtmp_source})
在主CMakeLists.txt中導(dǎo)入上面cmake生成的靜態(tài)庫
...
#引入rtmp的靜態(tài)庫
add_subdirectory(${CMAKE_SOURCE_DIR}/librtmp)
...
target_link_libraries(
native-lib
x264
faac
rtmp
${log-lib})