MAC环境编译Android环境下的FFmpeg6.0版本

文章目录

  • 一. 下载FFmpeg源码
  • 二、对FFmpeg进行安装编译
  • 三、进行JNI接口编写代码
  • 四、在Android 项目中调用.so库
  • 五、FFmpeg的代码学习技巧
    • 1、整体学习步骤
    • 2、FFmpeg的代码学习步骤
  • 六、参考链接:

一. 下载FFmpeg源码

该项目是基于FFmpeg6.0环境编写。文中涉及代码在不同版本可能会有变动
从以下两个地址任选其一下载源码:

  1. https://github.com/FFmpeg/FFmpeg
  2. https://ffmpeg.org/

二、对FFmpeg进行安装编译

执行根目录的configure文件,

./configure 

该方式会生成相关文件,否则项目不可运行,
参考如下:
https://ffmpeg.org/doxygen/6.0/md_INSTALL.html

编译过程中会出现错误,根据错误进行修改,通常会缺少pkg-config安装包,这个会在编译时候提示缺少这个,不提示就是不缺少。安装如下:
brew install pkg-config
整体错误大概分为缺少依赖的安装包和环境。这个根据提示进行安装即可。没办法绕过

编译成.so参考如下
Android 集成 FFmpeg (一) 基础知识及简单调用
https://blog.csdn.net/yhaolpz/article/details/76408829

如果使用sh文件进行编译的话会出现权限拒绝的情况,可以使用以下命令:

chmod +x build_android.sh

然后再执行sh文件。

以下为sh配置的文件,不需要在项目中配置环境变量,但是所有文件中用到的具体路径需要进行修改。
需要注意的是每次添加新的命令需要使用\ 进行分割。在进行配置的时候对于高级选项的命令,比如优化部分--disable-neon最好不要使用,否则容易出现错误。相关命令可以通过configure --help进行查看

#!/bin/bash 
NDK=/Users/c/Documents/sdk/android/ndk/25.1.8937393
TOOLCHAIN_ROOT_DIR=darwin-x86_64
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/$TOOLCHAIN_ROOT_DIR/
API=21
#要编译的ffmpeg内容方法
function build_android {
	echo "Compiling FFmpeg for $CPU" 
	./configure \
	--prefix=$PREFIX \
	--disable-hwaccels \
	--disable-gpl \
	--disable-postproc \
	--disable-programs \
	--disable-mediacodec \
	--disable-decoder=h264_mediacodec \
	--disable-static \
	--disable-vulkan \
	--disable-doc \
	--disable-ffmpeg \
	--disable-ffplay \
	--disable-ffprobe \
	--disable-avdevice \
	--disable-doc \
	--disable-symver \
	--disable-x86asm \
	--disable-filters \
	--enable-cross-compile \
	--enable-jni \
	--enable-shared \
	--cross-prefix=$CROSS_PREFIX \
	--nm=$NM \
	--strip=$STRIP \
	--pkgconfigdir=$PKG_CONFIG_DIR \
	--pkg-config=$PKG_CONFIG \
	--target-os=android \
	--arch=$ARCH \
	--cpu=$CPU \
	--cc=$CC \
	--cxx=$CXX \
	--sysroot=$SYSROOT \
	--extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
	--extra-ldflags="$ADDI_LDFLAGS" \
	$ADDITIONAL_CONFIGURE_FLAG 
	make clean 
	make 
	make install 
	echo "The Compilation of FFmpeg for $CPU is completed" 
}
#接下来是根据需要来决定
#armv8-a 
ARCH=arm64 
CPU=armv8-a 
CC=$TOOLCHAIN/bin/aarch64-linux-android$API-clang 
CXX=$TOOLCHAIN/bin/aarch64-linux-android$API-clang++
LLVM_TOOLCHAIN=$TOOLCHAIN/bin
PKG_CONFIG_DIR=/opt/homebrew/Cellar/pkg-config/0.29.2_3
PKG_CONFIG=$PKG_CONFIG_DIR/bin/pkg-config
NM=$LLVM_TOOLCHAIN/llvm-nm
STRIP=$LLVM_TOOLCHAIN/llvm-strip
SYSROOT=$TOOLCHAIN/sysroot 
CROSS_PREFIX=$TOOLCHAIN/bin/aarch64-linux-android-
PREFIX=$(pwd)/android/$CPU 
OPTIMIZE_CFLAGS="-march=$CPU" 
build_android

