通过编译FFmpeg并集成到Android App中实现视频转码,也可以直接执行FFmpeg命令实现视频拼接、剪切、添加水印、旋转等操作,或者在C++中引入各模块头文件直接调用FFmpeg的API满足更灵活的需求。
需要的可以直接下载头文件和.so文件,.so大概8M:FFmpeg Android动态链接库
在Android Studio中新建工程,在第一步中选择Native C++
在工程的\app\src\main\cpp\目录下新建ffmpeg目录,将编译好的.so文件和头文件拷贝到此目录,还需要将FFmpeg源码根目录下的config.h和以下文件拷贝到此目录:
fftools\cmdutils.c
fftools\cmdutils.h
fftools\ffmpeg.c
fftools\ffmpeg.h
fftools\ffmpeg_filter.c
fftools\ffmpeg_hw.c
fftools\ffmpeg_opt.c
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
配置sourceSets
sourceSets {
main {
jniLibs.srcDirs = ['src/main/cpp/ffmpeg/prebuilt/']
}
}
FFmpeg编译可以看这篇文章:FFmpeg一键编译Android armv7-a arm64
为了获取命令执行进度,需要在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=)";
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){
update_progress(line);
}
if (level <= AV_LOG_WARNING) {
LOGE("%s", line);
} else {
LOGD("%s", line);
}
}
//通过解析日志获取执行进度并更新到变量progress
int update_progress(char* srcStr) {
//char *srcStr = "Got output buffer 6 offset=0 size=3133440 ts=29267344 flags=0 ts=29267344";
char *regex = "ts=([0-9]+)";
regex_t comment;
int cnt;
char str[256];
regmatch_t regmatch[1];
regcomp(&comment, regex, REG_EXTENDED | REG_NEWLINE);
int status = regexec(&comment, srcStr, sizeof(regmatch) / sizeof(regmatch_t), regmatch, 0);
LOGD("status=%d",status);
if (status==0){
memset(str, sizeof(str), 0);
int start = regmatch[0].rm_so+3;
int end = regmatch[0].rm_eo;
cnt = end-start;
LOGD("start=%d, end=%d", end, start);
memcpy(str, &srcStr[start], cnt);
str[cnt] = '\0';
LOGD("result=%s\n", str);
}
int result = atoi(str);
regfree(&comment);
progress = result;
return result;
}
//提供获取进度的接口
int get_progress()
{
return progress;
}
在ffmpeg.c中找到main函数,将函数名修改为int ffmpeg_exec(int argc, char **argv)
,注释此函数中的所有exit_program(),并在函数末尾将下面的变量重新初始化:
nb_filtergraphs = 0;
nb_output_files = 0;
nb_output_streams = 0;
nb_input_files = 0;
nb_input_streams = 0;
progress = 0;
最后别忘了在头文件中申明:
int update_progress(char* srcStr);
int get_progress();
int ffmpeg_exec(int argc, char **argv);
完整代码:ffmpeg.c ffmpeg.h
引入Android日志,在ffmpeg.c中增加以下代码
#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__)
在com.github.transcoder.jni包下新建FFmpegCmd.java
引入动态链接库:
static {
System.loadLibrary("avutil");
System.loadLibrary("avcodec");
System.loadLibrary("swresample");
System.loadLibrary("avformat");
System.loadLibrary("swscale");
System.loadLibrary("avfilter");
System.loadLibrary("ffmpeg-cmd");
}
申明以下接口函数:
//执行FFmpeg命令
private static native int run(int cmdLen, String[] cmd);
//获取命令执行进度
public static native int getProgress();
对转码命令进行封装:
/**
* 执行FFMpeg命令, 同步模式
*
* @param cmd
* @return
*/
public static int run(ArrayList 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 targetFPS 视频输出帧率
* @param bitrate 输出码率
* @param duration 视频时长(ms)
* @param listener
*/
public static void transcode(String srcPath, String outPath, int targetFPS, int bitrate, int targetWidth, int targetHeight, long duration,String presets, ProgressListener listener) {
new Thread(() -> {
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, targetFPS, bitrate, targetWidth, targetHeight,presets);
}
public static void transcode(String srcPath, String outPath, int targetFPS, int bitrate, int width, int height,String presets) {
ArrayList cmd = new ArrayList<>();
cmd.add("ffmpeg");
cmd.add("-d");
cmd.add("-y");
cmd.add("-i");
cmd.add(srcPath);
cmd.add("-preset");
cmd.add(presets);
cmd.add("-b:v");
cmd.add(bitrate + "k");
if (width > 0) {
cmd.add("-s");
cmd.add(width + "x" + height);
}
cmd.add("-r");
cmd.add(String.valueOf(targetFPS));
cmd.add(outPath);
run(cmd);
}
接下来在C++中实现接口功能,在\src\main\cpp\ffmpeg中新建ffmpeg-cmd.cpp:
#include
#include
#include "android/log.h"
extern "C"{
#include "ffmpeg.h"
#include "libavcodec/jni.h"
}
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "ffmpeg-cmd", __VA_ARGS__)
extern "C"
JNIEXPORT jint JNICALL
Java_com_github_transcoder_jni_FFmpegCmd_run(JNIEnv *env, jclass type, 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(env->GetObjectArrayElement(cmd, i));
char *string = const_cast(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_github_transcoder_jni_FFmpegCmd_getProgress(JNIEnv *env, jclass type) {
return get_progress();
}
修改app\src\main\cpp\CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
#add libavcodec
add_library(libavcodec
SHARED
IMPORTED
)
SET_TARGET_PROPERTIES(
libavcodec
PROPERTIES IMPORTED_LOCATION
${PROJECT_SOURCE_DIR}/ffmpeg/prebuilt/${ANDROID_ABI}/libavcodec.so
)
#add libavfilter
add_library(libavfilter
SHARED
IMPORTED
)
SET_TARGET_PROPERTIES(
libavfilter
PROPERTIES IMPORTED_LOCATION
${PROJECT_SOURCE_DIR}/ffmpeg/prebuilt/${ANDROID_ABI}/libavfilter.so
)
#add libavformat
add_library(libavformat
SHARED
IMPORTED
)
SET_TARGET_PROPERTIES(
libavformat
PROPERTIES IMPORTED_LOCATION
${PROJECT_SOURCE_DIR}/ffmpeg/prebuilt/${ANDROID_ABI}/libavformat.so
)
#add libavutil
add_library(libavutil
SHARED
IMPORTED
)
SET_TARGET_PROPERTIES(
libavutil
PROPERTIES IMPORTED_LOCATION
${PROJECT_SOURCE_DIR}/ffmpeg/prebuilt/${ANDROID_ABI}/libavutil.so
)
#add libpostproc
add_library(libpostproc
SHARED
IMPORTED
)
SET_TARGET_PROPERTIES(
libpostproc
PROPERTIES IMPORTED_LOCATION
${PROJECT_SOURCE_DIR}/ffmpeg/prebuilt/${ANDROID_ABI}/libpostproc.so
)
#add libswresample
add_library(libswresample
SHARED
IMPORTED
)
SET_TARGET_PROPERTIES(
libswresample
PROPERTIES IMPORTED_LOCATION
${PROJECT_SOURCE_DIR}/ffmpeg/prebuilt/${ANDROID_ABI}/libswresample.so
)
#add libswscale
add_library(libswscale
SHARED
IMPORTED
)
SET_TARGET_PROPERTIES(
libswscale
PROPERTIES IMPORTED_LOCATION
${PROJECT_SOURCE_DIR}/ffmpeg/prebuilt/${ANDROID_ABI}/libswscale.so
)
include_directories(ffmpeg/)
add_library(
ffmpeg-cmd
SHARED
ffmpeg/ffmpeg-cmd.cpp ffmpeg/ffmpeg.c ffmpeg/cmdutils.c ffmpeg/ffmpeg_filter.c ffmpeg/ffmpeg_hw.c ffmpeg/ffmpeg_opt.c
)
find_library( # Sets the name of the path variable.
log-lib
log)
target_link_libraries( # Specifies the target library.
ffmpeg-cmd
libavcodec
libswscale
libswresample
libpostproc
libavutil
libavformat
libavfilter
${log-lib})
修改好以后同步一下Gradle,然后Make Project
编译过程中如果遇到找不到头文件的错误,比如:
fatal error: 'libavcodec/mathops.h' file not found
就到FFmpeg源码libavcodec模块找到该头文件并拷贝至相应目录下,然后重新编译即可。
一切准备就绪,写个简单的界面来测试下转码功能,新建TranscodeActivity.java
选择一个1920x1080分辨率、码率50Mbps、FPS为60、时长57s的视频,编码模式选择superfast可以兼顾编码效率与质量,将其压缩至4Mbps、30FPS,
在小米8上面测试结果为耗时34秒:
General
Format : MPEG-4
File size : 322 MiB
Duration : 57 s 37 ms
Overall bit rate : 47.4 Mb/s
Video
ID : 1
Format : AVC
Format/Info : Advanced Video Codec
Format profile : [email protected]
Format settings : CABAC / 1 Ref Frames
Format settings, CABAC : Yes
Bit rate : 47.2 Mb/s
Width : 1 920 pixels
Height : 1 080 pixels
Original frame rate : 60.000 FPS
Bits/(Pixel*Frame) : 0.399
Stream size : 321 MiB (100%)
General
Format : MPEG-4
File size : 26.8 MiB
Duration : 57 s 100 ms
Overall bit rate : 3 941 kb/s
Video
ID : 1
Format : AVC
Format/Info : Advanced Video Codec
Format profile : High@L4
Format settings : CABAC / 4 Ref Frames
Format settings, CABAC : Yes
Bit rate : 3 801 kb/s
Width : 1 920 pixels
Height : 1 080 pixels
Frame rate : 30.000 FPS
Bits/(Pixel*Frame) : 0.061
Stream size : 25.9 MiB (96%)
如果缩放分辨率会耗时更长,其它参数不变的情况下,将分辨率压缩至1280x720会耗时1分钟。
Demo已上传到Github以供参考:AndroidTranscoder