Android音视频: 引入FFmpeg

本文你可以了解到

本文将介绍如何将上一篇文章编译出来的 FFmpeg so 库,引入到 Android 工程中,并验证 so 是否可以正常使用。

一、开启 Android 原生 C/C++ 支持

在过去,通常使用 makefile 的方式在项目中引入 C/C++ 代码支持,随着 Android Studio 的普及,makefile 的方式已经基本被 CMake 替代。

有了 Android 官方的支持,NDK 层代码的开发变得更加容易。以前一谈到 Android NDK ,许多人就会大惊失色,感觉是深不可测的东西,一方面是 makefile 的编写很难,一方面是 C/C++ 相比 Java 来说,比较晦涩。

但是不必担心,一是有了 CMake ,二是对于 C/C++ 的基本使用其实和 Java 差不多,本系列涉及到的,也都是对 C/C++ 的基础使用,毕竟,高级的我也不会不是吗?哈哈哈~~

1. 安装 CMake

首先,需要下载 CMake 相关工具,在 Android Studio 中依次点击 Tools->SDK Manager->SDK Tools,然后勾选

CMake : CMake 构建工具

LLDB : C/C++ 代码调试工具

NDK : NDK 环境

最后依次点击 OK->OK->Finish ,开始下载(文件比较大,可能会比较慢,请耐心等待)。

下载CMake工具

2. 添加 C/C++ 支持

有两种方式:

一是,新建一个新的工程,并勾选 C/C++ 支持选项,系统将自动创建一个支持 C/C++ 编码的工程。
二是,在已有的项目上,手动添加所有的添加项来支持 C/C++ 编码,其实就是自己手动添加 「第一种方式」Android Studio 为我们自动创建的那些东西。

首先,通过新建一个新工程的方式,看看 IDE 为我们生成了那些东西。

1)新建 C/C++ 工程

依次点击 File -> New -> New Project,进入新建工程页面,拉到最后,选择 Native C++ 然后按照默认配置,一路 Next -> Next -> Finish 即可。

新建C++工程

2)Android Studio 自动生成了什么

生成的工程目录如下:

工程目录

重点关注上图标注的3个地方:

  • 第一,最上层的 MainActivity

    复制代码
    class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // Example of a call to a native method
    sample_text.text = stringFromJNI()
    }

    /**
    * A native method that is implemented by the 'native-lib' native library,
    * which is packaged with this application.
    */
    external fun stringFromJNI(): String

    companion object {

    // Used to load the 'native-lib' library on application startup.
    init {
    System.loadLibrary("native-lib")
    }
    }
    }

很简单,使用过 so 库的应该都看得懂,这里简单说一下。

代码的最下面,companion objectKotlin 中表示静态代码块,类似 Java 中的 static { },其中的代码有且只会被执行一次。

接着在 init{} 方法中,加载了 C/C++ 代码编译成的 so 库: native-lib

往上一句代码,用 external 声明了一个外部引用的方法 stringFromJNI() ,这个方法和 C/C++ 层的代码是对应的。

最终在最上面的 onCreate 中,将从 C/C++ 层返回的 String 显示出来。

【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~

  • 第二,创建了一个 cpp 文件包

其中有两个文件非常重要,分别是 native-lib.cppCMakeLists.txt

i. native-lib.cpp :是一个 C++ 接口文件,在 MainActivity 中声明的外部方法将在这里得到实现。

自动生成 native-lib.cpp 的内容如下:

复制代码
  #include <jni.h>
  #include <string>
​
  extern "C" JNIEXPORT jstring JNICALL
  Java_com_chenlittleping_mynativeapp_MainActivity_stringFromJNI(
          JNIEnv *env,
          jobject /* this */) {
      std::string hello = "Hello from C++";
      return env->NewStringUTF(hello.c_str());
  }

可以看到,这个 cpp 文件中的方法命名非常的长,不过其实非常简单。

首先是头部固定写法 extern "C" JNIEXPORT jstring JNICALL

extern "C" 表示以 C语言 的方式来编译;

jstring 表示该方法返回类型是 Java 层的 String 类型,类似的还是有: void jint等;

然后是 Java 层对应方法的映射 ,即整个方法命名其实是 Java 层对应方法的绝对路径。

其中,最前面的 Java_ 是固定写法;

com_chenlittleping_mynativeapp_MainActivity_: 对应的是 com.chenlittleping.mynativeapp.MainActivity.,其实就是 . 换为 _

stringFromJNI 和 Java 层的方法一致。

最后是两个参数JNIEnv *envjobject,分别代表 JNI 的上下文环境和调用这个接口的 Java 的类的实例。

调用这个方法,将会在 C++ 层创建一个字符串,并以 Java#String 的类型返回。