其实最初文件可以写成以下方式:

export ANDROID_NDK=<NDK路径>
export TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/darwin-x86_64
export SYSROOT=$ANDROID_NDK/toolchains/llvm/prebuilt/darwin-x86_64/sysroot

./configure \
--prefix=$PWD/build \
--enable-shared \
--disable-static \
--disable-doc \
--disable-programs \
--disable-symver \
--arch=arm64 \
--target-os=android \
--cc=$TOOLCHAIN/bin/aarch64-linux-android21-clang \
--cxx=$TOOLCHAIN/bin/aarch64-linux-android21-clang++ \
--cross-prefix=$TOOLCHAIN/bin/aarch64-linux-android- \
--sysroot=$SYSROOT

但是编译中会出现各种错误,最终要么重新指定路径,要么禁止使用,就改成了如此。
关于命令的含义参考如下:
FFmpeg中configure的参数配置解释

对于pkg-config的配置如果编译时候找不到,要么禁用--disable-pkgconfig,要么使用以下方式进行配置

./configure --pkg-config=/path/to/pkg-config --pkgconfigdir=/path/to/pkgconfigdir

最终会在ffmpeg的根目录生成android/目录(这里是android/),里面共有三个文件夹includelibshare。.so位于lib文件夹下:
MAC环境编译Android环境下的FFmpeg6.0版本_第1张图片
shell脚本参考如下:
shell脚本语言(超全超详细)

三、进行JNI接口编写代码

在FFmpeg编译成.so完成后,需要添加JNI编码,否则的话无法在Android项目中直接使用。该操作需要配置ndk-build环境
参考链接:
Android 集成 FFmpeg (一) 基础知识及简单调用

这里使用文本工具进行代码编写和编译,不使用Android Studio进行编译。
首先在上述的FFmpeg根目录下后面生成的文件夹android中创建文件ndkBuild\com\jni\FFmpeg.javandkBuild\jni文件夹
FFmpeg.java代码如下:

package com.jni;

public class FFmpeg {
    
    public static native void run();

}

然后在ndkBuild目录下执行命令生成com_jni_FFmpeg.h文件
该命令的中间的.表示当前路径,可以更改为其余路径,后面是具体的类的名字

javah -classpath .  com.jni.FFmpeg

然后在ndkBuild\jni文件夹下创建com_jni_FFmpeg.cAndroid.mkApplication.mk
代码如下:
com_jni_FFmpeg.c

#include 
#include "com_jni_FFmpeg.h"

#include 
#include 
#include 
#include 
#include 
#include 
#include 
# 这里是把AV_CODEC_ID_MP2的编码信息打印一下
JNIEXPORT void JNICALL Java_com_jni_FFmpeg_run(JNIEnv *env, jclass obj) {
	char info[40000] = {0};
	const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_MP2);
	AVCodecContext *avcodecContext = avcodec_alloc_context3(codec);
	const char *license = avcodec_license();
	if(avcodecContext->codec_type == AVMEDIA_TYPE_VIDEO){//音频
		sprintf(info,"%s[Video]",info);
	}else{//视频	
		sprintf(info,"%s[Audio]",info);
	}
	sprintf(info,"%s[%10s]\n",info,codec->name);
	avcodec_free_context(&avcodecContext); //释放内存
	__android_log_print(ANDROID_LOG_INFO,"myTag","info:\n%s",info);
}

Android.mk

LOCAL_PATH:= $(call my-dir)

