上次我们搭建了nginx流媒体服务器,接下来就是研究安卓端是如何直播推流到nginx服务器,之前我们了解到视频流和音频流,那么直播也必然绕不开这两个流,手机端的直播可想而知,视频流使用摄像头获取,音频流使用麦克风获取。然而摄像头和麦克风直接获取的裸数据的体积实在是太大了,如果要想进行网络传输,必须进行压缩,即编码
一、视频编码:使用h264
h264是目前使用最广泛的视频编码,由于高压缩比、高图像质量等优势,使得其在具有高压缩比的同时还拥有高质量流畅的图像,在网络传输过程中所需要的带宽更少,也更加经济
编码规则--h264拥有一套独特的编码
1.I帧 帧内编码帧
可以理解为这一帧画面的完整保留(实际是进行了切片和压缩);解码时只需要本帧数据就可以完成(因为包含完整画面)
I帧特点:
JPEG压缩编码
它是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输;
解码时仅用I帧的数据就可重构完整图像;
I帧描述了图像背景和运动主体的详情;
I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);
I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧
I帧不需要考虑运动矢量;
I帧所占数据的信息量比较大。
2.P帧 前向预测编码帧
以I帧为参考帧,在I帧中找出P帧“某点”的预测值和运动矢量, 取预测差值和运动矢量一起传送。在接收端根据运动矢量从I帧中找出P帧“某点”的预测值并与差值相加以得到P帧“某点”样值,从而可得到完整的P帧。
P帧特点:
P帧是I帧后面相隔1~2帧的编码帧;
P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差);
解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像;
P帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧;
由于P帧是参考帧,它可能造成解码错误的扩散;
由于是差值传送,P帧的压缩比较高
3.B帧 双向预测内插编码帧
B帧记录的是本帧(I帧或P帧)与前后帧(P帧)的差别(具体比较复杂,有4种情况,要解码B帧,不仅要取得之前的缓存画面(I帧或P帧),还要解码之后的画面(P帧),通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累。
B帧特点
B帧是由前面的I或P帧和后面的P帧来进行预测的;
B帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量;
B帧是双向预测编码帧;
B帧压缩比最高,因为它只反映丙参考帧间运动主体的变化情况,预测比较准确;
B帧不是参考帧,不会造成解码错误的扩散
I、B、P各帧是根据压缩算法的需要,是人为定义的,它们都是实实在在的物理帧。一般来说,I帧的压缩率是7(跟JPG差不多),P帧是20,B帧可以达到50。可见使用B帧能节省大量空间,节省出来的空间可以用来保存多一些I帧,这样在相同码率下,可以提供更好的画质。
4.宏块(对一帧画面进行切片后,片中包含的数据)是视频信息的主要承载者,因为它包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中的像素阵列。组成部分:一个宏块由一个16×16亮度像素和附加的一个8×8 Cb和一个 8×8 Cr 彩色像素块组成 。每个图象中,若干宏块被排列成片的形式。
I帧、P帧、B帧的概念比较抽象,可以用下面的列子作为理解
一个苹果在屏幕中间做自由落体运动,在中间时的画面如下:
这个画面是运动的起始点,可以把它当作I帧。另一个画面是苹果落到屏幕下边缘时,如下图:
这是苹果运动过程中,我们某个时间点记录的画面,可以把它当作P帧,而I帧和P帧中间过程的画面,我们把它们成为B帧,由于B帧不存在真正的画面,只有以前一画面(I帧或P帧,P帧也有画面信息)和后一画面(P帧)模拟,进行画面的预测,所以B帧所含的数据量很小,但是B帧越多,预测也就越多,会导致运动的不准确性,可以理解为B帧会使得这运动变为匀速运动,很显然上面两张图中间的速度并不是匀速的,所以P帧特点中有一项为:P帧是I帧后面相隔1~2帧的编码帧,即B帧在两个画面帧之间(I帧和P帧之间,P帧和P帧之间)只有1 ~ 2帧或没有。
我们对裸数据进行h264编码需要用到一个工具x264,接下来介绍如何编译它
1.首先下载x264
直接下载下来的文件是bz2文件,需要使用bzip2,执行以下命令安装bzip2
yum -y install bzip2.x86_64
执行解压命令
tar xvfj x264-master.tar.bz2
解压完毕后的文件内容如下
在该目录下,编写以下shell脚本,需要使用ndk下的编译器编译,ndk的安装可以参考我以前的文章:https://www.jianshu.com/p/5850b1f0e024
#!/bin/bash
#改为自己对应的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脚本执行权限后,执行该脚本
等待编译结束
将include和lib拷贝出来,以备待会放入as工程中
二、音频编码:使用aac
使用FAAC编码工具,同样的要编译它
下载后在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脚本执行权限后执行,将生成的include和lib拷贝出来,以备待会放入as工程中
三、推流:使用rtmpdump
从官网下载下来后解压(不要下windows和android的,android中只有so文件,如果要使用,需要将头文件导入as工程)
一会我们将该目录下的librtmp文件夹复制到as工程中,使用cmake编译它
四、创建AS工程:集成各个工具
1.创建ndk工程,在manifest中赋予权限
2.将faac和x264中的头文件和静态库复制进工程中:
在CMakeLists中导入它们
cmake_minimum_required(VERSION 3.4.1)
#将libs的路径设置到变量中
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中的相应编译器编译
ndk {
abiFilters "armeabi"
}
3.复制rtmpdump到工程下:
在librtmp中创建新的CMakeLists.txt,内容如下:
#编译时关闭openssl
#编译时关闭openssl
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_CRYPTO")
file(GLOB rtmp_source *.c)
add_library(
rtmp
STATIC
${rtmp_source})
在主CMakeLists.txt中导入上面cmake生成的静态库
...
#引入rtmp的静态库
add_subdirectory(${CMAKE_SOURCE_DIR}/librtmp)
...
target_link_libraries(
native-lib
x264
faac
rtmp
${log-lib})
build顺利通过,至此,集成第三方库的准备工作就结束了,下一篇文章就开始编写代码