专栏首页音视频修炼路Android FFmpeg 编译和集成(十四)
原创

Android FFmpeg 编译和集成(十四)

直接使用FFmpeg

因为FFmpeg是一套集录制、转换以及流化音视频的完整的跨平台解决方案,如果我们开发者想直接在自己开发的Android应用中使用ffmpeg的提供的功能,则需要引入so静态库,比如制作一些音视频编辑应用。

交叉编译生成,so动态库

编译工具链

对于C/C++的编译,通常有两个工具 GCC 和 CLANG 。

如果有用过c/c++的开发者应该都知道GCC,是一个编译工具,不仅可以编译C/C++,也可以编译Java,Object-C,Go等语言。

CLANG 则是更高效的C/C++编译工具,Google在ndk 17 以后,把 GCC 移除了,全面推行使用 CLANG 。 所以网上一些比较旧的ffmpeg编译教程没来得及更新,我们容易踩坑,导致ffmpeg源码编译失败。

使用CLANG编译FFmpeg

笔者的本文用的编译环境是:

编译机器: Mac OS Big Sur Version 11.1

NDK版本:android-ndk-r21d-darwin-x86_64.zip

测试机:华为Mate 30

FFmpeg版本:目前最新版本4.2.2

本文是使用目前最新的 NDK r21d 版本来编译。

NDK 下载地址:Android-NDK

NDK目录

编译工具链目录:
toolchains/llvm/prebuilt/darwin-x86_64/bin

交叉编译环境目录:
toolchains/llvm/prebuilt/darwin-x86_64/sysroot

如下图所示

因为笔者的测试机器是华为的Mate 30,所以选择 CPU 架构 aarch64,Android版本 29,我们可以按照自己的实际需求选择编译工具

aarch64-linux-androideabi21-clang
aarch64-linux-androideabi21-clang++

下载FFmpeg源码

FFmpeg官网下载,直接DownLoad即可。 本文使用的是目前最新的版本 ffmpeg-4.2.2。 下载解压源码后,进入根目录,找到congfigure 的文件,它是一个shell脚本,用于生成一些 FFmpeg 编译需要的配置文件。这个文件非常重要,FFmpeg 的编译配置是依赖它完成的。

修改 configure 脚本 (可以用Subline打开)

我们需要修改ffmpeg-4.2.2 根目录下的 configure 文件,实际上是因为Google 在新版ndk把 GCC 移除了,全面推行使用 CLANG,所以我们需要把编译工具配置进行修改。修改流程如下:

1.新增 cross_prefix_clang 参数

我们可以搜索 CMDLINE_SET ,可以找到以下代码,然后新增一个命令行选项:cross_prefix_clang

2.修改编译工具路径设置

我们可以搜索 ar_default="${cross_prefix}${ar_default}" , 找到以下代码:

ar_default="${cross_prefix}${ar_default}"

cc_default="${cross_prefix}${cc_default}"

cxx_default="${cross_prefix}${cxx_default}"

nm_default="${cross_prefix}${nm_default}"

pkg_config_default="${cross_prefix}${pkg_config_default}"

将中间两行修改为:

ar_default="${cross_prefix}${ar_default}"
#------------------------------------------------
cc_default="${cross_prefix_clang}${cc_default}"
cxx_default="${cross_prefix_clang}${cxx_default}"
#------------------------------------------------
nm_default="${cross_prefix}${nm_default}"
pkg_config_default="${cross_prefix}${pkg_config_default}"

3.新建编译配置脚本

在 ffmpeg-4.2.2 根目录下新建 shell 脚本,命名为: build_android_clang.sh,脚本代码如下:

#!/bin/bash
set -x
# 目标Android版本
API=29
ARCH=arm64
CPU=armv8-a
TOOL_CPU_NAME=aarch64
#so库输出目录
OUTPUT=/Users/pj1053/Downloads/ffmpeg_source/ffmpeg/android/$CPU
# NDK的路径,根据自己的NDK位置进行设置
NDK=/Users/pj1053/Downloads/android-ndk-r21d
# 编译工具链路径
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64
# 编译环境
SYSROOT=$TOOLCHAIN/sysroot