FFMPEG_PATH:=/Users/c/Documents/CTest/ffmpeg-6.0
FFMPEG_ANDROID:=$(FFMPEG_PATH)/android/armv8-a
INCLUDE_PATH:=$(FFMPEG_ANDROID)/include
FFMPEG_LIB_PATH:=$(FFMPEG_ANDROID)/lib

include $(CLEAR_VARS)
LOCAL_MODULE:= libavcodec
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavcodec.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)
 
include $(CLEAR_VARS)
LOCAL_MODULE:= libavformat
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavformat.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)
 
include $(CLEAR_VARS)
LOCAL_MODULE:= libswscale
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libswscale.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)
 
include $(CLEAR_VARS)
LOCAL_MODULE:= libavutil
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavutil.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)
 
include $(CLEAR_VARS)
LOCAL_MODULE:= libavfilter
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavfilter.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)
 
include $(CLEAR_VARS)
LOCAL_MODULE:= libswresample
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libswresample.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 := $(FFMPEG_PATH)
LOCAL_LDLIBS := -lm -llog
LOCAL_SHARED_LIBRARIES := libavcodec libavfilter libavformat libavutil libswresample libswscale
include $(BUILD_SHARED_LIBRARY)

Application.mk

APP_ABI := arm64-v8a
APP_PLATFORM=android-21

然后在当前目录下执行命令:

ndk-build

生成Android.mk中定义的.so文件。最终会在ndkBuild下面生成两个文件夹libsobj。在libs\arm64-v8a下面会有生成的.so库。共七个libavcodec.so libavfilter.so libavformat.so libavutil.so libswresample.so libswscale.so libffmpeg.so。其中libffmpeg.so为编写的JNI生成的so库,其余为依赖库。

四、在Android 项目中调用.so库

将上述的七个.so库复制到项目的libs文件夹下,可以将整个libs文件夹对Android项目下的libs进行覆盖。app目录下的build.gradle修改如下:

android{
sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
}

将刚才编写的java文件连同com.jni的文件夹一起复制到src目录下。整体结构如下:
MAC环境编译Android环境下的FFmpeg6.0版本_第2张图片
如果将.so连同arm64-v8a文件夹复制到main下面的jniLibs文件夹下面则不需要对build.gradle进行修改。如下
MAC环境编译Android环境下的FFmpeg6.0版本_第3张图片

然后在项目中调用代码

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val btn: Button = findViewById(R.id.button)
        btn.setOnClickListener {
            FFmpeg.run()
        }
        FFmpeg.test()
    }
}

可以在logcat下看到结果

2023-06-08 11:36:01.783  5346-5346  myTag                   com.test.ffmpeg                      I  info:
                                                                                                    [Audio][       mp2]

五、FFmpeg的代码学习技巧

1、整体学习步骤

在学习一个技巧的时候最好根据官方来进行学习,因为其余人的教程可能存在无法实时更新导致的api变动问题,这样使用旧版代码可能无法运行,也就无从下手。在学习ffmpeg的开发过程中主要有以下几个步骤

  1. 编译ffmpeg为.so。参考:https://ffmpeg.org/doxygen/6.0/md_INSTALL.html,编译过程需要的命令可以通过configure --help进行查看
  2. 将编译成功的结果.so和本地的环境比如Android进行编写桥接代码JNI,这里可以参考Android官方网站: https://developer.android.com/ndk?hl=zh-cn
  3. 在编写JNI时候需要使用ffmpeg的api来完成我们的目的,这里的学习方式最好使用官方教程: https://ffmpeg.org/documentation.html , 以及相关论坛

2、FFmpeg的代码学习步骤

FFmpeg本身是一个工具系列的库,是一个开源性质的库,所以教程相对于那些商业化的框架会比较难以上手,比如OkHttp框架。最初通过浏览器搜索的结果,代码基本上都难以运行,大部分都是api过时,这样其实不利于入门,毕竟通过别人的代码学会了,但是api过时了,又不知道怎么更新api。通过仔细查阅官方网站整理了一条路径(不一定是最优学习方式,有更好的可以留言)。
首先官网提供了必要的api文档和一些基本的示例(虽然示例可能不是我们想象中的示例程序)。通过官方文档我们知道整体文档分为: 命令行工具文档组件文档图书馆文件一般文件API文档社区贡献文档
MAC环境编译Android环境下的FFmpeg6.0版本_第4张图片

