ffmpeg 使用ndk编译、macos编译以及使用sourcetrail阅读ffmpeg源码介绍

一、mac系统下面通过ndk编译库

环境

  • ndk:NDK r20b (对应android-ndk-r20b-darwin-x86_64.zip)
  • FFmpeg 我用的版本是4.2.2

进入到ffmpeg目录下面。通过vim打开configure进行配置。

默认编译出来的so库包括avcodec、avformat、avutil、avdevice、avfilter、swscale、avresample、swresample、postproc,编译出来so是个软链接,真正so名字后缀带有一长串主版本号与子版本号,形如:libavcodec.so.57, 这样的so名字在Adnroid平台无法识别。所以我们需要修改一下configure使其生成以 .so 结尾格式的动态库。

用vim打开

vim configure

冒号进入命令模式, 输入 /SLIBNAME_WITH_MAJOR ,搜索找到如下命令行:

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

保存好了之后退出vim。

进入到ffmpeg根目录下面,创建一个文件,名为build_ffmpeg.sh

注意脚本需要修改 ndk 路径

复制以下内容到新建的build_ffmpeg.sh文件里面。

需要什么架构的可以自己选择编译

#!/bin/bash
 
#你的NDK路径
NDK=/Users/a888/android-ndk-r20b
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64
API=21
 
echo "进入FFmpeg编译脚本"
 
function build_android
{
echo "Compiling FFmpeg for $CPU"
./configure \
    --prefix=$PREFIX \
    --libdir=$LIB_DIR \
    --enable-shared \
    --disable-static \
    --enable-jni \
    --disable-doc \
    --disable-symver \
    --disable-programs \
    --target-os=android \
    --arch=$ARCH \
    --cpu=$CPU \
    --cc=$CC \
    --cxx=$CXX \
    --enable-cross-compile \
    --sysroot=$SYSROOT \
    --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
    --extra-ldflags="$ADDI_LDFLAGS" \
    --disable-asm \
    $COMMON_FF_CFG_FLAGS
make clean
make -j8 # 这里是定义用几个CPU编译
make install
echo "The Compilation of FFmpeg for $CPU is completed"
}
 
#################### armv7-a ################################

#echo "开始编译FFmpeg(armeabi-v7a)"
#OUTPUT_FOLDER="armeabi-v7a"
#ARCH="arm"
#CPU="armv7-a"
#TOOL_CPU_NAME=armv7a
#TOOL_PREFIX="$TOOLCHAIN/bin/arm-linux-androideabi"
#
#CC="$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang"
#CXX="$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang++"
#SYSROOT="$NDK/toolchains/llvm/prebuilt/darwin-x86_64/sysroot"
#PREFIX="${PWD}/android/$OUTPUT_FOLDER"
#LIB_DIR="${PWD}/android/libs/$OUTPUT_FOLDER"
#OPTIMIZE_CFLAGS="-march=$CPU"
#build_android


#################### armv8-a ################################

 echo "开始编译FFmpeg(arm64-v8a)"
 OUTPUT_FOLDER="arm64-v8a"
 ARCH=arm64
 CPU="armv8-a"
 TOOL_CPU_NAME=aarch64 
 TOOL_PREFIX="$TOOLCHAIN/bin/$TOOL_CPU_NAME-linux-android"

 CC="$TOOL_PREFIX$API-clang"
 CXX="$TOOL_PREFIX$API-clang++"
 SYSROOT="$NDK/toolchains/llvm/prebuilt/darwin-x86_64/sysroot"
 PREFIX="${PWD}/android/$OUTPUT_FOLDER"
 LIB_DIR="${PWD}/android/libs/$OUTPUT_FOLDER"
 OPTIMIZE_CFLAGS="-march=$CPU"
 build_android
 
#################### x86 ################################
 
# echo "开始编译FFmpeg(x86)"
# OUTPUT_FOLDER="x86"
# ARCH="x86"
# CPU="x86"
# TOOL_CPU_NAME="i686"
# TOOL_PREFIX="$TOOLCHAIN/bin/${TOOL_CPU_NAME}-linux-android"
 
