ffplay 源码编译-Android中整合FFmpeg和NDK基础知识

前言

在日常的App开发中,难免有些功能需要使用NDK来完成,比如常见的音视频处理等,明天我们就从ffmpeg开始学习AndroidNDK开发的例程。

JNI 和 NDK

很多人并没有消除JNI和NDK的概念,常常混淆这两个东西。 我们先来看看它们各自的定义。

JNI

NDK

NDK的全称是NativeDevelopmentKit,它是Android的开发工具包,与Java无关。

NDK 的核心目的之一是让您将 C 和 C++ 源代码重构为可供应用程序使用的共享库。 嗯,它提供了交叉编译的功能。

CPU架构

我们都知道什么是CPU,那么CPU架构到底是什么呢? 回到“架构”这个词本身的含义,CPU架构就是CPU的框架结构和设计方案。 处理器制造商基于某种架构生产自己的CPU,就像“总-点-总”是一种文章。 结构上,多篇文章可以按照“总-分-总”的结构。

源码编译安装的基本过程_ffplay 源码编译_源码编译是什么意思

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

x86框架主要针对PC端。 对于联通这边来说,arm框架处于主导地位。 由于其具有体积小、帧率低、成本低、性能高等优点,在嵌入式系统中得到广泛应用。 目前大多数Android、苹果手机的CPU都是基于arm架构的。 这里所说的arm架构是指arm系列架构,包括ARMv5、ARMv7等。

最后看Android端。 Android系统目前支持七种CPU架构:ARMv5、ARMv7、ARMv8、x86、x86_64、MIPS、MIPS64。 也就是说ffplay 源码编译,其他CPU架构的硬件无法运行Android系统。

交叉编译

在某个平台上,编译该平台的可执行程序称为本地编译,例如在Windows平台上编译Windows本身的可执行程序; 在x86平台上,编译x86平台本身的可执行程序。

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

交叉编译时,由于宿主机和目标机的架构和环境不同,交叉编译比本地编译复杂得多。 需要一些工具来解决主机和目标不同特性的问题。 由此类工具组成的工具集称为交叉编译。 链。

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

在项目中使用NDK

这里可以查看一个官方文档,英文写的,很详细:Add C and C++ code to your project,强烈建议仔细阅读这部分文档

CMake

源码编译是什么意思_源码编译安装的基本过程_ffplay 源码编译

NDK的构建方式有两种,一种是前期使用的ndk-build,另一种是AndroidStudio2.2之后推荐的cmake,明天只讲推荐的cmake方法。

如何编写FFmpeg CMakeLists.txt

FFmpeg 是一个开源框架ffplay 源码编译,可用于录制、处理数字音频和视频,并将其转换为流。 它采用LPL或GPL许可证,提供录制、转换和流式传输音频和视频的完整解决方案。 名称中的mpeg来自视频编码标准mpeg,前缀FF是FastForward的缩写。 一个音视频处理的开源库,可以完成大部分音视频相关功能。 很多著名的软件和开源库都是基于它进行二次开发的,比如bilibi的ijkPlayer。

GitHub 链接

编译 FFmpeg

FFmpeg 的编译形式与大多数 GNU 软件类似。 通过configure脚本实现编译前的定制。 这些方法允许用户在编译之前对软件进行剪裁,同时配置最终运行的系统和目标平台。 确定各个模块的适当配置。 所以这里以configure的形式生成一个Makefile,然后使用make和makeinstall来编译安装。

配置环境

首先我们需要规划相关的编译环境。 建议在linux下编译。 配置简单,问题很少。 其实Mac也可以,Windows不推荐。

对于Linux环境(Ubuntu16.04)和Windows,可以方便地下载VMwareWorkstation并安装ubuntu。

NDK环境这里使用ndk-r17,附相关下载链接NDK下载

下载FFmpeg源码 FFmpeg下载地址

ffplay 源码编译_源码编译安装的基本过程_源码编译是什么意思

更改配置文件

因为FFmpeg默认生成的库文件格式是libavcodec.so.xx.xx.x。 其中,xx为主版本号和次版本号。 这些格式在Ubuntu下使用没有问题,而且是在Android下开发和使用的,不被视为有效的库文件。 所以需要更改其他生成的文件名的格式。

要通过更改配置文件来实现此目的,请打开配置并找到以下内容:

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)'

编译脚本文件,并在FFmpeg根目录下创建build.sh脚本文件,以便更方便地配置configure。 如下:

#!/bin/bash  
# 配置NDK路径
NDK=/home/xinyang/develop/android-ndk-r17
# 指定了交叉编译环境,使其在编译过程中能够引用到 NDK 提供的原生标头和共享库文件
SYSROOT=$NDK/platforms/android-23/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
# 声明方法
function build_one
{
./configure 
--prefix=$PREFIX       # 设置输出路径
--enable-shared        # 打开动态库输出
--disable-static       # 关闭静态库输出
--disable-doc          # 关闭不需要的功能
--disable-ffmpeg 
--disable-ffplay 
--disable-ffprobe 
--disable-avdevice 
--disable-symver 
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi-   # 指定交叉编译工具链 
--target-os=linux      # 目标系统 android基于linux 所以这里指定为linux
--arch=armeabi-v7a     # 目标平台架构
--enable-cross-compile # 开启交叉编译
--sysroot=$SYSROOT     # 交叉编译环境
--extra-cflags="-Os -fpic $ADDI_CFLAGS" 
--extra-ldflags="$ADDI_LDFLAGS" 
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
}
CPU=armeabi-v7a
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one

