Android编译ffmpeg源码,编译 Android 下可执行命令的 FFmpeg

该博客介绍了如何在Android中利用FFmpeg命令行工具生成视频封面图片,通过修改FFmpeg源码使其适应Android环境。主要步骤包括导入FFmpeg源码、修改main方法、处理程序退出、增加日志输出以及编写执行命令的工具类方法。此外,建议使用现有的开源库如mobile-ffmpeg,以简化集成过程和提高稳定性。
摘要由CSDN通过智能技术生成

使用 FFmpeg 生成视频封面图时,其实能够直接使用 FFmpeg 相关命令截取一帧的图像数据保存到本地,而后加载到 ImageView 上。android

有时候使用命令确实比写代码更加简单和令人轻松一点。git

因此这一篇是讲解如何导入 FFmpeg 相关源码 而后如何执行命令行工具的博客,可是其实这只是个 Demo 而已,由于有不少细节须要处理,推荐直接使用开源库。github

导入源码

从FFmpeg源码中导入 cmdutils.c、cmdutils.h、config.h、ffmpeg.c、ffmpeg.h、web

ffmpeg_filter.c、ffmpeg_hw.c、ffmpeg_opt.c 这几个源码。shell

通常存放在 fftools 目录下。config.h 若是编译生成目录下没有,就能够直接使用ffmpeg 根目录下的 config.h。api

编写 CmakeList

# 设置构建本机库文件所需的 CMake的最小版本

cmake_minimum_required(VERSION 3.4.1)

#添加头文件的搜索路径

include_directories(src/main/cpp/include)

#设置查找动态库位置

set(LINK_DIR ${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI})

link_directories(${LINK_DIR})

#找到全部的so库,存放在全局变量SO_DIR中

