Android studio 引入FFmpeg422+libx264 並支持命令行工具

首先引入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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章