—交叉前缀

与以键值形式指定bin目录下以arm-linux-androideabi-开头的交叉编译工具类似,如果不支持这些配置方式,则需要单独指定:

执行脚本cd到ffmpeg目录

ffplay 源码编译_源码编译是什么意思_源码编译安装的基本过程

chmod 777 build.sh

首先更改脚本文件的可执行权限

./build.sh

然后执行脚本,整个过程比较慢,耐心等待即可,整个过程大概需要5-10分钟。 编译完成后,可以看到右图,其中include中有一些头文件,lib中生成的.so动态存货

集成 FFmpeg

至此,我们就可以将生成的.so文件集成到我们的项目中了,我们来看看步骤:

将项目与NDK关联,按照此处的教程将C和C++代码添加到您的项目中;

将生成的.so文件复制到libs目录(或jniLibs);

将生成的include文件夹复制到cpp目录下;

将ffmpegfftools目录下的文件复制到cpp目录下;

ffplay 源码编译_源码编译是什么意思_源码编译安装的基本过程

编译本地方法

package com.xinyang.ndkdemo;
public class FFmpegCmd {
static {
    System.loadLibrary("ffmpeg");
}
public native static void handle();
}

在cpp目录下创建ffmpeg_cmd.c文件,实现native方法。 这里可以使用javah生成头文件然后实现,也可以直接使用java类中的快捷键提示直接生成:

#include 
#include 
#include 
#include 
#include "ffmpeg/ffmpeg.h"
JNIEXPORT void  JNICALL Java_com_xinyang_ndkdemo_FFmpegCmd_handle
(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[s]n",info,c_temp->name);
        c_temp=c_temp->next;
    }
    __android_log_print(ANDROID_LOG_INFO,"myTag","info:n%s",info);
}

该程序用于输出FFmpeg支持的编解码信息,通过 __android_log_print方法可以直接向AndroidStudio的logcat输出信息。

编辑CMakeLists.txt导出相关.so文件,使用add_library导出库依次导出生成的.so文件,使用include_directories导出头文件,最后使用target_link_libraries将导出的库与生成的目标库关联起来,如下如下图所示:

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
         ffmpeg
         # Sets the library as a shared library.
         SHARED
         # Provides a relative path to your source file(s).
         src/main/cpp/ffmpeg_cmd.c
         src/main/cpp/ffmpeg/cmdutils.c
         src/main/cpp/ffmpeg/ffmpeg.c
         src/main/cpp/ffmpeg/ffmpeg_filter.c
         src/main/cpp/ffmpeg/ffmpeg_opt.c
          )
include_directories(src/main/cpp)
include_directories(src/main/cpp/include)
add_library(
    avutil-55
    SHARED
    IMPORTED
)
set_target_properties( avutil-55
           PROPERTIES IMPORTED_LOCATION
           ../../../../libs/armeabi-v7a/libavutil-55.so )
add_library(
    avcodec-57
    SHARED
    IMPORTED
)
set_target_properties( avcodec-57
           PROPERTIES IMPORTED_LOCATION
           ../../../../libs/armeabi-v7a/libavcodec-57.so )
add_library(
    avformat-57
    SHARED
    IMPORTED
)
set_target_properties( avformat-57
           PROPERTIES IMPORTED_LOCATION
           ../../../../libs/armeabi-v7a/libavformat-57.so )
add_library(
    avdevice-57
    SHARED
    IMPORTED
)
set_target_properties( avdevice-57
           PROPERTIES IMPORTED_LOCATION
           ../../../../libs/armeabi-v7a/libavdevice-57.so )
add_library(
    swresample-2
    SHARED
    IMPORTED
)
set_target_properties( swresample-2
           PROPERTIES IMPORTED_LOCATION
           ../../../../libs/armeabi-v7a/libswresample-2.so )
add_library(
    swscale-4
    SHARED
    IMPORTED
)
set_target_properties( swscale-4
           PROPERTIES IMPORTED_LOCATION
           ../../../../libs/armeabi-v7a/libswscale-4.so )
add_library(
    postproc-54
    SHARED
    IMPORTED
)
set_target_properties( postproc-54
           PROPERTIES IMPORTED_LOCATION
           ../../../../libs/armeabi-v7a/libpostproc-54.so )
add_library(
    avfilter-6
    SHARED
    IMPORTED
)
set_target_properties( avfilter-6
           PROPERTIES IMPORTED_LOCATION
           ../../../../libs/armeabi-v7a/libavfilter-6.so )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
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 )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
                    ffmpeg
                    avutil-55
                    avcodec-57
                    avformat-57
                    avdevice-57
                    swresample-2
                    swscale-4
                    postproc-54
                    avfilter-6
                   # Links the target library to the log library
                   # included in the NDK.
                   ${log-lib} )

尝试调用native方法,在logcat中查看具体输出信息,如下:

总结

总的来说,使用CMake方法比较简单。 编译CMakeLists.txt文件并指定该文件在gradle手上的位置。 重点是相关库的交叉编译以及调用相关api文件的C文件的编译。 这里需要一些C文件。 基础。