TOOL_PREFIX="$TOOLCHAIN/bin/$TOOL_CPU_NAME-linux-android"
 
CC="$TOOL_PREFIX$API-clang"
CXX="$TOOL_PREFIX$API-clang++"
OPTIMIZE_CFLAGS="-march=$CPU"
function build
{
  ./configure \
  --prefix=$OUTPUT \
  --target-os=android \
  --arch=$ARCH  \
  --cpu=$CPU \
  --disable-asm \
  --enable-neon \
  --enable-cross-compile \
  --enable-shared \
  --disable-static \
  --disable-doc \
  --disable-ffplay \
  --disable-ffprobe \
  --disable-symver \
  --disable-ffmpeg \
  --cc=$CC \
  --cxx=$CXX \
  --sysroot=$SYSROOT \
  --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \

  make clean all
  # 这里是定义用几个CPU编译
  make -j8
  make install
}
build

其中有两个地方要修改成自己的ffmpeg源码项目和NDK编译工具的本地路径,如下图:

4.添加脚本权限

编写完脚本文件,需要添加权限。

chmod +x  build_android_clang.sh

5.脚本执行

添加脚本权限之后,我们可以直接运行脚本。

 ./build_android_clang.sh

等待编译完成,将会在 当前文件夹的/android/armv8-a目录下得到 include 和 lib 两个目录,分别是 头文件 和 so库文件,就是我们需要编译生成的ffmpeg静态库文件和头文件。

使用FFmpeg so动态库

1.使用Android Studio 创建Native C++工程

新建项目的时候有一个选项是选择Native C++的模板

点击next,配置项目的信息

点击next,选择使用哪种C++标准,选择Toolchain Default会使用默认的CMake设置即可

点击finish即可完成工程的创建。

2.工程结构

这时候主工程目录下会有cpp文件夹

cpp文件夹:存放C/C++代码文件,native-lib.cpp文件默认生成的;

cpp文件夹下有两个文件,一个是native-lib.cpp文件,一个是CMakeLists.txt文件。CMakeLists.txt文件是cmake脚本配置文件,cmake会根据该脚本文件中的指令去编译相关的C/C++源文件,并将编译后产物生成共享库或静态块,然后Gradle将其打包到APK中。

3.Java调用native层c/c++代码

在MainActivity.java,static{}语句中使用了加载so库,在类加载中只执行一次。

 static {
        System.loadLibrary("native-lib");
    }

然后,编写了原生的函数,函数名中要带有native。

public native String stringFromJNI();

最后,编写相对应的c函数,注意函数名的构成Java_com_pengjie0668_demo_myapplication_MainActivity_stringFromJNI加上包名、类型、方法名的下划线连成一起。

注意:要按照jni的规范定义方法(Java包名类名native方法名,其中包名中的点用代替)

native-lib.cpp文件

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_pengjie0668_demo_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

4.引入 FFmpeg so库

  • 添加ffmpeg so库文件

首先,在 app/src/main/ 目录下,新建文件夹,并命名为 jniLibs ,接着,在 jniLibs 目录下,新建 arm64-v8a 目录,

最后把 FFmpeg 编译得到的所有 so 库粘贴到 arm64-v8a 目录。如图:

  • 添加 FFmpeg so库的头文件

在 cpp 目录下,新建 ffmpeg 目录,然后把编译时生成的 include 文件粘贴进来。

  • 配置CMakeLists.txt

上面已经把 so 和 头文件 放置到对应的目录中了,但是编译器是不会把它们编译、链接、并打包到 Apk 中的,我们还需要在 CMakeLists.txt 中显性的把相关的 so 添加和链接起来。完整的 CMakeLists.txt 如下:

