FFmpeg编译成Android动态库

项目需要,网上也有现成的FFmpeg Android动态库,但是本着亲力亲为的宗旨,做了不断地尝试,最终也是成功了,在此做一个笔记,以备日后查阅。

附上给我帮助的资料链接:
王英豪大神的博客
雷霄骅大神的博客

因为FFmpeg,虽未谋面,但是有了第一个年龄相仿的励志偶像,那就是雷霄骅,给了我无限的正能量,努力!

我们开始正题,上边的两位大神的博客,整个编译过程描述的非常详细了,但是因为我第一次尝试,对c的编译不是很熟悉,所以碰到的很多比较低级细节问题,上述博客中没有提及,在此做一下记录。

1、Android 端集成 FFmpeg 需要掌握哪些基础知识呢?个人认为以下内容是需要了解的:

JNI
CPU架构
交叉编译
NDK
FFmpeg 简介

这些内容,王英豪大神描述的很清楚,在开始编译之前,需要细细琢磨,理解。

2、我的编译环境:

Linux 环境(Ubuntu 14.04 ,x86_64)
NDK 版本(android-ndk-r13b)
FFmpeg 版本(ffmpeg-3.3.7): 官网下载链接:https://ffmpeg.org/download.html

3、工具,环境准备完毕,我们开始编译动态库
3.1 将FFmpeg源码解压到文件夹中
这里写图片描述
解压方法:tar -xzvf ffmpeg-3.3.7.tar.gz (解压到当前文件夹)
3.2 修改FFmpeg 配置文件
进入解压后的 ffmpeg-3.3.7 文件夹

vim configure 

