Android集成FFmpeg并实现视频转码

通过编译FFmpeg并集成到Android App中实现视频转码,也可以直接执行FFmpeg命令实现视频拼接、剪切、添加水印、旋转等操作,或者在C++中引入各模块头文件直接调用FFmpeg的API满足更灵活的需求。
需要的可以直接下载头文件和.so文件,.so大概8M:FFmpeg Android动态链接库

1.配置环境

在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

最后的目录结构如下:
Android集成FFmpeg并实现视频转码_第1张图片
配置abiFilters:

ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a'
        }

配置sourceSets

sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/cpp/ffmpeg/prebuilt/']
        }
}

FFmpeg编译可以看这篇文章:FFmpeg一键编译Android armv7-a arm64

2.修改代码

为了获取命令执行进度,需要在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__)

3.编写JNI接口和C++代码

在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();
}

4.编译C\C++代码

修改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模块找到该头文件并拷贝至相应目录下,然后重新编译即可。

5.测试转码

一切准备就绪,写个简单的界面来测试下转码功能,新建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

你可能感兴趣的:(Android)