file(GLOB SO_DIR ${LINK_DIR}/*.so)

#找到全部的源文件,存放在全局变量中

#file(GLOB FFMPEG_DIR src/main/cpp/ffmpeg/*.c)

#message("FFMPEG_DIR == ${FFMPEG_DIR}")

file(GLOB CPP_DIR src/main/cpp/*.cpp)

file(GLOB FFMPEG_DIR src/main/cpp/include/*.c)

# 添加本身写的 C/C++源文件

add_library(utils #so名称

SHARED #动态库

${CPP_DIR}

${FFMPEG_DIR}

)

#  依赖 NDK中自带的log库

find_library(log-lib log)

#  连接库

target_link_libraries(

utils

${SO_DIR}

jnigraphics

${log-lib})

我是将 ffmpeg 的源码和以前生成的 ffmpeg 头文件都放在了 cpp/include 目录下。bash

这样在 CmakeList 中使用 include_directories 就能够直接找到全部的头文件,而后将 ffmpeg 的源码和本身写的工具类源码关联起来就好了。微信

修改 FFmpeg 的源码

修改ffmpeg.c的main方法名称为exe_cmd,并在ffmpeg.h头文件加上一样名称的方法声明。app

//ffmpeg.c

int exe_cmd(int argc, char **argv){

...

}

//ffmpeg.h

int exe_cmd(int argc, char **argv);

原生命令行工具在执行完 FFmpeg 命令后都会退出程序,可是在 Android 里面可不能这样,因此咱们要修改 FFmpeg 结束程序的函数。编辑器

修改 cmdutils.c 和 cmdutils.h,注释掉退出程序的代码,而且增长一个int的返回值。

//cmdutils.c

int exit_program(int ret){

//    if (program_exit)

//        program_exit(ret);

//    exit(ret);

return ret;

}

//cmdutils.h

int exit_program(int ret);

而且在 Android 里面咱们确定是执行完一条命令,接着还会继续执行其余命令,因此咱们须要从新初始化一些关键变量的值。

找到 ffmpeg.c 中的 ffmpeg_cleanup函数,在末尾将一些关键变量从新初始化。

//ffmpeg.c

static void ffmpeg_cleanup(int ret){

...

nb_filtergraphs = 0;

nb_output_files = 0;

nb_output_streams = 0;

nb_input_files = 0;

nb_input_streams = 0;

}

最后在 main 函数末尾调用ffmpeg_cleanup 函数。

int exe_cmd(int argc, char **argv){

...

//    exit_program(received_nb_signals ? 255 : main_return_code);

ffmpeg_cleanup(0);

}

增长 FFmpeg 日志输出

在 ffmpeg.c 中找到 log_callback_null 的函数,添加以下代码,原代码块是空实现。

#include "android/log.h"

#define logDebug(...) __android_log_print(ANDROID_LOG_DEBUG,"MainActivity",__VA_ARGS__)

static void log_callback_null(void *ptr, int level, const char *fmt, va_list vl){

static int print_prefix = 1;

static int count;

static char prev[1024];

char line[1024];

static int is_atty;

av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);

strcpy(prev, line);

logDebug("ffmpeg log ----- %s", line);

}

在 main 函数中调用 log_callback_null 函数。

int exe_cmd(int argc, char **argv){

av_log_set_callback(log_callback_null);

int i, ret;

...

}

编写工具类方法

在 MainActivity中 增长 exeCmd(String[] cmd) 方法。

public static native int exeCmd(String[] cmd);

在 ffmpeg_utils.cpp 增长 jni 方法。

JNIEXPORT jint JNICALL

Java_demo_simple_example_1ffmpeg_MainActivity_exeCmd(JNIEnv *env, jclass clazz, jobjectArray cmd){

int argc = env->GetArrayLength(cmd);

logDebug("argc == %d", argc);

char *argv[argc];

for (int i = 0; i 

jstring str = (jstring) env->GetObjectArrayElement(cmd, i);

argv[i] = (char *) env->GetStringUTFChars(str, JNI_FALSE);

logDebug("%s ", argv[i]);

}

return exe_cmd(argc, argv);

//    return 1;

}

执行命令。

private void exeCmd() {

String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator

+ "get_cover1.mp4";

String outPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator

+ "video.flv";

File outFile = new File(outPath);

if (outFile.exists()) {

outFile.delete();

}

//裁剪个1s视频

String cmd = "ffmpeg -ss 00:00:00 -t 00:00:10 -i " + path + " -vcodec copy -acodec copy " + outPath;

String[] cmdArr = cmd.split(" ");

int result = exeCmd(cmdArr);

Log.d(TAG, "exe cmd result == " + result);

}

查看日志输出

demo.simple.example_ffmpeg D/MainActivity: ffmpeg log ----- Output file #0 (/storage/emulated/0/video.flv):

demo.simple.example_ffmpeg D/MainActivity: exe cmd result == 0

执行命令的返回值 ==0,而且也看到确实文件已经生成出来了,咱们 adb pull 把文件导出到桌面用 ffprobe 或 ffplay 看看。

ffprobe video.flv

major_brand : mp42

Duration: 00:00:01.07, start: 0.033000, bitrate: 3130 kb/s

Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1080x1920, 3390 kb/s, 30 fps, 30 tbr, 1k tbn, 60 tbc

Stream #0:1: Audio: aac (LC), 48000 Hz, stereo, fltp, 317 kb/s

能够看到确实裁剪生成了一个1秒的视频,虽而后缀名咱们用的 .flv,可是其实咱们是拷贝的视频编码,因此仍是mp4的封装格式。

源码:

https://github.com/simplepeng/AndroidExamples/tree/master/example_ffmpeg

使用已有的轮子

上面的例子并非一个完善的工具类,好比缺乏 Native 层的线程支持,出现错误就会直接闪退,缺乏进度回调等,因此仍是直接使用现成的轮子比较靠谱,只是咱们须要知道轮子大概是怎么造出来的就好了。

这里我推荐使用mobile-ffmpeg这个开源库,1.8k 的 star 足以证实其品质还行,直接导入编译好的aar就能够执行命令行工具链,并且能够自行编译连接不少有用的第三方library,好比 x26四、libwebp 等。

动手能力强或有特殊需求的可使用 android.sh 自行编译出 FFmpeg 头文件和动态库,以及 Android 工具链的 aar。

好比说我如今只须要一个支持 arm64-v8a 和 api16 及以上的动态库,那么我就本身新建了一个shell脚本文件:

#!/bin/bash

export ANDROID_HOME="/Users/chenpeng/Library/Android/sdk/"

export ANDROID_NDK_ROOT="/Users/chenpeng/Desktop/work_space/ndk/android-ndk-r21b/"

build() {

./android.sh \

--lts \

--disable-arm-v7a \

--disable-arm-v7a-neon \

--disable-x86 \

--disable-x86-64

}

build

执行完这个 shell 后,就会在 prebuilt 目录下生产对应的头文件,动态库,以及 aar 文件,直接拿来用就能够了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值