通过每一个都点进去后可以简单知道,内容很多,但是作为初学者来说,实在是难以接受,茫然无措,不知道从何开始。如果是用命令行就可以完成需求的话,当然命令行工具文档是很好的选择,但是如果需要自己通过api开发,就只能查看api文档了。其余的文档可以等后面熟悉了再进行阅读。
api文档位于:https://ffmpeg.org/doxygen/trunk/index.html。每天都会更新。整体页面如下

通过此我们知道的有用信息如下:
整个库分为八个部分

  1. libavcodec编码/解码库
  2. libavfilter基于图形的帧编辑库
  3. libavformat I/O 和 muxing/demuxing 库
  4. libavdevice特殊设备复用/解复用库
  5. libavutil通用实用程序库
  6. libswresample音频重采样、格式转换和混合
  7. libpostproc后处理库
  8. libswscale颜色转换和缩放库

在上面的Modules选项下面可以看到都有哪些模块:

通过Examples选项下面可以看到有哪些例子

如果恰巧自己想实现的需求是例子中所有的,那就非常完美,可是点进去例子后发现,例子对初学者来说写的很复杂。我们最初只想写一个简单的测试例子,测试程序是否编译通过,比如打印下ffmpeg的基础信息,打印下基本的信息。如果不这样做,直接完成一个音视频播放的例子,无疑会显得特别困难。然后我们进行需求分析,什么样的场景满足这样的需求?通过上述信息搜集,可以知道我们打印下ffmpeg支持的格式会比较简单,也不需要进行文件读写。这里的话就锁定使用libavformat。点击进去后发现内容如下:

模块如下,文件有三个头文件,一个avformat.h,剩下两个是版本信息,其余的库也是如此,我们的主要参考api就位于第一个文件中,点击进去后发现,里面包含了定义的常量和函数。

被标记的就是会用到的遍历函数。我们点击第一个函数。

这里是函数的定义,解释了需要从传参数为NULL开始便利,下面是引用该函数的位置(有的api下面还要示例程序的引用地址,不过这个api没有)。选择第一个引用的位置点击进去查看:

这里是一个简单的使用方式。将该代码拷贝到实际项目中去,然后别忘了添加相应的头文件,格式如下:<库名/头文件名.h> #include 。相应的库名和头文件名在上述步骤已经给出。
所以整体代码如下:

#include 
#include "com_jni_FFmpeg.h"

#include 
#include 
#include 
#include 

JNIEXPORT void JNICALL Java_com_jni_FFmpeg_run(JNIEnv *env, jclass obj) {
	 const AVOutputFormat *fmt = NULL;
	     const AVOutputFormat *fmt_found = NULL;
	     void *i = 0;
	     int score_max, score;
	 while ((fmt = av_muxer_iterate(&i))) {
	         score = 0;
			 const char *fmtName = fmt->name;
			 __android_log_print(ANDROID_LOG_INFO,"myTag","fmtName:\n%s",fmtName);
	     }
}

运行程序后可再logcat控制台看到如下内容:

2023-06-08 14:56:30.886  8824-8824  myTag                   com.test.ffmpeg                      I  fmtName:
                                                                                                    a64
2023-06-08 14:56:30.886  8824-8824  myTag                   com.test.ffmpeg                      I  fmtName:
                                                                                                    ac3
2023-06-08 14:56:30.886  8824-8824  myTag                   com.test.ffmpeg                      I  fmtName:
                                                                                                    adts
2023-06-08 14:56:30.886  8824-8824  myTag                   com.test.ffmpeg                      I  fmtName:
                                                                                                    adx
