最纯粹的Android直播技术实战01-FFmpeg的编译与运行
最新实战教程,Android自动化刷量、作弊与防作弊,案例:刷友盟统计、批量注册苹果帐号
这个系列的文章将会研究最纯粹的Android直播的实现,而且不是用现在的集成SDK来达到直播的技术实现,而是从一个比较底层的直播实现来探讨这个技术,这样子对于直播技术的理解,现成的一些直播框架等都有一个比较好的理解。
首先:视频播放的过程
现在的一些视频文件都是经过编码-->封装得到的,比如说一个mp4的视频文件mp4就是它的封装格式,h.264就是它的编码格式(h.264是现在最广泛的编码格式了
绝大部分的视频文件都是用它来进行编码的),那么我们的一个视频的播放就会是下面的流程了:
mp4文件-->h.264文件(解封装后生成)-->yuv文件(解码后生成)
yuv就是比较原始的视频文件了,它是非常的大的,和h.264文件相比,可以达到1:100就是说10M大小的h.264文件解码后生成的yuv文件会达到100多M。所以我们的视频文件都会经过一个编码的过程。
那么反过来,我们生成一个视频文件的时候也就会经过:
yuv文件(h.264编码)-->h.264文件(mp4封装)-->mp4文件
比如说我们Android默认的Camera的预览数据就是一种yuv的数据来的,所以我们从Camera里面获取的yuv数据,然后经过一个h.264的编码,然后就可以进行封装或者传输了。
那么直播的这个过程就有可能会是下面那样了:
这系列的文章主要就会研究Android端的直播和看直播的技术,直播并不单单只有RTMP一种协议,还有RTP这些协议等,RTMP比较常用,它使用的封装格式是FLV的,用的网络协议是TCP,RTP用的封装格式是mpegts,用的网络协议是UDP。这里就会使用RTMP这种协议。
在那个图片里面可以看到,在推流前,主要做的就是完成一个视频文件的生成了yuv-->h.264,在Camera获取到的yuv数据里面,其实我们还可以加上养颜啊,特效叠加这些功能,然后再进行编码。也可以对声音进行处理,比如说娃娃音这些。
注意:声音是在视频封装的时候才加上去的,解封装的时候是会把图像数据和音频数据分开的
要注意的是编码格式和封装格式是有很多种的,这个系列文章并不会详细的研究这个,只会在需要的时候讲解这些知识,所以如果想详细知道这些编码和封装的知识的,可以先去Google一下。下面我们就要进入我们这篇文章的实战了,首先就是平台、工具的介绍:
Windows7
MinGW
AndroidStudio 2.2
NDK(r13b)
FFmpeg 3.4
libx264
因为我是在Windows平台下进行编译的,所以就需要MinGW了,MinGW的安装一定要安装msys,不然就没办法进行编译了。
在开发过程中,推荐使用AndroidStudio2.2以上的版本,因为2.2之后,对NDK的支持有了很大的提升,比如说:不需要自己javah来生成native方法对应的C方法了,AS会帮我们自动生成,这个功能就很值得使用了,当然还有其他强大的NDK支持。
我使用的NDK的版本是r13b,最好使用比较新版本的NDK,因为AndroidStudio都使用到2.2了
在直播的流程中,可以看到我们需要对视频进行编码,封装等操作,那么进行这些操作的话,我们需要FFmpeg这个库来完成,FFmpeg对音视频的处理是非常的强大的。这样说吧,常见的播放器,QQ影音,暴风影音啊,都是使用FFmpeg来进行核心的解码,编码等操作的,格式工厂这些转换软件也是,而且现在的一些集成的直播方案,绝大部分也是对FFmpeg进行了包装。所以在这个系列中,就会使用FFmpeg来还原最纯粹的直播技术,这样就可以知道现在打包好的直播技术是怎样实现的。PS:FFmpeg的读法是: F F mpeg 而不是一个一个字母的读
libx264是一个h.264的编码器,在使用FFmpeg的AV_CODEC_ID_H264编码器进行编码的时候,我们就需要使用到这个库了,所以编码FFmpeg的时候,我们会把libx264加进去。
默认Android开发环境已经配置好,NDK环境弄好,安装完成MinGW之后,我们再把FFmpeg和libx264下载下来就可以了,然后解压好。
然后我们修改一下里面的configure文件,让我们编译出来的文件不会带有奇怪的名字,不被Android识别。只要把
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'
修改成
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
修改完成之后,我们就要在当前目录新建一个.sh文件,就是shell脚本
#!/bin/bash
NDK=E:/Programs/android-ndk-r13b
SYSROOT=$NDK/platforms/android-19/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64
function build_one {
./configure \
--prefix=$PREFIX \
--enable-asm \
--enable-neon \
--enable-static \
--enable-small \
--disable-shared \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-avdevice \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--cpu=armv7-a \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-fPIC -DANDROID -mfpu=neon -mfloat-abi=softfp -I$NDK/platforms/android-19/arch-arm/usr/include" \
--extra-ldflags="$ADDI_LDFLAGS"
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one
在这个脚本前面,配置了三个变量,都是指定好NDK的一些工具,目录的,要注意的是在:SYSROOT里面,我编译的platform是android-19的,因为NDK对platform有时会有一些影响的,做过深入的NDK开发的同学应该清楚,所以这个要看具体的需要进行更改什么的。
--enable-asm 开启汇编
--enable-neon 针对armv7-a平台的专属指令集进行的优化,可以让编码效率提升
--enable-small 可以尽量的缩小编译出来的库大小,比如我没打开它,编译出来的.a有80多M,开启后就只有57M左右
--disable-ffmpeg 不要ffmpeg这个工具,ffmpeg是一个命令行工具,非常强大,在官网也可以看到,但我们通过代码开发,就不需要它了
--disable-ffplay --disable-ffprobe --disable-ffserver 这些都是不需要的工具,当然也可以编译出来看看
--disable-avdevice 这个库是用来操作一些硬件设备的,如摄像头这些,但Android中感觉不需要它,也可以编译出来试试能不能使用在Android上
--disable-symver 禁用 symbol versioning
PREFIX=$(pwd)/android/$CPU 指定编译成功的库所在的目录,这个配置是:当前目录下的android目录下的指定的cpu平台目录下
--enable-static --disable-shared这两个是看编译出来的库是静态库(.a)还是动态库(.so),如果要编译成动态库就--enable-shared --disable-static。或者两个都编译出来。
动态库就是只要Android设备里面有公开这个so,那APP就可以使用,就是只要一次安装,其他APP就可以使用了
但APP打包进去的so都是在当前APP的私有目录下的,不能被其他APP使用的,所以动态库的优势就没有了,
而且就暴露了它的缺点,就是so非常大的时候,就会形成Apk包非常大
静态库就是会把需要的代码打包到自己的so里面,所以这样子就可以解决动态库上面的那个缺点
所以这里我们编译出来的是静态库,因为编译出来的库有50多M,所以使用静态库的方式,可以使打包出来的apk最小化
其他的配置都是比较通俗易懂的了,就不多做解释了。上面那个脚本编译出来的就是一个完整功能的FFmpeg库了,我们就可以打开MinGW的msys了
打开msys.bat,然后cd到FFmpeg的根目录,执行./build_script.sh ,就是执行我们上面写的脚本
要打开一个盘:如打开E盘,cd /e 就可以了。
运行那个脚本就会进行一个编译的了,编译过程中,有时可以什么反应都没有,千万不要以为没有运行,停止它,因为编译是比较耗时的,编译个20分钟是很正常的,电脑性能好的,会快点,差的可能半小时都正常,要确定有没有在编译,可以查看一下cpu的使用率就可以了。
经过一段时间的编译,我们就可以在脚本里面定义的那个目录里面看到编译成功的库了。在当前目录下就会有一个android的目录,点进里面后,就会看到编译成功的库了
可以看到有7个库左右。可能版本不一样或配置不一样会有不一样的库,一般用到的会是libavcodec libavfilter libavformat libavutil libswscale这些。
这些库的功能都是非常齐全的,包含了大量的编解码器等,但有时我们并不需要那么多功能,这样就可以通过禁用一些功能来达到精简一些库的大小了
如:不需要解码器 --disable-decoders就可以在上面的那个编译脚本里面加上这个配置。如果只需要某个解码器就可以指定--disable-decoders --enable-decoder=h264 这样就是先禁用解码器,然后指定一个解码器,就是只需要h264的解码器了
所以就可能通过上面那个文件达到精简库的大小了,如下面那个精简的脚本:
#!/bin/bash
NDK=E:/Programs/android-ndk-r13b
SYSROOT=$NDK/platforms/android-19/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64
function build_one {
./configure \
--prefix=$PREFIX \
--enable-asm \
--enable-neon \
--enable-static \
--enable-small \
--disable-shared \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-avdevice \
--disable-symver \
--disable-muxers \
--enable-muxer=mov \
--enable-muxer=mp4 \
--enable-muxer=avi \
--disable-decoders \
--enable-decoder=aac \
--enable-decoder=h264 \
--enable-decoder=mpeg4 \
--disable-demuxers \
--enable-demuxer=h264 \
--enable-demuxer=avi \
--disable-parsers \
--enable-parser=aac \
--enable-parser=h264 \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--cpu=armv7-a \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-fPIC -DANDROID -mfpu=neon -mfloat-abi=softfp -I$NDK/platforms/android-19/arch-arm/usr/include" \
--extra-ldflags="$ADDI_LDFLAGS"
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one
这样就可以大大的缩小库的大小了
那么,接下来就要编译包含了libx264的FFmpeg库了,首先,我们要先编译libx264,我们先在msys里面cd到libx264的目录下面,然后再新建一个shell脚本:
#!/bin/bash
NDK=E:/Programs/android-ndk-r13b
SYSROOT=$NDK/platforms/android-19/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64
function build_one {
./configure \
--prefix=$PREFIX \
--enable-static \
--enable-pic \
--disable-asm \
--disable-cli \
--host=arm-linux \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--sysroot=$SYSROOT
make clean
make
make install
}
PREFIX=$(pwd)/android-lib/
build_one
配置基本上是差不多的,其实这些配置都是可以在configure文件里面找到的,大家可以看看。这里,我们也是编译成静态库。
编译成功后,我们就会在当前目录下的android-lib目录里面看到编译的结果。
那么我们就要把android-lib整个目录拷贝到FFmpeg的根目录下面了,然后就准备编译FFmpeg
其实也很简单,只需要把上面的编译文件添加一些东西就行了
#!/bin/bash
NDK=E:/Programs/android-ndk-r13b
SYSROOT=$NDK/platforms/android-19/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64
EXTRA_LIB=E:/Programs/FFmpeg-3.2.4/android-lib
function build_one {
./configure \
--prefix=$PREFIX \
--enable-asm \
--enable-neon \
--enable-static \
--enable-small \
--enable-libx264 \
--enable-gpl \
--enable-encoder=libx264 \
--disable-shared \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-avdevice \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--cpu=armv7-a \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-I$EXTRA_LIB/include -fPIC -DANDROID -mfpu=neon -mfloat-abi=softfp -I$NDK/platforms/android-19/arch-arm/usr/include" \
--extra-ldflags="-L$EXTRA_LIB/lib -lx264 $ADDI_LDFLAGS"
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/x264_lib/$CPU
ADDI_CFLAGS="-marm"
build_one
EXTRA_LIB 指定刚刚编译的libx264的目录
--enable-libx264 --enable-gpl 就是把libx264启用,两个都需要加
--extra-cflags --extra-ldflags需要指定编译libx264所生成的头文件的lib文件
这样子,我们就可以编译一个带有libx264编码器的FFmpeg库了
在Android中使用FFmpeg
首先,新建一个新的AndroidStudio项目
在新建项目的时候,最好不要勾选*Include C++ Support,因为它默认是采用cmake编译的,所以对于我们想到ndk_build的话就要修改配置了,所以最好不要勾选,除非你用cmake。
新建项目完成之后呢,我们就把视图选成project,然后再在main目录下新建一个cpp的目录
新建cpp目录完成后,就可以把Android.mk和Application.mk文件给拷贝或新建到这个目录里面了,然后再新建一个c的源文件
注意:这里新建的文件是.c的源文件,为什么不要新建.cpp的源文件,
是因为使用.cpp的源文件的时候,会编译不通过,因为FFmpeg是一个纯c语言的项目
也有可能是我的项目配置的问题,所以这里我是新建.c的文件
有找到原因或解决方法的也可以告诉我
我在.cpp 里面使用 extern "C" 也是会编译不通过的
做完这些之后,就可以配置Gradle了,在main目录上右键
要注意,这里我们要选择的是ndk_build,除非用想使用的是cmake,选完这个之后,就再把我们上面添加的Android.mk文件指定到下面的那个Project Path里面去。确定完成后,Gradle就会自动添加一些编译一次的。这样子,这个项目就可以成为一个NDK的项目了
接下来,就要把我们刚刚编译成功的FFmpeg,libx264的库,头文件,都拷贝到cpp这个目录下
然后就要编写Android.mk文件了
LOCAL_PATH := $(call my-dir)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := libavcodec.a
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := libavfilter.a
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := libavformat.a
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := libavutil.a
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := postproc
LOCAL_SRC_FILES := libpostproc.a
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := libswresample.a
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := libswscale.a
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := x264
LOCAL_SRC_FILES := libx264.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a
LOCAL_MODULE := live_jni
LOCAL_SRC_FILES := live_jni.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_CFLAGS := -D__STDC_CONSTANT_MACROS -Wno-sign-compare -Wno-switch -Wno-pointer-sign -DHAVE_NEON=1 -mfpu=neon -mfloat-abi=softfp -fPIC -DANDROID
LOCAL_STATIC_LIBRARIES := avfilter avformat avcodec postproc swresample swscale avutil x264
LOCAL_LDLIBS := -L$(NDK_ROOT)/platforms/$(APP_PLATFORM)/arch-arm/usr/lib -L$(LOCAL_PATH) -llog -ljnigraphics -lz -ldl
include $(BUILD_SHARED_LIBRARY)
这个Android.mk有些不要必须的,之些踩坑可能就会写得有些重复。所以看需要各位进行调整
TARGET_ARCH_ABI := armeabi-v7a
最好指定一下平台,有时候ndk prebuild的时候,有可能会编译全平台,就会出现找不到对应的库的问题的了
LOCAL_STATIC_LIBRARIES := avfilter avformat avcodec postproc swresample swscale avutil x264
这个是非常重要的,后面的那些库的顺序是有讲究的,如果顺序有问题,有可能会报找不到链接的问题
因为FFmpeg每个版本都可能不一样的,所以想要知道这个的顺序,可以去FFmpeg目录下的Makefile文件看看
# $(FFLIBS-yes) needs to be in linking order
FFLIBS-$(CONFIG_AVDEVICE) += avdevice
FFLIBS-$(CONFIG_AVFILTER) += avfilter
FFLIBS-$(CONFIG_AVFORMAT) += avformat
FFLIBS-$(CONFIG_AVCODEC) += avcodec
FFLIBS-$(CONFIG_AVRESAMPLE) += avresample
FFLIBS-$(CONFIG_POSTPROC) += postproc
FFLIBS-$(CONFIG_SWRESAMPLE) += swresample
FFLIBS-$(CONFIG_SWSCALE) += swscale
Android.mk文件写好了之后,我们就写一下Application.mk文件了
APP_STL := gnustl_static
APP_LDFLAGS := -latomic
APP_ABI := armeabi-v7a
APP_PLATFORM := android-19
APP_LDFLAGS
这个最好指定一下 不然可能报undefined reference to '__atomic_fetch_add_4 error
编写完成Application.mk后,我们最好还是去配置一下app的build.gradle
externalNativeBuild {
ndkBuild {
arguments "NDK_APPLICATION_MK=src/main/cpp/Application.mk"
cppFlags "-frtti", "-fexceptions"
abiFilters "armeabi-v7a"
}
}
到现在,我们的整个配置就可以完成了。现在就可以回到MainActivity里面,然后添加一个native方法,然后在方法上Alt+Enter,这样子就会有提示,然后Enter让它生成native方法对应的c方法就可以了
但是,第一次生成对应的native方法的时候,可能会有点问题的
它会新建一个jni的目录,里面新建一个c文件,生成在里面了,我们只要把这个生成的方法,拷贝到我们自己新建的c文件里面,就可以自动建立关联了,这样,以后再自动生成的方法都会在我们新建的那个文件里面了。然后,我们把jni这个目录删掉就行了。
Android官方是说不需要指定Application.mk的,如果和Android.mk在同一个目录下面
这样子,我们就可以写我们的FFmpeg的代码了
#include <jni.h>
#include "libavcodec/avcodec.h"
JNIEXPORT jstring JNICALL
Java_com_xiaoxiao_live_MainActivity_helloFromFFmpeg(JNIEnv *env, jobject instance) {
// TODO
char info[10000] = {0};
sprintf(info, "%s\n", avcodec_configuration());
return (*env)->NewStringUTF(env, info);
}
然后在MainActivity里面把so加载进去就可以了
写完这个之后,我们就可以把我们的项目运行起来了
得到这个结果就说明我们的FFmpeg编译是成功的,而且我们也可以在Android上面使用了
而且从上面看到,我们打包出来的so是非常的小的,这样就可以大大减少我们apk包的大小了
提示:如果修改了Android.mk 或build.gradle文件,建议执行一下下面的操作
总结
到这里,我们的编译和运行就已经完成了,那么接下来,我们就要处理Android的Camera了,以及完成编码,推流等操作了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。