更新:
最近在做摄像头画面H.264编码,拿起MediaCodec咔咔一顿写,抱着一遍过的心态,然后果不其然的直接崩了,看了错误日志才发现,我的手机没有H.264的编码器,然后查了一下才发现,不同设备对MediaCodec硬编的支持参差不齐,于是索性换成软编,然后想起了FFmpeg,这次有了前车之鉴,先查了一下,发现FFmpeg默认没有集成这个编码器,需要引入libx264,好吧,革命事业曲折离奇,然后又是一顿踩坑之后,有了下面这段更新。
git clone git://git.videolan.org/x264.git
直接克隆下来,省时省力,然后进入文件目录下,创建待会儿要使用的shell脚本,如下:
#!/bin/bash
export NDK=/home/download/android-ndk-r15c
export SYSROOT=$NDK/platforms/android-9/arch-arm/
export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
export CPU=arm
export PREFIX=/usr/local/x264
export ADDI_CFLAGS="-marm"
./configure \
--prefix=$PREFIX \
--enable-shared \
--enable-static \
--disable-gpac \
--disable-cli \
--disable-asm \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--host=arm-linux \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make j8
make install
这个脚本和下面编译FFmpeg的脚本非常类似,主要一定要加入跨平台编译,不然编译出来的so文件是64位的,在android armeabi-v7a上没法使用。下面是重头戏,也是在整个编译过程中一直困扰我的,就是在编译过程中经常出现一个log2f,起初没注意,每次编译到这儿就会停顿一下,后面混编的时候一直找不到libx264也是这个原因(这是libx264 not found的一个隐藏得比较深的原因,还可能是其他原因)。我们需要把libx264文件夹下的config.h里面的#define HAVE_LOG2F 1关闭,也就是修改成:
#define HAVE_LOG2F 0
这下再make也就不会出现log2f了。
#!/bin/bash
export NDK=/home/download/android-ndk-r15c
export SYSROOT=$NDK/platforms/android-9/arch-arm/
export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
export PREFIX=/usr/local/ffmpeg/
# 下面这两句就是告诉FFmpeg x264相关文件的位置
export ADDI_CFLAGS="-I/usr/local/x264/include"
export ADDI_LDFLAGS="-L/usr/local/x264/lib"
./configure --target-os=android \
--prefix=$PREFIX --arch=arm \
--disable-doc \
--enable-shared \
--disable-static \
--disable-x86asm \
--disable-symver \
--enable-gpl \
# 开启libx264
--enable-libx264 \
--enable-encoder=libx264 \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make
make install
最后贴一个混编之后的so文件,虽然痛恨CSDN的这个积分制,但是别人的资源也需要积分才能下啊,所以允许我小赚两个积分,终于我也活成了我曾经讨厌的样子…
资源
-------------------------------------------我是三八线---------------------------------------------
这篇文章的重点在于编译FFmpeg库和Android studio 3.0中配置FFmpeg相关文件,所以如果是需要了解FFmpeg的实际应用的可以不用继续往下看了。因为我自己在整个过程中遇到很多的坑,所以我在整个过程中都用云笔记记录了下来,希望帮助到后来需要的同学,文章涉及的所有步骤我都是亲自尝试过的,如果有不正确的地方,烦请指正。
阿里云服务器 Ubuntu 16.04.3 LTS
android-ndk-r15c
国内可能无法访问,可以通过如下命令下载:
wget https://dl.google.com/android/repository/android-ndk-r15c-linux-x86_64.zip
如果需要下载其他版本的可以查阅这篇博客
下载完成后,使用如下命令解压:
sudo unzip android-ndk-r15c-linux-x86_64.zip
然后把ndk的路径加入环境变量,使用如下命令:
vim /etc/profile
然后在文件末尾加入ndk的路径,内容如下:
export ANDROID_NDK=/home/download/android-ndk-r15c
export PATH=$ANDROID_NDK:$PATH
然后使用如下命令,是环境变量生效:
source /etc/profile
NOTE:上面这个命令只是临时生效,重新打开一个终端就失效了,优点是不用重启系统就能马上生效
到官网下载ffmpeg,或者使用命令下载,我这里下载的是4.0.2的版本:
wget https://ffmpeg.org/releases/ffmpeg-4.0.2.tar.bz2
然后解压:
tar -jxvf ffmpeg-4.0.2.tar.bz2
进入ffmpeg解压完成后的根目录,为了方便多次编译,我们可以将编译的命令写入一个shell脚本中,以后每次更改编译参数重新运行脚本就可以了。
编译脚本:
#!/bin/bash
export NDK=/home/download/android-ndk-r15c
export SYSROOT=$NDK/platforms/android-9/arch-arm/
export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
export PREFIX=/usr/local/ffmpeg/
export ADDI_CFLAGS="-marm"
./configure --target-os=android \
--prefix=$PREFIX --arch=arm \
--disable-doc \
--enable-shared \
--disable-static \
--disable-x86asm \
--disable-symver \
--enable-gpl \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make
make install
上面编译完成之后会在编译脚本指定的PREFIX的路径下生成include、lib、share三个文件夹,include中是FFmpeg的方法的头文件,lib是生成的so动态链接库,share里面有一些FFmpeg的示例程序。
这里我们使用Android Studio 3.0来创建android工程,从as 2.2之后,我们开始用cmake编译jni,所以我们用CMakeLists.txt来代替Android.mk。略过使用as创建android ndk项目,创建完成后,我们开始配置。
在src/main下新建ffmpeg文件夹,然后将编译生成的包含所有ffmpeg头文件的整个include文件夹拷贝到该目录下,因为我们是编译的armeabi-v7a平台下的so库,所以在ffmpeg目录下新建armeabi-v7a文件夹,然后将所有so库拷贝到该文件夹下,最终目录结构如下:
# CMake的最低版本
cmake_minimum_required(VERSION 3.4.1)
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp )
find_library( log-lib
log )
set(JNI_LIBS_DIR ${CMAKE_SOURCE_DIR}/src/main/ffmpeg)
add_library(avutil
SHARED
IMPORTED )
set_target_properties(avutil
PROPERTIES IMPORTED_LOCATION
${JNI_LIBS_DIR}/${ANDROID_ABI}/libavutil.so )
add_library(swresample
SHARED
IMPORTED )
set_target_properties(swresample
PROPERTIES IMPORTED_LOCATION
${JNI_LIBS_DIR}/${ANDROID_ABI}/libswresample.so )
add_library(swscale
SHARED
IMPORTED )
set_target_properties(swscale
PROPERTIES IMPORTED_LOCATION
${JNI_LIBS_DIR}/${ANDROID_ABI}/libswscale.so )
add_library(avcodec
SHARED
IMPORTED )
set_target_properties(avcodec
PROPERTIES IMPORTED_LOCATION
${JNI_LIBS_DIR}/${ANDROID_ABI}/libavcodec.so )
add_library(avformat
SHARED
IMPORTED )
set_target_properties(avformat
PROPERTIES IMPORTED_LOCATION
${JNI_LIBS_DIR}/${ANDROID_ABI}/libavformat.so )
add_library(avfilter
SHARED
IMPORTED )
set_target_properties(avfilter
PROPERTIES IMPORTED_LOCATION
${JNI_LIBS_DIR}/${ANDROID_ABI}/libavfilter.so )
add_library(avdevice
SHARED
IMPORTED )
set_target_properties(avdevice
PROPERTIES IMPORTED_LOCATION
${JNI_LIBS_DIR}/${ANDROID_ABI}/libavdevice.so )
include_directories(src/main/ffmpeg/include)
target_link_libraries( native-lib
avutil
swresample
swscale
avcodec
avformat
avfilter
avdevice
android
${log-lib})
说明:
#include "libavcodec/avcodec.h"
#include "libavutil/dict.h"
#include "libavutil/log.h"
看到这里我们就能明白为什么一定要指定这个头文件的根目录了
android {
......
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-fexceptions"
abiFilters "armeabi-v7a"
}
}
}
// 加上这个,打包apk的时候才会将ffmpeg的so库打包进libs目录里面
sourceSets {
main {
jni.srcDirs = []
jniLibs.srcDirs = ['src/main/ffmpeg']
}
}
......
}
上面配置完成之后,我们就可以编写代码,然后在代码中使用ffmpeg的方法了。
在src/main/java的包下创建我们的java类,然后在类中编写native方法,然后通过System.loadLibrary加载我们需要的so库,如下所示:
static {
System.loadLibrary("avutil");
System.loadLibrary("swresample");
System.loadLibrary("swscale");
System.loadLibrary("avcodec");
System.loadLibrary("avformat");
System.loadLibrary("avfilter");
System.loadLibrary("avdevice");
}
public native void decode(String input, String output);
然后在命令行下,进入项目根目录app/src/main/java/目录下,使用命令生成头文件:
javah 包名.类名
然后我们在创建ndk项目时自动生成的cpp文件里面加入一个方法,方法名和参数就是我们刚生成的头文件里面声明的方法,示例如下:
#include
#include
#include
#include
#include "libyuv.h"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
}
// __android_log_print()需要头文件
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO, "sisyphus", FORMAT, ##__VA_ARGS__)
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR, "sisyphus", FORMAT, ##__VA_ARGS__)
extern "C"
JNIEXPORT jstring
JNICALL
Java_com_sisyphus_ffmpegdemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_sisyphus_ffmpegdemo_FFmpegPlayer_decode(JNIEnv *env, jobject instance, jstring input_,
jstring output_, jobject surface) {
const char *input = env->GetStringUTFChars(input_, 0);
const char *output = env->GetStringUTFChars(output_, 0);
av_register_all();
env->ReleaseStringUTFChars(input_, input);
env->ReleaseStringUTFChars(output_, output);
}
这里还有最后一个需要注意的地方就是:因为ffmpeg是用c语言编写的,我们是在cpp文件里面使用的,所以我们在include他的头文件时需要将头文件包含在extern “C” {}里面,如下:
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
}
不然的话,虽然我们在编写代码的时候不会报错,在最后编译的时候就会报错说我们使用的方法都没有定义。
点击菜单Build->Make Project编译项目,最后会在项目根目录的\app\build\intermediates\cmake\debug\obj\armeabi-v7a下生成一个so库。
打完收工