ii. CMakeLists.txt : 也就是构建脚本。内容如下:

复制代码
  # cmake 最低版本
  cmake_minimum_required(VERSION 3.4.1)
​
  # 配置so库编译信息
  add_library( 
          # 输出so库的名称
          native-lib
​
          # 设置生成库的方式,默认为SHARE动态库
          SHARED
​
          # 列出参与编译的所有源文件
          native-lib.cpp)
​
  # 查找代码中使用到的系统库
  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)
​
  # 指定编译目标库时,cmake要链接的库
  target_link_libraries(
          # 指定目标库,native-lib 是在上面 add_library 中配置的目标库
          native-lib
  
          # 列出所有需要链接的库
          ${log-lib})

这是最简单的编译配置,具体见上面的注释。

CMakeLists.txt 的目的就是配置可以编译出 native-lib so 库的构建信息。

说白了,就是告诉编译器:

复制代码
  - 编译的目标是谁
  - 依赖的源文件在哪里找
  - 依赖的 `系统或第三方` 的 `动态或静态` 库在哪里找。
  • 第三,在 Gradle 文件中注册 CMake 脚本

第二步 中,已经把构建 so 库的信息配置好了,接下来要把这些信息注册到 Gradle 中,编译器才会去编译它。

app 的 build.gradle 内容如下:

复制代码
  apply plugin: 'com.android.application'
​
  apply plugin: 'kotlin-android'
  
  apply plugin: 'kotlin-android-extensions'
​
  android {
      compileSdkVersion 28
      buildToolsVersion "29.0.1"
      defaultConfig {
          applicationId "com.chenlittleping.mynativeapp"
          minSdkVersion 19
          targetSdkVersion 28
          versionCode 1
          versionName "1.0"
          testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        
          // 1) CMake 编译配置
          externalNativeBuild {
              cmake {
                  cppFlags ""
              }
          }
      }
      buildTypes {
          release {
              minifyEnabled false
              proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
          }
      }
    
      // 2) 配置 CMakeLists 路径
      externalNativeBuild {
          cmake {
              path "src/main/cpp/CMakeLists.txt"
              version "3.10.2"
          }
      }
   }
​
  dependencies {
      // 省略无关代码
      //......
  }

最主要的两个地方是两个 externalNativeBuild

第 1 个 externalNativeBuild 中,可以做一些优化配置,比如只打包包含 armeabi 架构的 so

复制代码
  externalNativeBuild {
      cmake {
          cppFlags ""
      }
      ndk {
          abiFilters  "armeabi" //, "armeabi-v7a"
      }
  }

第 2 个 externalNativeBuild ,主要是配置 CMakeLists.txt 的路径和版本。

Android Studio 为我们生成的关于 C/C++ 支持的主要就是以上三个地方,有了以上配置,就可以在 MainActivity 页面中正常的显示出 Hello from C++

3) 在已有工程上添加 C/C++ 支持

前面就说过,在已有项目上添加 C/C++ 支持,就是由我们自己手动添加整个配置。那么根据签名介绍的三个步骤,依葫芦画瓢,就可以添加了。

这里刚好就用添加 FFMpeg so 到本系列文章现有 Demo 工程中来演示一遍。

二、引入 FFmpeg so

1. 新建 cpp 目录

首先,在 app/src/main/ 目录下,新建文件夹,并命名为 cpp

接着,在 cpp 目录下,右键 New -> C/C++ Source File ,新建 native-lib.cpp 文件。

接着,在 cpp 目录下,右键 New -> File ,新建 CMakeLists.txt ,先将上面 IDE 生成的那份代码粘贴进来, FFmpeg的配置在后面详细讲解。

复制代码
  # CMakeLists.txt
​
  # cmake 最低版本
  cmake_minimum_required(VERSION 3.4.1)
​
  # 配置so库编译信息
  add_library( 
          # 输出so库的名称
          native-lib
​
          # 设置生成库的方式,默认为SHARE动态库
          SHARED
​
          # 列出参与编译的所有源文件
          native-lib.cpp)
​
  # 查找代码中使用到的系统库
  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)
​
# 指定编译目标库时,cmake要链接的库
target_link_libraries(
          # 指定目标库,native-lib 是在上面 add_library 中配置的目标库
          native-lib
​
          # 列出所有需要链接的库
          ${log-lib})

2. 将 CMakeLists 配置到 build.gradle 中

复制代码
  android {
      // ...
    
      defaultConfig {
      // ...
    
      // 1) CMake 编译配置
      externalNativeBuild {
              cmake {
                  cppFlags ""
              }
          }
      }
    
      // ...
    
      // 2) 配置 CMakeLists 路径
      externalNativeBuild {
          cmake {
              path "src/main/cpp/CMakeLists.txt"
              version "3.10.2"
          }
      }
  }
