音视频开发一:编译Android端使用的FFmpeg库

        最近公司要做应用内更新的功能,但是包体积居然有45M,太大了。经过一系列的勘查,最后发现影响APP大小的主要是四个方面:播放器(基于ijkplayer)、视频压缩剪切(基于FFmpeg)、接入Flutter、无用的老代码。

         就当前项目而言,缩减包体积的方案分两步走:

         1、编译、裁剪FFmpeg库,把里面的视频剪切和视频压缩用到的代码分离出来。

         2、将ijkplayer播放器换成原生的播放器,因为目前公司的视频都是.mp4格式。

接下来的几天,小编将带领大家简单的学习下FFmpeg。如果想了解更多,可以看雷霄骅博士的博客。

前言:

以下信息来自http://www.apkbus.com/blog-664340-79314.html ,这篇博客中博主对JNI、CUP架构、交叉编译和 NDK的介绍很到位,顾在此引用,帮助读者更好的理解这些术语。再此感谢博主的阐释。

1.JNI

JNI,即 Java Native Interface ,是 Java 提供用来与其他语言通信的 api ,“其他语言”意味不止局限于 C 或 C++ ,也可以调用除 C 和 C++ 之外的语言,只是大多数情况下调用 C 或 C++ ; “通信”意味着 Java 和 其他语言之间可以相互调用,不止局限于 Java 调用其他语言,其他语言也可以主动调用 Java .

Java 虚拟机实现了跨平台特性, 无法很好的实现与操作系统相关的本地操作,而 C 或 C++ 可以,同时代表着 C 或 C++ 不具备 Java 的跨平台能力,那么当我们在程序中使用 JNI 功能时,就必须关注程序的平台可移植性, JNI 标准要求本地代码至少能工作在任何Java 虚拟机环境。

简而言之,跨平台的 Java 调用了不跨平台的 C/C++,使程序丧失了跨平台性,这就是 JNI 的副作用,所以可以不使用 JNI 时就尽量避免。而大多数不可避免的情况是:已存在用 C/C++ 写的程序/库或者 Java 语言不支持程序所要实现的特性,比如 ffmpeg 是由 C 编写的,则必须要通过 JNI 实现调用。

JNI 的实现步骤很简单,如下:

编写带有 native 方法的 Java 类

生成该类扩展名为 .h 的头文件

创建该头文件的 C/C++ 文件,实现 native 方法

将该 C/C++ 文件编译成动态链接库

在Java 程序中加载该动态链接库

动态链接库是一组源代码的模块,其中包含可供应用程序调用的函数。比如 Windows 下的 .dll 文件就是一种动态链接库,也就是说 Java 程序在 Windows 中运行,所需的动态链接库就是 .dll 文件; 如果 Java 程序在 Linux 中运行,所需的动态链接库就是 .so 文件 ,这里 JNI 的副作用已初见端倪,本来无视操作系统的 Java ,因为 JNI ,就要考虑运行环境是 Windows 还是 Linux 。除此之外,还要考虑 CPU 架构,这也是 Android 中使用 JNI 主要需考虑的 so 库兼容型问题。

对于 Java 程序来说,需要的仅仅是编译后的动态链接库,不需要 C/C++ 文件和 .h 头文件。Android 亦如此,网上很多 Android NDK 教程会把需要编译的 C/C++ 源码放入 Android 工程中,形成类似这样的工程结构:

这对新手来说可能会产生误导,误以为 Android 工程需要 C/C++ 文件或 .h 头文件或者其他的文件,要清楚的是, Android 工程需要的仅仅是编译后的 .so 库,所以我们可以在工程之外编译完后,只将 .so 库移植到工程中。那为什么大多数教程会把源码先移植到 Android 工程中再去编译呢?目的只有一个:节省目录的切换以及 .so 库的复制时间,实际上这些时间微乎其微,我推荐新手将编译操作于工程外进行,更易理解。

2.CPU 架构

