首先引入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);