音视频开发之一-交叉编译ffmpeg so库

音视频开发之一-交叉编译ffmpeg so库

1. ffmpeg简介

ffmpeg是一个开源的音视频处理框架,它提供了丰富的功能和接口,可以实现音视频的编解码、转换、滤镜、播放、推流等操作。ffmpeg由以下几个主要组件构成:

  • libavcodec:一个包含了各种音视频编解码器的库,支持多种格式和协议。
  • libavformat:一个负责封装和解封装音视频数据的库,可以处理多种容器格式。
  • libavfilter:一个负责对音视频进行各种滤镜处理的库,例如裁剪、旋转、水印等。
  • libavdevice:一个提供了访问捕获和渲染设备的接口的库,例如摄像头、麦克风、屏幕等。
  • libavutil:一个包含了一些通用的工具函数和结构体的库,例如日志、错误码、内存管理等。
  • libswscale:一个负责对图像进行缩放和色彩转换的库。
  • libswresample:一个负责对音频进行重采样和混合的库。

2. ffmpeg源码下载地址

ffmpeg是一个开源项目,它的源码托管在GitHub上,可以通过以下命令克隆到本地:

git clone https://github.com/FFmpeg/FFmpeg.git

也可以直接从GitHub上下载zip压缩包或者tar.gz压缩包。

3. ffmpeg的编译命令

为了在安卓平台上使用ffmpeg,我们需要使用NDK(Native Development Kit)工具链来交叉编译出适用于不同CPU架构(如armeabi-v7a, arm64-v8a, x86, x86_64等)的so(shared object)库文件。NDK工具链提供了gcc或clang两种编译器来进行交叉编译。

ffmpeg本身提供了一个configure脚本来配置编译选项,并生成makefile文件。我们可以通过在终端中运行以下命令来查看configure脚本支持的所有选项:

./configure --help

其中比较重要的选项有:

  • --prefix:指定生成文件的输出目录,默认为当前目录下的install文件夹。
  • --enable-shared:指定生成动态链接库(so文件),默认为否。
  • --disable-static:指定不生成静态链接库(a文件),默认为否。
  • --enable-small:指定优化生成文件大小,可能会牺牲一些性能,默认为否。
  • --disable-all:指定禁用所有组件,默认为否。
  • --enable-gpl:指定启用GPL许可证下的组件,默认为否。
  • --enable-nonfree: 指定启用非自由组件,默认为否。
  • --enable-libx264: 指定启用libx264编码器,默认为否。
  • --enable-libx265: 指定启用libx265编码器,默认为否。

除此之外,还有一些针对安卓平台特有的选项:

  • --target-os=android: 指定目标操作系统为安卓,默认为linux。
  • --cross-prefix: 指定交叉编译器的前缀,例如arm-linux-androideabi-。
  • --arch: 指定目标CPU架构,例如arm, arm64, x86, x86_64等。
  • --cpu: 指定目标CPU型号,例如cortex-a8, cortex-a53等。
  • --sysroot: 指定系统根目录,通常为NDK中的platforms目录下对应的API级别和架构的目录。
  • --extra-cflags: 指定额外的C编译器参数,例如-I, -D等。
  • --extra-ldflags: 指定额外的链接器参数,例如-L, -l等。

根据不同的需求和配置,我们可以组合出不同的configure命令来编译ffmpeg。例如,以下命令可以编译出一个支持x264和x265编码器,并且适用于arm64-v8a架构的ffmpeg库:

./configure \
--prefix=install/arm64-v8a \
--enable-shared \
--disable-static \
--enable-small \
--disable-all \
--enable-gpl \
--enable-nonfree \
--enable-libx264 \
--enable-libx265 \
--target-os=android \
--cross-prefix=aarch64-linux-android- \
--arch=aarch64 \
--cpu=cortex-a53 \
--sysroot=$NDK/platforms/android-21/arch-arm64/ \ 
--extra-cflags="-I$NDK/sysroot/usr/include/aarch64-linux-android -I$X264/include -I$X265/include" \ 
--extra-ldflags="-L$X264/lib -L$X265/lib"

其中"$NDK"是NDK工具链所在的目录,"$X264"和X265是"$libx264"和libx265库所在的目录。

运行configure命令后,会生成一个config.h文件和一个makefile文件。我们可以通过运行以下命令来查看config.h文件中定义了哪些宏³:

cat config.h | grep '#define'

我们可以通过运行以下命令来查看makefile文件中定义了哪些变量和规则:

cat makefile | grep '='

如果没有出现错误信息,则说明configure成功。接下来我们可以通过运行以下命令来开始编译过程:

make -j4

其中-j4表示使用4个线程进行并行编译,可以加快速度。

如果没有出现错误信息,则说明make成功。接下来我们可以通过运行以下命令来将生成的文件复制到指定的输出目录:

make install

这样就完成了一次交叉编译过程。我们可以重复上述步骤,并修改相应的选项来针对不同的CPU架构进行交叉编译。

4. ffmpeg的编译脚本

为了方便管理和重用ffmpeg的交叉编译过程,我们可以将上述步骤封装成一个脚本文件,并传入一些参数来控制不同的选项。以下是一个示例脚本:

#!/bin/bash

# 定义变量
NDK=/home/ndk/android-ndk-r21d # NDK工具链所在目录,请根据实际情况修改
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64 # 工具链子目录,请根据实际情况修改
API=21 # API级别,请根据实际情况修改