我们都知道 CPU 是什么,那 CPU 架构到底是什么呢?回归到“架构”这个词本身含义,CPU 架构就是 CPU 的框架结构、设计方案,处理器厂商以某种架构为基础,生产自己的 CPU,就好比“总-分-总”是文章的一种架构,多篇文章可以都基于“总-分-总”架构。

常见的 CPU 架构有 x86、x86-64 以及 arm 等, x86-64 其实也是基于 x86 架构,只是在 x86 的基础上做了一些扩展,以支持 64 位程序的应用,常见的 Intel 、AMD 处理器都是基于 x86 架构的。

而 x86 架构主打的是 pc 端,对于移动端,arm 架构处于霸主地位 ,由于其体积小、低功耗、低成本、高性能的优点,被广泛应用在嵌入式系统中,目前大多数安卓、苹果手机的 CPU 都基于 arm 架构,此处所说的 arm 架构指 arm 系列架构,其中包括 ARMv5 、ARMv7 等等。

最后再看 Android 端 , Android 系统目前支持 ARMv5、ARMv7、ARMv8、 x86 、x86_64、MIPS 以及 MIPS64 共七种 CPU 架构,也就是说除此之外其他 CPU 架构的硬件并不能运行 Android 系统。

3.交叉编译

在某个平台上,编译该平台的可执行程序,叫做本地编译,比如在 Windows 平台上编译 Windows 自身的可执行程序;在 x86 平台上,编译 x86 平台自身的可执行程序。

在某个平台上,编译另一种平台的可执行程序,就是交叉编译,比如在 x86 平台上,编译 arm 平台的可执行程序,这也是 Android 端使用最多的交叉编译类型。

在交叉编译时,由于主机与目标的体系架构、环境不同,所以交叉编译比本地编译复杂很多,需要一些工具来解决主机与目标不同特性的问题,这些工具构成的工具集就叫做交叉编译链。

既然交叉编译比本地复杂很多,那为什么不使用本地编译,比如在 arm 平台编译 arm 平台的可执行程序呢?这是因为目标平台存储空间和计算能力通常是有限的,而编译过程需要较大的存储空间和较快的计算能力,但目标平台无法提供。

4.NDK

我们需要的是 arm 平台的动态库,而这一编译过程往往是在 x86 平台上进行,所以属于交叉编译,需要交叉编译链来实现,所以 NDK(Native Development Kit )中提供了交叉编译链,方便开发。

Android 中包括七种 CPU 架构,NDK 中自然就有与之对应的交叉编译链,以下是 Android 官网对此的表格描述:

架构工具链名称

基于 ARMarm-linux-androideabi- < gcc-version >

基于 ARM64aarch64-linux-android- < gcc-version >

基于 x86x86- < gcc-version >

基于 X86-64x86_64- < gcc-version >

基于 MIPSmipsel-linux-android- < gcc-version >

基于 MIPS64mips64el-linux-android– < gcc-version >

除此之外,NDK 还提供了一些原生标头和共享库文件,包括 C/C++ 支持库、从 C/C++ 代码中可以向 Android 系统输出日志的 < android/log.h > 等等,可以点击这里了解更多,总之,NDK 是用来帮助我们实现交叉编译的工具。

在实际使用时,比较重要的是 Android.mk 语法,内容并不多,但你必须了解,不然只复制别人的配置很容易出错,关键是你无法真正的掌握这部分知识,而最好的学习方法就是仔细阅读几遍 Android.mk 官网教程 。

另外还需要了解什么是 ABI ,ABI 即 application binary interface ,应用程序二进制接口,顾名思义,“二进制接口”说明这是程序与系统之间的底层接口,它定义了程序如何与系统交互。我们应该指定每个 CPU 架构所对应的 ABI,所以 Android 中就出现了 armeabi 、armeabi-v7a、arm64-v8a、x86、x86_64、mips 以及 mips64 目录来区分不同的 ABI ,我们将编译好的动态库放入对应 CPU 架构的 ABI 目录中就可以了。

掌握了以上知识点,才能知道 Android 集成 FFmpeg本质上是在做什么,为什么要这样做。不只是集成 FFmpeg,这些知识对于任何底层库的集成都是通用、必要的。

 