cmake_minimum_required(VERSION 3.10.2)

# 支持gnu++11
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")

# 1. 定义so库和头文件所在目录,方面后面使用
set(ffmpeg_lib_dir ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
set(ffmpeg_head_dir ${CMAKE_SOURCE_DIR})

# 2. 添加头文件目录
include_directories(${ffmpeg_head_dir}/include)

# 3. 添加ffmpeg相关的so库
add_library( avutil
        SHARED
        IMPORTED )
set_target_properties( avutil
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavutil.so )

add_library( swresample
        SHARED
        IMPORTED )
set_target_properties( swresample
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libswresample.so )

add_library( avcodec
        SHARED
        IMPORTED )
set_target_properties( avcodec
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavcodec.so )

add_library( avfilter
        SHARED
        IMPORTED)
set_target_properties( avfilter
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavfilter.so )

add_library( swscale
        SHARED
        IMPORTED)
set_target_properties( swscale
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libswscale.so )

add_library( avformat
        SHARED
        IMPORTED)
set_target_properties( avformat
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavformat.so )

add_library( avdevice
        SHARED
        IMPORTED)
set_target_properties( avdevice
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavdevice.so )

# 查找代码中使用到的系统库
find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log )

# 配置目标so库编译信息
add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp
        )

# 指定编译目标库时,cmake要链接的库
target_link_libraries(

        # 指定目标库,native-lib 是在上面 add_library 中配置的目标库
        native-lib

        # 4. 连接 FFmpeg 相关的库
        avutil
        swresample
        avcodec
        avfilter
        swscale
        avformat
        avdevice

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib} )

5.验证使用 FFmpeg

要检查 FFmpeg 是否可以使用,可以通过获取 FFmpeg 基础信息来验证。

(1)在 native-lib.cpp 中添加对应的 JNI 层方法。

#include <jni.h>
#include <string>
#include <unistd.h>

extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libavfilter/avfilter.h>
    #include <libavcodec/jni.h>

    JNIEXPORT jstring JNICALL
    Java_com_cxp_learningvideo_FFmpegActivity_ffmpegInfo(JNIEnv *env, jobject  /* this */) {

        char info[40000] = {0};
        AVCodec *c_temp = av_codec_next(NULL);
        while (c_temp != NULL) {
            if (c_temp->decode != NULL) {
                sprintf(info, "%sdecode:", info);
            } else {
                sprintf(info, "%sencode:", 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[%s]\n", info, c_temp->name);
            c_temp = c_temp->next;
        }
        
        return env->NewStringUTF(info);
    }
}

首先,我们看到代码被包裹在 extern "C" { } 当中,和前面的系统创建的稍微有些不同,通过这个大括号包裹,我们就不需要每个方法都添加单独的 extern "C" 开头了。 另外,由于 FFmpeg 是使用 C 语言编写的,所在 C++ 文件中引用 #include 的时候,也需要包裹在 extern "C" { },才能正确的编译。

(2)在 MainActivity 中添加一个外部方法 ffmpegInfo

   public native String ffmpegInfo();

(3)如果一切正常,App运行后,就会显示出 FFmpeg 音视频编解码器的信息

小结:

使用Android NDK工具对ffmpeg 源码进行交叉编译动态库的原理比较简单,但是在实践操作过程中,需要主要编译工具中路径的设置,和编译脚本内参数的设置。

Github Demo下载链接

原创声明,本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

登录 后参与评论
0 条评论

相关文章

  • FFmpeg 开发(01):FFmpeg 编译和集成

    FFmpeg 是一款知名的开源音视频处理软件,它提供了丰富而友好的接口支持开发者进行二次开发。

    字节流动
  • Android FFmpeg系列01--编译与集成

    FFmpeg是一套用于录制、转换和流化音视频的完整的跨平台解决方案,它的强大之处不用过多描述,本文主要介绍如何编译出so文件和在Android Studio工程...

    雪月清
  • 偶遇FFmpeg(三)——Android集成

    其实这部分,不比多言了。虽然在网上可以找到很多类似的经验,但其实第一次使用还是要花费不少的时间。

    deep_sadness
  • 基于Android平台的ffmpeg编译

        前面介绍了Android jni 相关知识,但jni最终还是要调用的第三方的C/C++库,这里我们以ffmpeg为例,介绍第三方C/C++如何编译成an...

    用户4148957
  • Linux下ndk编译移植FFmpeg到Android平台(二)——集成x264和libfdk-aac

    在前面的Linux下ndk编译移植FFmpeg到Android平台文章中介绍了如何将最基本的FFmpeg到Android平台。但只是简单的移植了FFmpeg,没...

    用户2929716
  • 技术解码 | 详解快直播传输层SDK的FFmpeg集成和编译

    自从快直播传输层SDK发布以来,越来越多的客户通过快直播传输层SDK libLebConnection接入,其接入便捷性受到客户的肯定。libLebConnec...

    腾讯云音视频
  • FFmpeg4.0.2 编译成Android动态库

    近期需要用到FFmpeg的库,来做视频流的处理,今天尝试实现了一下,手动编译FFmpeg,然后引入到Android项目去使用。

    包子388321
  • Linux下ndk编译移植FFmpeg到Android平台简介

    这里我们选择3.2.4版本(注意:这里使用的3.2.4版本,如果用最新的版本,编译可能出现问题,为了想让大家上手,建议版本先保持一致)。直接github上选择下...

    用户2929716
  • Window 下 FFmpeg 和 LibX264 的编译和配置

    周末在家折腾 Windows 平台下 FFmepg 和 LibX264 库的编译,长期以来都是在 Mac 平台下做开发,切换到 Windows 平台下还是踩了不...

    音视频开发进阶
  • opencv集成opencv_contrib编译生成Android工程

    在项目的主目录下./build.gradle替换build tools,使用gradle-experimental,如下

    jerrypxiao
  • android混淆和反编译

    混淆 Android Studio: 只需在build.gradle(Module:app)中的buildTypes中增加release的编译选项即可,...

    xiangzhihong
  • FFmpeg4.0.2编译32位和64位动态库,并且引入到Android项目中

    近期刚好用到FFmpeg来处理视频编码,由于网上各种版本的so库大部分都32位的,所以打算自己来编译32位和64位的库,我之前有写编译32位的库https://...

    包子388321
  • 【Android 音视频开发打怪升级:FFmpeg音视频编解码篇】一、FFmpeg so库编译

    网上其实已经有很多的关于FFmpeg so库编译的分享,但是大部分都是直接把配置文件的内容贴出来。我想大部分取搜索 「如何编译FFmpeg so库」的人,对交叉...

    开发的猫
  • ffmpeg android ndk编译,还是用的gcc,不会配置clang版的。

    因为google在 NDK R19C中把GCC删除了。本来想着能不能配置出用clang编译ffmpeg,可是折腾了半天还是不行,于是还是用gcc吧。。支持gcc...

    xiny120
  • android反编译工具和命令

    [apktool](https:/ /bitbucket.org/iBotPeaches/apktool/downloads/)

    tea9
  • android应用资源预编译,编译和打包全解析

    我们知道,在一个APK文件中,除了有代码文件之外,还有很多资源文件。这些资源文件是通过Android资源打包工具aapt(Android Asset P...

    xiangzhihong
  • Android源码折腾(一)下载和编译

    我下载和编译用的是ubuntu20.04系统,之前在win上一直存在问题,甚至在下载环节就出问题,无法通过。

    笔头
  • SDL2库(2)-Android 端集成FFmpeg及简单的播放器

    项目位置 https://github.com/deepsadness/SDLCmakeDemo

    deep_sadness

扫码关注腾讯云开发者

领取腾讯云代金券