​
  // ...

如果只是简单的编写 C/C++ 代码,以上基础配置就可以了。

接着来看看本文的重点,如何使用 CMakeLists.txt 引入 FFmpeg 的动态库。

3. 将 FFmpeg so 库放到对应的 CPU 架构目录

上一篇文章中,我们编译的 FFmpeg so 库的 CPU 架构为 armv7-a,所以,我们需要把所有的 so 库放置到 armeabi-v7a 目录下。

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

app/src/main/jniLibs 是 Android Studio 默认的放置 so 动态库的目录。

接着,在 jniLibs 目录下,新建 armeabi-v7a 目录。

最后把 FFmpeg 编译得到的所有 so 库粘贴到 armeabi-v7a 目录。如下:

so目录

4. 添加 FFmpeg so 的头文件

在编译 FFmpeg 的时候,除了生成 so 外,还会生成对应的 .h 头文件,也就是 FFmpeg 对外暴露的所有接口。

FFmpeg编译输出

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

头文件目录

5. 添加、链接 FFmpeg so 库

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

复制代码
  cmake_minimum_required(VERSION 3.4.1)
​
  # 支持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}/ffmpeg)
​
  # 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} )

主要看看注释中新加入的 1~4 点。

1)通过 set 方法定义了 so头文件 所在目录,方便后面使用。

其中 CMAKE_SOURCE_DIR 为系统变量,指向 CMakeLists.txt 所在目录。 ANDROID_ABI 也是系统变量,指向 so 对应的 CPU 框架目录:armeabi、armeabi-v7a、x86 ...

复制代码
set(ffmpeg_lib_dir ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
set(ffmpeg_head_dir ${CMAKE_SOURCE_DIR}/ffmpeg)

2)通过 include_directories 设置头文件查找目录

复制代码
include_directories(${ffmpeg_head_dir}/include)

3)通过 add_library 添加 FFmpeg 相关的 so 库,以及 set_target_properties 设置 so 对应的目录。

其中,add_library 第一个参数为 so 名字, SHARED 表示引入方式为动态库引入。

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

4)最后,通过 target_link_libraries 把前面添加进来的 FFMpeg so 库都链接到目标库 native-lib 上。

这样,我们就将 FFMpeg 相关的 so 库都引入到当前工程中了。下面就要来测试一下,是否可以正常调用到 FFmpeg 相关的方法了。

三、使用 FFmpeg

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

1. 在 FFmpegAcrtivity 中添加一个外部方法 ffmpegInfo

把获取到的 FFmpeg 信息显示出来。

复制代码
  class FFmpegActivity: AppCompatActivity() {
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_ffmpeg_info)
​
          tv.text = ffmpegInfo()
      }
​
      private external fun ffmpegInfo(): String
​
      companion object {
          init {
              System.loadLibrary("native-lib")
          }
      }
  }

2. 在 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);
                  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);
              } else {
                  sprintf(info, "%sencode:", info);
              }
              c_temp = c_temp->next;
          }
          return env->NewStringUTF(info);
      }
  }

首先,我们看到代码被包裹在 extern "C" { } 当中,和前面的系统创建的稍微有些不同,通过这个大括号包裹,我们就不需要每个方法都添加单独的 extern "C" 开头了。

另外,由于 FFmpeg 是使用 C 语言编写的,所在 C++ 文件中引用 #include 的时候,也需要包裹在 extern "C" { },才能正确的编译。

方法的新建就不用说了,和前面介绍的命名方法一致。

在方法中,使用 FFmpeg 提供的方法 av_codec_next,获取到 FFmpeg 的编解码器,然后通过循环遍历,将所有的音视频编解码器信息拼接起来,最后返回给 Java 层。

至此,FFmpeg 加入到工程中,并被调用。

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

原文 Android音视频: 引入FFmpeg - 掘金

相关推荐
EasyDSS26 分钟前
视频监控从安装到优化的技术指南,视频汇聚系统EasyCVR智能安防系统构建之道
大数据·网络·网络协议·音视频
似霰1 小时前
安卓adb shell串口基础指令
android·adb
fatiaozhang95273 小时前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
CYRUS_STUDIO4 小时前
Android APP 热修复原理
android·app·hotfix
阿酷tony4 小时前
将视频生成视频二维码步骤
音视频·视频格式·视频二维码·视频生成二维码
鸿蒙布道师4 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师4 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
9527华安4 小时前
国产紫光同创FPGA视频采集转SDI编码输出,基于HSSTHP高速接口,提供2套工程源码和技术支持
fpga开发·音视频·紫光同创·sdi·高速接口·hssthp
大耳猫5 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白5 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度