一:FFmpeg简介。

1、FFmpeg官网地址:http://ffmpeg.org 。

2、FFmpeg的Github项目地址是:  https://github.com/FFmpeg/FFmpeg 。

3、版本:static、shared、dev

static(静态库版本):

可以直接在命令行中使用,里面包含三个应用程序:ffmpeg.exe、ffplayer.exe、ffprobe.exe,每个exe都比较大,因为相关的dell文件都编译到了exe中。

shared(动态库版本):

可以直接在命令行中使用,里面包含三个应用程序:ffmpeg.exe、ffplayer.exe、ffprobe.exe,每个exe都比较小,因为相关的dell文件没有编译到exe文件中,运行时程序会自动去寻找相关的dell文件。

ffmepg.exe:用于进行视频处理。
ffplayer.exe:用于播放视频。
ffprobe.exe:用于查看文件格式。

dev(开发者版本):不可以在命令行中直接运行,里面不包含exe文件。里面包含了库文件和头文件

音视频开发一:编译Android端使用的FFmpeg库_第1张图片

libavcodec:用于各种类型声音/图像编解码;
libavdevice:用于音视频数据采集和渲染等功能的设备相关;
libavfilter:包含多媒体处理常用的滤镜功能;
libavformat:包含多种多媒体容器格式的封装、解封装工具;
libavutil:包含一些公共的工具函数;
libpostproc:用于后期效果处理;
libswresample:用于音频重采样和格式转换等功能;
libswscale:用于视频场景比例缩放、色彩映射转换;

二、编译FFmpeg源码

编译环境:macOS

ndk版本:android-ndk-r17c

ffmpeg版本:3.4.6

注:编译请严格按照本篇博客中使用的NDK版本和ffmpeg版本,因为不同的版本会出现不同的错误。

1、下载NDK和配置环境变量。NDK下载地址   官网

以下为NDK环境变量配置

export ANDROID_NDK=/Users/****/Documents/Android/NDK/android-ndk-r17c
export PATH=$PATH:$ANDROID_NDK

注:请自行将 ANDROID_NDK 改为自己的NDK路径名字。

2、下载ffmpeg代码。下载地址

音视频开发一:编译Android端使用的FFmpeg库_第2张图片

3、将下载的压缩包双击解压。或者使用unzip命令解压,切记不要用三方的解压软件,因为有坑。

4、进入刚才解压的文件夹中,打开configure文件,用vim或者是文本文件软件。

定位到:(注:不同ffmpeg版本,所在的行数是不一样的)

将这四行配置改为:(不然生成的文件如:libavcodec.so.5.100.1,Android平台不能识别这种格式的文件,Android只能识别.so结尾的文件)

原始配置:
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)'

5、编写执行脚本文件。

在解压后文件的根目录下新建一个build_android.sh文件(当然这个“build_android“名字可以任意取,但是我知道你肯定会起英文名字的,对吧?)

在terminal中进入到ffmpeg根目录下,执行  ./build_android.sh命令。

在里面加上如下配置:

#!/bin/sh
NDK=/Users/****/Documents/Android/NDK/android-ndk-r12b # 修改成自己本地的ndk路径。
SYSROOT=$NDK/platforms/android-21/arch-arm
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
function build #定义方法名字
{
./configure \
--prefix=$PREFIX \
--enable-shared \
--enable-static \
--enable-doc \
--enable-ffmpeg \
--enable-ffplay \
--enable-ffprobe \
--enable-ffserver \
--enable-avdevice \
--enable-doc \
--enable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"

build #执行这个方法

注:

1、这个里面的enable可以改为disable,就是不用哪个就把哪个改为disable,因为我们这次是实验编译,所以小编就将他们全部打开了,看看会不会有什么问题。之后我们会将很多配置给disable掉,不然包会很大。

2、上面的NDK路径需要替换成你自己的路径。

6、参数含义:

target-os:target系统,Android 内核为 Linux ,故在此指定为 linux ;如果要编译的目标系统为 ios ,则指定为 darwin
prefix:编译生成目录
arch:CPU架构
enable-shared:表示生成的是动态库
disable-static:不生成静态库
disable-yasm:不使用yasm
disable-ffmpeg:不生成ffmpeg软件
disable-ffplay:不生成ffplay播放器软件
disable-ffprobe:不生成ffprobe信息查看软件
disable-ffsever:不生成ffserver(主要用于与服务器通信)
disable-doc:不生成doc文档
enable-cross-compile:使用交叉编译
cross-prefix:交叉编译链目录
sysroot:指定系统目录
extra-cflags:添加参数,使得动态库可以被加载使用

三、遇到的问题。

以下为小编在编译时遇到的问题:

1、

解决办法:将libavcodec包下的aaccoder.c文件的B0改为b0,B1改为b1(当然改为自定义的名字也行,只要不重复就行了)

2、

音视频开发一:编译Android端使用的FFmpeg库_第3张图片

解决办法::将libavcodec包下的hevc_mvs.c文件中的变量B0改成b0,xB0改成xb0,yB0改成yb0

3、

音视频开发一:编译Android端使用的FFmpeg库_第4张图片

解决办法::将libavcodec包下的opus_pvq.c文件中的变量B0改成b0.

四、将编译后的文件应用到Android工程

1、编译成功后,ffmpeg工程的根目录下会生成一个android文件夹,其中include是相关的头文件,lib中是编译结果的静态库。

音视频开发一:编译Android端使用的FFmpeg库_第5张图片

注:因为我们的build_android.sh文件中的配置了 --enable-static     --enable-shared   所以这个lib文件夹下既有静态库又有动态库。.so文件为动态库,.a文件为静态库。各位可以改变下配置,看看得到的文件有什么不同。

2、当目前为止,我们的ffmpeg已经全部编译完成,证明我们的工程是可以。但是下面为了方便集成,我们更改build_android.sh这个文件的配置为如下:

#!/bin/sh
NDK=/Users/****/Documents/Android/NDK/android-ndk-r12b # 修改成自己本地的ndk路径。
SYSROOT=$NDK/platforms/android-21/arch-arm
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
function build_one
{
./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--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 \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"

build_one

然后在terminal中重新运行 ./build_android.sh 命令,等运行完毕后。在工程下的android 文件下的arm文件夹下:

音视频开发一:编译Android端使用的FFmpeg库_第6张图片

3、编译Android工程可使用的so库。

到目前位置ffmpeg的编译就完成了,但是Android工程是不能直接使用的,接下来我们将编译出一个Android可以直接使用的so库。这里需要使用JNI相关的操作。你可以将这些so库拷贝到一个Android工程中,然后创建含有native方法的java类,再创建.mk文件,用NDK进行编译,也可以直接在工程外用NDK命令编译好在拷贝到工程中直接使用。

下面我将在工程外编译好,然后再拷贝到工程中使用。

1.创建一个这样层级的文件夹ndkBuild -> jni -> com -> jni,在第二个jni文件下创建一个java类FFmpegJni:

package com.jni;

public class FFmpegJni {


    public static native int run(String[] commands);

}

2.然后在terminal中进入第一个jni目录,运行 javah -classpath . com.jni.FFmpegJni 命令。这时会生成一个com_jni_FFmpegJni.h头文件如下。(如果失败,请自行百度”java生成头文件“)

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_jni_FFmpegJni */

#ifndef _Included_com_jni_FFmpegJni
#define _Included_com_jni_FFmpegJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_jni_FFmpegJni
 * Method:    run
 * Signature: ([Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_jni_FFmpegJni_run
  (JNIEnv *, jclass, jobjectArray);

#ifdef __cplusplus
}
#endif
#endif

3.将com_jni_FFmpegJni.h拷贝到第一个jni目录下,如果已经在了,则忽略这一步。

4.在第一个jni目录下创建一个com_jni_FFmpegJni.h的.c文件,名字为com_jni_FFmpegJni.c  :

#include "android_log.h"
#include "com_jni_FFmpegJni.h"
#include "ffmpeg.h"
 
JNIEXPORT jint JNICALL Java_com_jni_FFmpegJni_run(JNIEnv *env, jclass obj, jobjectArray commands) {
    int argc = (*env)->GetArrayLength(env, commands);
    char *argv[argc];
    int i;
    for (i = 0; i < argc; i++) {
        jstring js = (jstring) (*env)->GetObjectArrayElement(env, commands, i);
        argv[i] = (char*) (*env)->GetStringUTFChars(env, js, 0);
    }
    LOGD("----------begin---------");
    return main(argc, argv);
}

5.这个时候我们在第一个jni文件夹下创建一个android_log.h文件,用于在AndroidStudio的logcat中打印日志:

#ifdef ANDROID
#include 
#ifndef LOG_TAG
#define  MY_TAG   "MYTAG"
#define  AV_TAG   "AVLOG"
#endif
#define LOGE(format, ...)  __android_log_print(ANDROID_LOG_ERROR, MY_TAG, format, ##__VA_ARGS__)
#define LOGD(format, ...)  __android_log_print(ANDROID_LOG_DEBUG,  MY_TAG, format, ##__VA_ARGS__)
#define  XLOGD(...)  __android_log_print(ANDROID_LOG_INFO,AV_TAG,__VA_ARGS__)
#define  XLOGE(...)  __android_log_print(ANDROID_LOG_ERROR,AV_TAG,__VA_ARGS__)
#else
#define LOGE(format, ...)  printf(MY_TAG format "\n", ##__VA_ARGS__)
#define LOGD(format, ...)  printf(MY_TAG format "\n", ##__VA_ARGS__)
#define XLOGE(format, ...)  fprintf(stdout, AV_TAG ": " format "\n", ##__VA_ARGS__)
#define XLOGI(format, ...)  fprintf(stderr, AV_TAG ": " format "\n", ##__VA_ARGS__)
#endif

6.在第一jni下创建一个android.mk文件。

LOCAL_PATH:= $(call my-dir)

INCLUDE_PATH:=/Users/***/Documents/Documents/Projects/FFmpeg/ffmpeg-3.4.6/android/arm/include

FFMPEG_LIB_PATH:=/Users/***/Documents/Documents/Projects/FFmpeg/ffmpeg-3.4.6/android/arm/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 := ffmpeg

LOCAL_SRC_FILES := com_jni_FFmpegJni.c \
	                cmdutils.c \
                  	ffmpeg.c \
                  	ffmpeg_opt.c \
                  	ffmpeg_hw.c \
                  	ffmpeg_filter.c   

LOCAL_C_INCLUDES := /Users/***/Documents/Documents/Projects/FFmpeg/ffmpeg-3.4.6

LOCAL_LDLIBS := -lm -llog

LOCAL_SHARED_LIBRARIES := libavcodec libavfilter libavformat libavutil libswresample libswscale

include $(BUILD_SHARED_LIBRARY)

注:INCLUDE_PATH  、  FFMPEG_LIB_PATH  、 LOCAL_C_INCLUDES 替换成自己的路径。

7.在第一个jni文件夹下创建一个application.mk文件。

APP_ABI := armeabi-v7a
APP_PLATFORM=android-16

8.将ffmpeg源码中的 ffmpeg.h 、 ffmpeg.c 、 cmdutils.h 、 cmdutils.c、  ffmpeg_filter.c 、 ffmpeg_opt.c、  ffmpeg_hw.c、  cmdutils_common_opts.h  这8个文件拷贝到第一个jni文件夹下。此时第一级 jni 文件夹下有如下文件:

音视频开发一:编译Android端使用的FFmpeg库_第7张图片

9.下面我们来改造这几个拷贝过来的文件:

1)打开 ffmpeg.c 文件

¥1. 在 ffmpeg.c 文件中引入日志文件 android_log.h .

#include "android_log.h"

¥2.  修改 log_callback_null 方法为如下:

static void log_callback_null(void *ptr, int level, const char *fmt, va_list vl)
{
    static int print_prefix = 1;
    static int count;
    static char prev[1024];
    char line[1024];
    static int is_atty;
    av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);
    strcpy(prev, line);
    if (level <= AV_LOG_WARNING){
        XLOGE("%s", line);
    }else{
        XLOGD("%s", line);
    }
}

¥3. 在main方法中假如一下设置:

av_log_set_callback(log_callback_null);

音视频开发一:编译Android端使用的FFmpeg库_第8张图片

¥4. 在 ffmpeg_cleanup 方法末尾增加如下代码:


    nb_filtergraphs = 0;
    nb_output_files = 0;
    nb_output_streams = 0;
    nb_input_files = 0;
    nb_input_streams = 0;

¥5. 在 main 方法的最后调用 ffmpeg_cleanup(0) 方法。

ffmpeg_cleanup(0);

音视频开发一:编译Android端使用的FFmpeg库_第9张图片2)打开 cmdutils.c 文件

