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

12 篇文章 1 订阅

首先引入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);
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值