首先引入ffmpeg库
Android Studio 引入 FFmpeg-339/405
支持命令行工具
1. 复制以下文件到项目中
编译后的根目录下的
config.h
源码中fftools下的
cmdutils.c
cmdutils.h
ffmpeg.c
ffmpeg.h
ffmpeg_filter.c
ffmpeg_hw.c
ffmpeg_opt.c
几个文件复制到Android cpp/include目录下
2.修改CMakeLists.txt
将导入的文件一并打包到native-lib中
add_library(native-lib
SHARED
native-lib.cpp
include/cmdutils.c
include/ffmpeg.c
include/ffmpeg_filter.c
include/ffmpeg_hw.c
include/ffmpeg_opt.c)
3.修改ffmpeg.c和cmdutils.c文件
注释掉cmdutils.c中exit_program(int ret)方法的最后一句
void exit_program(int ret)
{
if (program_exit)
program_exit(ret);
// exit(ret);
}
修改ffmpeg.c,加入进度信息,和日志信息
static int progress = 0;
//实现日志回调函数
static void log_callback_null(void *ptr, int level, const char *fmt, va_list vl) {
static int print_prefix = 1;
static char prev[1024];
char line[1024];
av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);
strcpy(prev, line);
//updata progress
// char *regex = "size=[0-9]*[ ]*ts=([0-9]+[ ]*flags=)";
char *regex = ", dts [0-9]*";
regex_t comment;
regmatch_t regmatch[1];
regcomp(&comment, regex, REG_EXTENDED | REG_NEWLINE);
int status = regexec(&comment, line, sizeof(regmatch) / sizeof(regmatch_t), regmatch, 0);
if (status == 0) {
LOGE("%s", line);
progress = update_progress(line);
}
if (level <= AV_LOG_WARNING) {
LOGE("%s", line);
} else {
LOGD("%s", line);
}
}
//通过解析日志获取执行进度并更新到变量progress
int update_progress(char *srcStr) {
// 2020-04-26 17:26:33.089 31378-31447/com.zhangyu.myopengl E/ffmpeg.c: [mov,mp4,m4a,3gp,3g2,mj2 @ 0xce11bc00] stream 1, sample 965, dts 22360816
// 2020-04-26 17:26:33.089 31378-31447/com.zhangyu.myopengl E/ffmpeg.c: result=22
char *regex = "dts [0-9]*";
char str[256];
regex_t comment;
regmatch_t regmatch[1];
regcomp(&comment, regex, REG_EXTENDED | REG_NEWLINE);
int status = regexec(&comment, srcStr, 1, regmatch, 0);
if (status == 0) {
memset(str, sizeof(str), 0);
int start = regmatch[0].rm_so + 4;//因为dst 所以偏移4个字符
int end = regmatch[0].rm_eo;
int len = end - start;
memcpy(str, &srcStr[start], len);
str[len] = '\0';
}
regfree(&comment);
int result = ceil(atoi(str) / 1000000);
LOGE("result=%d\n", result);
return result;
}
//提供获取进度的接口
int get_progress() {
return progress;
}
在ffmpeg.c中增加Android日志系统
#include "android/log.h"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "ffmpeg.c", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , "ffmpeg.c", __VA_ARGS__)
在ffmpeg.c中找到main函数,将函数名修改为int ffmpeg_exec(int argc, char **argv),将最后一行代码注释掉。并且加上几个变量的重置。
// exit_program(received_nb_signals ? 255 : main_return_code);
progress = 0;
nb_filtergraphs = 0;
nb_input_streams = 0;
nb_input_files = 0;
nb_output_streams = 0;
nb_output_files = 0;
progress_avio = NULL;
input_streams = NULL;
input_files = NULL;
output_streams = NULL;
output_files = NULL;
return main_return_code;
在头文件ffmpeg.h中声明
int update_progress(char* srcStr);
int get_progress();
int ffmpeg_exec(int argc, char **argv);
4.编写JNI和C++
package com.zhangyu.myffmpeg;
import android.util.Log;
import java.util.ArrayList;
public class FFmpegCmd {
static {
System.loadLibrary("avcodec");
System.loadLibrary("avdevice");
System.loadLibrary("avfilter");
System.loadLibrary("avformat");
System.loadLibrary("avutil");
System.loadLibrary("postproc");
System.loadLibrary("swresample");
System.loadLibrary("swscale");
System.loadLibrary("native-lib");
}
//执行FFmpeg命令
private static native int run(int cmdLen, String[] cmd);
//获取命令执行进度
public static native int getProgress();
/**
* 执行FFMpeg命令, 同步模式
*
* @param cmd
* @return
*/
public static int run(ArrayList<String> cmd) {
String[] cmdArr = new String[cmd.size()];
Log.d("FFmpegCmd", "run: " + cmd.toString());
return run(cmd.size(), cmd.toArray(cmdArr));
}
public static int run(String[] cmd) {
return run(cmd.length, cmd);
}
public interface ProgressListener {
void onProgressUpdate(int progress);
}
/**
* @param srcPath 视频源路径
* @param outPath 视频输出路径
* @param duration 视频时长(ms)
* @param listener
*/
public static void transcode(String srcPath, String outPath, final long duration, final ProgressListener listener) {
new Thread(new Runnable() {
@Override
public void run() {
int ts = -1;
boolean started = false;
while (ts != 0) {
int tsTemp = getProgress();
int progress;
if (tsTemp > 0) {
started = true;
}
if (started) {
ts = tsTemp;
progress = (int) Math.ceil(ts / 10.0 / duration);
listener.onProgressUpdate(progress);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
transcode(srcPath, outPath);
}
public static void transcode(String srcPath, String outPath) {
ArrayList<String> cmd = new ArrayList<>();
cmd.add("ffmpeg");
cmd.add("-i");
cmd.add(srcPath);
cmd.add("-r");
cmd.add("30");
cmd.add("-c:v");
cmd.add("libx264");
cmd.add("-y");
cmd.add(outPath);
run(cmd);
}
}
#include <jni.h>
#include <string>
#include <android/log.h>
extern "C" {
#include <ffmpeg.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/jni.h>
}
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "ffmpeg-cmd", __VA_ARGS__)
extern "C"
JNIEXPORT jstring JNICALL
Java_com_zhangyu_myffmpeg_Demo_stringFormJni(JNIEnv *env, jobject thiz) {
std::string hello = "hell from c++";
// return env->NewStringUTF(hello.c_str());
return env->NewStringUTF(avcodec_configuration());
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_zhangyu_myffmpeg_FFmpegCmd_run(JNIEnv *env, jclass clazz, jint cmdLen, jobjectArray cmd) {
//set java vm
JavaVM *jvm = NULL;
env->GetJavaVM(&jvm);
av_jni_set_java_vm(jvm, NULL);
char *argCmd[cmdLen] ;
jstring buf[cmdLen];
for (int i = 0; i < cmdLen; ++i) {
buf[i] = static_cast<jstring>(env->GetObjectArrayElement(cmd, i));
char *string = const_cast<char *>(env->GetStringUTFChars(buf[i], JNI_FALSE));
argCmd[i] = string;
LOGD("argCmd=%s",argCmd[i]);
}
int retCode = ffmpeg_exec(cmdLen, argCmd);
LOGD("ffmpeg-invoke: retCode=%d",retCode);
return retCode;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_zhangyu_myffmpeg_FFmpegCmd_getProgress(JNIEnv *env, jclass clazz) {
return get_progress();
}
5.运行项目,此过程中会出现一些错误
从源码中找到相关文件复制到对应的目录下
#include "libavutil/thread.h"
#include "libavutil/libm.h"
#include "libavutil/internal.h"
#include "timer.h"//有多个文件,选择最外层那个即可
#include "libavutil/reverse.h"
#include "compat/va_copy.h"
#include "libavformat/network.h"
#include "os_support.h"
#include "url.h"
#include "libavcodec/mathops.h"//有多个文件,选择最外层那个即可
在cmdutils.c ffmpeg_filter.c中
删除关于avresample的内容
在cmdutils中
void show_help_children(const AVClass *class, int flags);改为clazz
void show_help_children(const AVClass *clazz, int flags);
snprintf(name, sizeof(name), "0x%"PRIx64, ch_layout);改为PRIx64前加一个空格
snprintf(name, sizeof(name), "0x%" PRIx64, ch_layout);