¥1.  将exit_program(int ret)方法改为如下:(注意返回值的改变)

int exit_program(int ret)
{
//    if (program_exit)
//        program_exit(ret);

//    exit(ret);
    return ret;
}

¥2.  改变 cmdutils.h 头文件中的exit_program() 方法为如下:

int exit_program(int ret);

10. 在terminal中进入一级jni文件夹,运行 ndk-build 命令。

这时在ndkBuild文件目录下(也就是和一级jni文件同级)生成了libs 和 obj文件夹。

音视频开发一:编译Android端使用的FFmpeg库_第10张图片

到目前为止,已经生成了可以在Android工程中直接使用的so库。

11. 将so库接入Android工程。

新建一个Android工程,取名FFmpeg(名字随意)。

¥1.  新建一个libs文件夹,在libs下新建armeabi文件夹,将刚才生成的libs下的so库拷贝到armeabi下:

音视频开发一:编译Android端使用的FFmpeg库_第11张图片

¥2.  将FFmpegJni类拷贝到工程中,在FFmpegJni中添加如下代码:

static {
        System.loadLibrary("avutil-55");
        System.loadLibrary("avcodec-57");
        System.loadLibrary("avformat-57");
        System.loadLibrary("swresample-2");
        System.loadLibrary("swscale-4");
        System.loadLibrary("avfilter-6");
        System.loadLibrary("ffmpeg");
    }