# CC="$TOOL_PREFIX$API-clang"
# CXX="$TOOL_PREFIX$API-clang++"
# SYSROOT="$NDK/toolchains/llvm/prebuilt/darwin-x86_64/sysroot"
# PREFIX="${PWD}/android/$OUTPUT_FOLDER"
# LIB_DIR="${PWD}/android/libs/$OUTPUT_FOLDER"
# OPTIMIZE_CFLAGS="-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32"
# build_android
 
#################### x86_64 ################################
 
# echo "开始编译FFmpeg(x86_64)"
# OUTPUT_FOLDER="x86_64"
# ARCH="x86_64"
# CPU="x86-64"
# TOOL_CPU_NAME="x86_64"
# TOOL_PREFIX="$TOOLCHAIN/bin/${TOOL_CPU_NAME}-linux-android"
 
# CC="$TOOL_PREFIX$API-clang"
# CXX="$TOOL_PREFIX$API-clang++"
# SYSROOT="$NDK/toolchains/llvm/prebuilt/darwin-x86_64/sysroot"
# PREFIX="${PWD}/android/$OUTPUT_FOLDER"
# LIB_DIR="${PWD}/android/libs/$OUTPUT_FOLDER"
# OPTIMIZE_CFLAGS="-march=$CPU"
# build_android

需要给出权限,进入到ffmpeg对应的根目录下面。

运行完了授权命令之后执行./build_ffmpeg.sh

chmod 777 build_ffmpeg.sh
./build_ffmpeg.sh

执行完成,在以下目录获取到so文件和头文件。

二、macos m1方式编译ffmpeg库(适合arm64架构使用)

不推荐的原因是感觉通过此种方式进行集成的demo比较少,出错了难解决问题。上面那种编译的方式存在挺多例子,遇上了问题更加容易解决。

本人也是因为在使用此种方式进行实践遇上了很多坑所以没有继续尝试,采取了第一种编译ffmpeg的方法,当然,编译的方法有很多种,最关键的是大致理解脚本里面的命令配置以及能根据自己的需求去设置脚本。

第一步,先安装brew。

这里,brew是什么东西?

简介:brew就是mac上面一个安装软件包的一个工具。

里面就只有3个基本命令,就是安装、更新、和卸载

安装brew:https://blog.csdn.net/rockvine/article/details/121895416

https://www.zhihu.com/question/431585800(正在尝试的版本)

如果没有安装git,会出现错误需要安装git(进行中)

通过提示,安装git就完事~

执行安装brew,在m1 pro出现以下的错误。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGRGBMAN-1657905361421)(ffmpeg.assets/v2-ade07de0dba7c557a18ec3be7237b4f9_r.jpg)]

这是因为M1芯片的包安装位置不在是以前的/usr/local/而是/opt/homebrew,所以要将配置文件里的环境变量改过来。

#操作步骤,打开终端
cd ~   #进入用户的家的目录
mkdir .zshrc    #创建对应的文件夹,如果存在了将不会继续创建
open -e .zshrc   #打开这个文件夹

#将里面关于brew的路径配置换成如下:
  export PATH=/opt/homebrew/bin:$PATH
	export PATH=/opt/homebrew/sbin:$PATH
   
#保存好之后,终端输入:
  source .zshrc

#最后,通过下面的命令验证是否brew安装成功
	brew -v
 

解释:
这个东西在访达里面.zshrc是不能直接看到的哦,是属于隐藏文件。在这里有人可能就会有疑问,zshrc是什么呢?

首先我们需要去了解什么是zsh,他是属于一个shell,macos从卡特琳系统版本开始,macos就将之前使用的bash改成zshell,本质上来说,他就是一个shell。

那么.zshrc是什么呢?我的理解就是一个配置文件,配置某些软件运行的时候找到他的一个地址,就是配置在这个地方。(类似于windows的高级环境配置变量)

第二个要说的是source命令。

说到这个命令,就要说一下shell脚本运行的两种模式。

  • 创建子进程,在子进程里面进行新的命令的执行。如在终端里面执行./a.shell,a.shell是我们新创建的一个脚本
  • 不创建子进程,在本进程里面继续进行新的命令。这个方法所指向的就是source. x x x 的形式