2023-06-08 14:56:30.886  8824-8824  myTag                   com.test.ffmpeg                      I  fmtName:
                                                                                                    aiff
2023-06-08 14:56:30.886  8824-8824  myTag                   com.test.ffmpeg                      I  fmtName:
                                                                                                    alp
2023-06-08 14:56:30.886  8824-8824  myTag                   com.test.ffmpeg                      I  fmtName:
                                                                                                    amr
2023-06-08 14:56:30.886  8824-8824  myTag                   com.test.ffmpeg                      I  fmtName:
                                                                                                    amv
                                                                                                    ...
                                                                                                    ...

不过这种代码编写方式不太好,没办法发挥ide的特性,想了解使用ide编写代码的方式可以查看Mac环境下使用Clion编译测试运行Ffmpeg。
下面是根据网上其余人的需求写的代码,打印出支持的编解码类型

#include 
#include "com_jni_FFmpeg.h"

#include 
#include 
#include 
#include 
#include "libavcodec/avcodec.h" //编码器格式
#include 
#include 

void iterateAvOutFormat(){
	const AVOutputFormat *fmt = NULL;
	    const AVOutputFormat *fmt_found = NULL;
	    void *i = 0;
	    int score_max, score;
	 
	while ((fmt = av_muxer_iterate(&i))) {
	        score = 0;
				 const char *fmtName = fmt->name;
				 __android_log_print(ANDROID_LOG_INFO,"myTag","fmtName:\n%s",fmtName);
	    }
}
void printMp2Decoder(){
	char info[40000] = {0};
	const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_MP2);
	AVCodecContext *avcodecContext = avcodec_alloc_context3(codec);
	const char *license = avcodec_license();
	if(avcodecContext->codec_type == AVMEDIA_TYPE_VIDEO){//音频
		sprintf(info,"%s[Video]",info);
	}else{//视频	
		sprintf(info,"%s[Audio]",info);
	}
	sprintf(info,"%s[%10s]\n",info,codec->name);
	avcodec_free_context(&avcodecContext); //释放内存
	__android_log_print(ANDROID_LOG_INFO,"myTag","info:\n%s",info);
}

void iterateMimeType(AVCodecDescriptor *desc){
    if (desc == NULL){
        return;
    }
    const char *const *const mime_types = desc->mime_types; //这是个数组类型
    if (mime_types == NULL){
        return;
    }
    char** current_str = mime_types;
    while (*current_str != NULL) {
//        printf("类型:%s\n", *current_str);
        __android_log_print(ANDROID_LOG_INFO,"myTag","mimeType:\n%1c",*current_str);
        current_str++;
    }
}

//打印编码信息
//代码源自: https://ffmpeg.org/doxygen/trunk/opt__common_8c_source.html#l00635
void iterateCodec(){
	     const AVCodecDescriptor *desc = NULL;
	     const AVCodecDescriptor **codecs;
		 unsigned nb_codecs = 0, i = 0;
		  while ((desc = avcodec_descriptor_next(desc))){
			nb_codecs++; //计算有多少个编码器
			enum AVCodecID avCodecId = desc->id;
			enum AVMediaType avCodecType = desc->type;
			const char *avCodecName = desc->name;
			const char *avCodecLongName = desc->long_name;
			const char *const *const mime_types = desc->mime_types; //这是个数组类型
			__android_log_print(ANDROID_LOG_INFO,"myTag","avCodecName:\n%1s, avCodecLongName: %2s",avCodecName, avCodecLongName);
			iterateMimeType(desc);
		  }
}

JNIEXPORT void JNICALL Java_com_jni_FFmpeg_run(JNIEnv *env, jclass obj) {
	// printMp2Decoder();
	// iterateAvOutFormat();
	iterateCodec();
}

六、参考链接:

  1. ndk-build 脚本
  2. Android studio添加第三方库和so
  3. Mac环境下使用Clion编译测试运行Ffmpeg

你可能感兴趣的:(C++,ffmpeg)