function build_android {
echo "Compiling FFmpeg for $CPU"
./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-symver \
--cross-prefix=$CROSS_PREFIX \
--target-os=android \
--arch=$ARCH \
--cpu=$CPU \
--cc=$CC \ 
--cxx=$CXX \ 
--enable-cross-compile \ 
--sysroot=$SYSROOT \ 
$ADDITIONAL_CONFIGURE_FLAG
make clean
make -j4
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++
SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot # 系统根目录,请根据实际情况修改
CROSS_PREFIX=$TOOLCHAIN/bin/aarch64-linux-android-
PREFIX=$(pwd)/android/$CPU # 输出目录,请根据实际情况修改
ADDITIONAL_CONFIGURE_FLAG=
build_android

# x86_64
ARCH=x86_64
CPU=x86-64
CC=$TOOLCHAIN/bin/x86_64-linux-android$API-clang 
CXX=$TOOLCHAIN/bin/x86_64-linux-android$API-clang++ 
SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot # 系统根目录,请根据实际情况修改
CROSS_PREFIX=$TOOLCHAIN/bin/x86_64-linux-android-
PREFIX=$(pwd)/android/$CPU # 输出目录,请根据实际情况修改 
ADDITIONAL_CONFIGURE_FLAG= --enable-asm --enable-yasm --disable-mmx --disable-inline-asm 
build_android

# 其他架构可以类似地添加和修改参数

这个脚本定义了一个build_android函数,用来执行configure, make和make install命令,并传入不同的参数。然后针对不同的CPU架构,定义了一些变量,并调用build_android函数。这样就可以一次性编译出多个架构的ffmpeg库。

我们可以将这个脚本保存为一个文件,例如build_ffmpeg.sh,并赋予可执行权限。然后在终端中运行以下命令来开始编译过程:

./build_ffmpeg.sh

如果没有出现错误信息,则说明编译成功。我们可以在输出目录下找到生成的ffmpeg库文件。

5. ffmpeg的使用方法

在Android Studio中,我们可以通过以下步骤来引用和使用ffmpeg库:

在app模块下创建一个jniLibs文件夹,并将生成的ffmpeg库文件复制到对应的子文件夹中,例如arm64-v8a, x86_64等。
在app模块下创建一个src/main/cpp文件夹,并在其中创建一个native-lib.cpp文件,用来编写调用ffmpeg库函数的本地代码。
在app模块下创建一个src/main/java/com/example/ffmpegdemo/MainActivity.java文件,用来编写调用本地代码的Java代码。
在app模块下创建一个CMakeLists.txt文件,用来配置NDK相关参数。
在app模块下打开build.gradle文件,在defaultConfig节点下添加以下内容:

externalNativeBuild {
    cmake {
        cppFlags ""
    }
}
ndk {
    abiFilters 'arm64-v8a', 'x86_64' // 根据需要选择支持的架构,请与jniLibs中保持一致 
}

在app模块下打开build.gradle文件,在android节点下添加以下内容:

externalNativeBuild {
    cmake {
        path "CMakeLists.txt" // 指定CMakeLists.txt所在路径,请根据实际情况修改
    }
}

在native-lib.cpp文件中,添加以下内容:

#include <jni.h>
#include <string>
#include <android/log.h>

// 引入ffmpeg头文件
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/log.h"
}

#define LOG_TAG "FFmpegDemo"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

// 定义一个本地函数,用来打印ffmpeg版本信息
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ffmpegdemo_MainActivity_getFFmpegVersion(
        JNIEnv* env,
        jobject /* this */) {
    std::string version = av_version_info();
    LOGE("FFmpeg version: %s", version.c_str());
    return env->NewStringUTF(version.c_str());
}

在MainActivity.java文件中,添加以下内容:

package com.example.ffmpegdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // 加载本地库文件
    static {
        System.loadLibrary("native-lib");
    }

    // 声明本地函数,用来调用本地代码
    public native String getFFmpegVersion();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 获取文本视图对象,并设置文本为ffmpeg版本信息
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(getFFmpegVersion());
    }
}

在CMakeLists.txt文件中,添加以下内容:

cmake_minimum_required(VERSION 3.4.1)

# 设置变量,指定ffmpeg库所在目录,请根据实际情况修改 
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../jniLibs)

# 添加子目录,指定源码所在目录,请根据实际情况修改 
add_subdirectory(${distribution_DIR}/${ANDROID_ABI} ${CMAKE_CURRENT_BINARY_DIR}/ffmpeg)

# 添加本地库,指定源码所在文件,请根据实际情况修改 
add_library( native-lib SHARED src/main/cpp/native-lib.cpp )

# 查找系统库,并赋值给变量 
find_library( log-lib log )

# 链接本地库和系统库 
target_link_libraries( native-lib ffmpeg ${log-lib} )

同步项目并运行应用,在模拟器或真机上查看效果。如果一切正常,应该可以看到文本视图显示了ffmpeg的版本信息,并且在Logcat中也可以看到相应的日志输出。
以上就是一个简单的示例,展示了如何在Android Studio中引用和使用ffmpeg库。我们可以根据不同的需求,在native-lib.cpp文件中编写更多的调用ffmpeg库函数的代码,并在MainActivity.java文件中编写更多的调用本地代码的代码。例如,我们可以实现视频播放器、视频编辑器、视频转码器等功能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容