注意文件目录:com.jni.FFmpegJni.java

音视频开发一:编译Android端使用的FFmpeg库_第12张图片

¥3.  在module的gradle文件的android节点下加上如下代码:

sourceSets{
        main{
            jniLibs.srcDirs=["libs"]
        }
    }

音视频开发一:编译Android端使用的FFmpeg库_第13张图片

¥4. 在清单文件中加入读取文件的权限。


¥5. 在MainActivity中加入剪切视频的代码:

(注意:1、必须有读取文件的权限。2、文件根目录下必须有一个test1.mp4的视频文件)

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        if ( ActivityCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) {
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE }, 1);
        }
    }
 
public void click(View view) {
        /**
         * 在执行前必须有读取文件的权限
         */
        String path = Environment.getExternalStorageDirectory().getPath();
        String dir = path + "/test1.mp4";

        String[] commands = new String[10];
        commands[0] = "ffmpeg";
        commands[1] = "-i";
        commands[2] = dir;
        commands[3] = "-ss";
        commands[4] = "00:00:05";
        commands[5] = "-t";
        commands[6] = "00:00:20";
        commands[7] = "-acodec";
        commands[8] = "copy";
        commands[9] = path + "/cut_video_" + System.currentTimeMillis() + "_.avi";

        int result = FFmpegJni.run(commands);
        Log.d(TAG, "result:" + result);
    }

音视频开发一:编译Android端使用的FFmpeg库_第14张图片

 

到目前为止,我们已经编译了一个可以运行ffmpeg命令的so库,并把他集成到了工程中。下次,我将带领大家,给ffmpg增加成功、失败和进度回调功能。

你可能感兴趣的:(FFmpeg,Android,ffmpeg,音视频)