另外,还有一个和source命令比较接近的命令,就是exe命令。

exec命令在执行时会把当前的shell process关闭,然后换到后面的命令继续执行。

到了这里,我们又终于可以继续安装我们的ffmpeg了。

参考链接:

通过终端,运行以下的命令。

Git clone https://git.ffmpeg.org/ffmpeg.git

cd到ffmpeg到目录下面。执行以下的命令

./configure --prefix=/usr/local/ffmpeg --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264 --enable-libx265 --enable-filter=delogo --enable-debug --disable-optimizations --enable-libspeex --enable-videotoolbox --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --cc=clang --host-cflags= --host-ldflags= --disable-x86asm --enable-ffplay

要想知道对应的命令是什么意思可以通过./configure --help 查看

遇到类似报错,就是缺少对应的库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g0fJ3Cnj-1657905361422)(ffmpeg.assets/image-20220630222829176.png)]

然后尝试网上的方法,通过以下代码尝试。

brew install fdk-aac&&brew install x264&&brew install x265&&brew install speex&&brew install pkg-config&&brew  install sdl2

然后继续执行

./configure --prefix=/usr/local/ffmpeg --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264 --enable-libx265 --enable-filter=delogo --enable-debug --disable-optimizations --enable-libspeex --enable-videotoolbox --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --cc=clang --host-cflags= --host-ldflags= --disable-x86asm --enable-ffplay
make &&sudo make install

完成

三、将ffmpeg集成到安卓项目中

新建一个android 的C++项目。

将编译好的include文件以及so库放到下图中对应的位置。

如果途中找不到对应的文件夹,则自己进行创建

在src/main下建一个jniLibs目录,并把编译的库拷贝进去。结构如下:

如果找不到对应的文件夹,记得需要新建。

Build.gradle 文件配置

如果配置看不懂的自行了解一下下面的配置命令

plugins {
    id 'com.android.application'
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.ffmpegdemo"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ''
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.18.1'
        }
        defaultConfig {
            ndk {
                abiFilters 'arm64-v8a'
            }
        }
      //下面这个方式在不同的gradle版本可能会报错
//        sourceSets {
//            main {
                jniLibs.srcDirs = ["src/main/jniLibs"]
//            }
//        }
        sourceSets {
            main {
                jniLibs.srcDirs = ['jniLibs']
            }
        }

    }

    buildFeatures {
        viewBinding true
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

cmakelist配置

暂时不过多解释,如果还有看不懂的,请自行进行cmakelist命令的相关了解。

# 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.18.1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
set(ffmpeg_lib_dir ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
set(ffmpeg_head_dir ${CMAKE_SOURCE_DIR})

include_directories(${ffmpeg_head_dir}/ffmpeg)
include_directories(${ffmpeg_head_dir}/include)

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 )
# Declares and names the project.

project("ffmpegdemo")

# 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.
        ffmpegdemo

        # Sets the library as a shared library.
        SHARED

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

# 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.
        ffmpegdemo
        avutil
        swresample
        avcodec
        avfilter
        swscale
        avformat
        avdevice
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

在需要引入本地接口的地方,进行动态库加载。

在这里插入图片描述

在jni层进行ffmpeg函数的调用。

只需要在java层进行路径的传入就可以调用了。

#include <jni.h>
#include <string>
extern "C" {
#include "ffmpeg/libavformat/avformat.h"
#include "ffmpeg/libavcodec/avcodec.h"
#include "ffmpeg/libavutil/macros.h"
#include "ffmpeg/libavutil/error.h"
#include "ffmpeg/libavutil/file.h"
#define ANDROID_LOG_INFO "nisen"

}

struct buffer_data {
    uint8_t *ptr;
    size_t size; ///< size left in the buffer
};


static int read_packet(void *opaque, uint8_t *buf, int buf_size)
{
    struct buffer_data *bd = (struct buffer_data *)opaque;
    buf_size = FFMIN(buf_size, bd->size);
    if (!buf_size)
        return AVERROR_EOF;
//    __android_log_print(ANDROID_LOG_INFO, "lhr","ptr:%p size:%zu\n", bd->ptr, bd->size);
    /* copy internal buffer data to buf */
    memcpy(buf, bd->ptr, buf_size);
    bd->ptr  += buf_size;
    bd->size -= buf_size;
    return buf_size;
}

void StartPlay(const std::string &path) {
    AVFormatContext *fmt_ctx = nullptr;
    AVIOContext *avio_ctx = nullptr;
    uint8_t *buffer = nullptr, *avio_ctx_buffer = nullptr;
    size_t buffer_size, avio_ctx_buffer_size = 4096;
    char *input_filename = nullptr;
    int ret = 0;
    struct buffer_data bd = { 0 };
    input_filename = const_cast<char *>(path.data());

    ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL);
    if (ret < 0) {
        goto end;
    }

    bd.ptr  = buffer;
    bd.size = buffer_size;

    if (!(fmt_ctx = avformat_alloc_context())) {
        ret = AVERROR(ENOMEM);
        goto end;
    }
    avio_ctx_buffer = static_cast<uint8_t *>(av_malloc(avio_ctx_buffer_size));
    if (!avio_ctx_buffer) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size,
                                  0, &bd, &read_packet, NULL, NULL);
    if (!avio_ctx) {
        ret = AVERROR(ENOMEM);
        goto end;
    }
    fmt_ctx->pb = avio_ctx;
    ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
    if (ret < 0) {
//        __android_log_print(ANDROID_LOG_INFO, "lhr", "Could not open input\n");
        goto end;
    }

    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if (ret < 0) {
//        __android_log_print(ANDROID_LOG_INFO, "lhr","Could not find stream information\n");
        goto end;
    }
    av_dump_format(fmt_ctx, 0, input_filename, 0);