替换操作(建议用 #号注释掉被替换部分):

#注释掉原来的
#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)'

3.3 执行配置文件:

在FFmpeg根目录下,执行 ./configure
如果不执行这句,后边执行脚本时,可能会出现某些配置项不存在的提示

3.4 下载并解压ndk
ndk 是什么,用来干什么,看文章开头王英豪大神链接。
下载:
ndk 下载可能需要翻墙,为了方便,我在文章末尾会附上资源链接。
解压:
下载下来之后是 .zip 文件,所以我们使用以下方法解压:

unzip *.zip

3.5 编写编译脚本
网上有好多脚本,我尝试过几个,觉得雷霄骅这份不错,我使用这份:
FFmpeg类库完整功能脚本
下面这个脚本可以生成一套功能完整,体积比较大的类库。

注意事项:
1、路径相关的信息,一定认真比对,保证无误
2、因为编辑器的原因,直接复制粘贴,脚本每一行最后都会有多余的空格,执行之前,一定要将每一行最后的空格删除掉,否则会出现很多莫名的错误。
3、有时候也会出现一些权限的问题,这个,赋予合适的权限就好了。
4、我觉得最好也不要有中文

make clean
#自己的ndk路径(相对路径或绝对路径都可以)
export NDK=/home/shoppingchen/work/ffmpeg_for_android/android-ndk-r13b
export PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt
# 支持的最低 android 版本
export PLATFORM=$NDK/platforms/android-14/arch-arm
#最终的库生成位置为上一级目录的 simplefflib
export PREFIX=../simplefflib
build_one(){
./configure --target-os=linux --prefix=$PREFIX \
--enable-cross-compile \
--enable-runtime-cpudetect \
--disable-asm \
--arch=arm \
--cc=$PREBUILT/linux-x86_64/bin/arm-linux-androideabi-gcc \
--cross-prefix=$PREBUILT/linux-x86_64/bin/arm-linux-androideabi- \
--disable-stripping \
--nm=$PREBUILT/linux-x86_64/bin/arm-linux-androideabi-nm \
--sysroot=$PLATFORM \
--enable-gpl --enable-shared --disable-static --enable-small \
--disable-ffprobe --disable-ffplay --disable-ffmpeg --disable-ffserver --disable-debug \
--extra-cflags="-fPIC -DANDROID -D__thumb__ -mthumb -Wfatal-errors -Wno-deprecated -mfloat-abi=softfp -marm -march=armv7-a"
}

#make
#make install
build_one

在这儿,有一个建议,就是 make 命令和make install 命令,对正在尝试的人来说,不要把他写到脚本中,建议执行完脚本之后再手动执行比较好。因为我们在执行脚本或命令之后,需要密切关注屏幕上打出来的log,如果有异常,应立即停止编译,对log进行分析。
在我自己编译过程中,刚开始,直接复制脚本执行,完成之后是不会出现 fail 或者 success 的提示,在此处浪费了很多时间。
3.6、执行完make 和 make install 命令之后,我们就可以看到生成的 simplefflib 文件夹,下图所示:
这里写图片描述
如果没有该文件夹生成,那么详细检查上一步操作,详细阅读log,看哪儿出了异常,无外乎注意事项中的三点。

如果一切顺利,那么到这儿,我们的动态库就完成了。但是要使用到 Android 项目中,我们还需要 jni
5、搞定 jni
5.1 jni 步骤

编写带有 native 方法的 Java 类
生成该类扩展名为 .h 的头文件
创建该头文件的 C/C++ 文件,实现 native 方法
将该 C/C++ 文件编译成动态链接库
在Java 程序中加载该动态链接库

5.2 编写带有 native 方法的 Java 类

package com.jni;
public class FFmpeg {
    public static native void run();
}

位置可以是任何地方,我是在 ubantu下边,看图
这里写图片描述
就是创建了类似结构的文件夹树结构
5.3 生成该类扩展名为 .h 的头文件
在图中 ffmpeg_for_android 文件夹下执行以下命令:

javah -classpath .  com.jni.FFmpeg

执行完之后,我们就发现在当前目录下边,生成了 com_jni_FFmpeg.h 如下图所示
这里写图片描述
5.4 创建该头文件的 C/C++ 文件,实现 native 方法
此处建议,新建一个目录来编译jni 的动态库,我这儿新建了一个 ndkBuild 目录,然后把上一步生成的头文件复制到该目录下,并在该目录下创建 com_jni_FFmpeg.c 文件,文件内容如下:

#include <android/log.h>
#include "com_jni_FFmpeg.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
JNIEXPORT void JNICALL Java_com_jni_FFmpeg_run(JNIEnv *env, jclass obj) {

    char info[40000] = {0};
    av_register_all();
    AVCodec *c_temp = av_codec_next(NULL);
    while(c_temp != NULL){
       if(c_temp->decode!=NULL){
          sprintf(info,"%s[Dec]",info);
       }else{
          sprintf(info,"%s[Enc]",info);
       }
       switch(c_temp->type){
        case AVMEDIA_TYPE_VIDEO:
          sprintf(info,"%s[Video]",info);
          break;
        case AVMEDIA_TYPE_AUDIO:
          sprintf(info,"%s[Audio]",info);
          break;
        default:
          sprintf(info,"%s[Other]",info);
          break;
       }
       sprintf(info,"%s[%10s]\n",info,c_temp->name);
       c_temp=c_temp->next;
    }
__android_log_print(ANDROID_LOG_INFO,"myTag","info:\n%s",info);
}

这段程序用于输出 FFmpeg 支持的编解码信息,通过 < android/log.h > 的 __android_log_print 方法可以直接将信息输出到 Android Studio 的 logcat 。
5.5 将该 C/C++ 文件编译成动态链接库
首先在 jni 目录下创建 Application.mk 文件 :

APP_ABI := armeabi
APP_PLATFORM=android-14

APP_ABI 表示编译生成 armeabi 架构的 so 库,APP_PLATFORM 表示最低支持的 Android 版本。

然后在 jni 目录下创建 Android.mk 文件:

LOCAL_PATH:= $(call my-dir)
INCLUDE_PATH:=../jniLibs/include
FFMPEG_LIB_PATH:=../jniLibs/lib

include $(CLEAR_VARS)
LOCAL_MODULE:= libavcodec
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavcodec-57.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libavformat
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavformat-57.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libswscale
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libswscale-4.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libavutil
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavutil-55.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libavfilter
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavfilter-6.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libswresample
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libswresample-2.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libpostproc
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libpostproc-54.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavdevice
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavdevice-57.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg
LOCAL_SRC_FILES := com_jni_FFmpeg.c 
LOCAL_C_INCLUDES := /home/shoppingchen/work/ffmpeg_for_android/ffmpeg-3.3.7
LOCAL_LDLIBS := -lm -llog
LOCAL_SHARED_LIBRARIES := libavcodec libavfilter libavformat libavutil libswresample libswscale libavdevice
include $(BUILD_SHARED_LIBRARY)

注意:Android.mk 文件中
INCLUDE_PATH:=../jniLibs/include
FFMPEG_LIB_PATH:=../jniLibs/lib
这两个路径为,我们第四步中生成的两个文件夹的路径,看图
这里写图片描述
LOCAL_C_INCLUDES := /home/shoppingchen/work/ffmpeg_for_android/ffmpeg-3.3.7
这个路径为 ffmpeg 源码路径,在3.1中提到过

然后在 ndkBuild 目录下边执行命令:

ndk-build

这个命令其实就是一个可执行文件,这个文件在 3.4 提到的ndk 解压根目录下边,
如果执行上述命令系统提示找不到,说明我们没有配置环境变量,此时我们有两种选择
①配置环境变量(自行百度)
②通过路径来直接调用(我是通过相对路径找到这个可执行文件来调用的)

执行完毕后,在当前目录下边,会生成两个文件夹,目录结构如下所示:
这里写图片描述
5.6 在Java 程序中加载该动态链接库
5.6.1 在Android_studio 中新建一个 Android 工程
5.6.2 将5.5 中生成的libs 下的armeabi-v7a 文件夹复制到工程的 libs 目录下边
这里写图片描述
5.6.3 将我们写的 FFmpeg.java 文件复制到下图中位置(注意,路径不能错):
这里写图片描述
5.6.4 尝试进行编译
这里写图片描述
如果编译不通过,提示说run 方法找不到,按下图操作
去掉 Android NDK Support 勾选
这里写图片描述

到此,我们Android 动态库的编译就完成了,本博客是参考雷霄骅和王英豪两位大神博客而写,记录了一下自己编译的“坎坷“过程,如有侵犯版权,请告知,如有错误之处,请告知,非常感谢

猜你喜欢

转载自blog.csdn.net/chenxiaoping1993/article/details/80306928
今日推荐