//    __android_log_print(ANDROID_LOG_INFO, "lhr","av_dump_format finish");

    end:
    avformat_close_input(&fmt_ctx);
    /* note: the internal buffer could have changed, and be != avio_ctx_buffer */
    if (avio_ctx) {
        av_freep(&avio_ctx->buffer);
        av_freep(&avio_ctx);
    }
    av_file_unmap(buffer, buffer_size);
    if (ret < 0) {
        return;
    }
}




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



extern "C" JNIEXPORT void JNICALL
Java_com_example_ffmpegdemo_MainActivity_startPlayJNI(
        JNIEnv* env,
        jobject thiz, jstring str) {
    const jsize len = env->GetStringUTFLength(str);
    const char* strChars = env->GetStringUTFChars(str, (jboolean *)0);
    std::string path(strChars,  len);
    StartPlay(path);
    env->ReleaseStringUTFChars(str, strChars);
}

四、使用sourcetrail阅读ffmpeg源码相关

我对ffmpeg 的学习计划是。通过一个简单的项目去在上层跑起来项目,然后通过官网的demo熟悉ffmpeg 的API。

为了提升对源码的把握能力,企图从整体上大致把握主要函数。这里,分享一个阅读源码的好方法。-sourcetrail,可以图形化查看代码和代码之间的对应关系,以及在客户端 里面支持函数跳转等功能。

另一方面来说遇上了其他的源码的时候也可以进行相同的方式进行对应的代码阅读。在看源码的时候也可以多学习一下优秀代码的写法,在一定的程度上提高大型项目的阅读能力以及设计代码的能力

下面介绍sourcetrail如何去阅读ffmpeg源码的使用方法。

一、下载

sourcetrail下载地址

二、新建项目

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在1⃣️里面选择ffmpeg根目录就行。然后点击create,等待引入完毕即可。

完成解析~~~

下面,简单举一个栗子来体验sourcetrail的效果是如何。

此次是结合到雷神的文章进行分析

ffmpeg 源代码简单分析 : av_register_all()

在sourcetrail的搜索框中搜查对应的函数。查看下图就是source呈现的最直观效果

图形中的每一条线都可以点击,点击之后右边的代码会自动跳到引用或者是被引用的地方。

在阅读了雷神的部分文章之后,配合这个源码阅读的方法,自己觉得会比单纯的看文章更加容易理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值