Android多媒體之二:jni調(diào)用ffmpeg命令

FFmpeg除了提供了強大的編解碼庫之外,也提供了一些命令行工具ffmpeg、ffplay、ffprobe、ffserver。如果對lib不太熟悉,而要實現(xiàn)的功能也比較簡單,可以直接調(diào)用ffmpeg的命令實現(xiàn)。

準(zhǔn)備

以ffmpeg2.5為例,準(zhǔn)備ffmpeg2.5源碼,以及編譯好的lib和include。編譯方法見:

Android多媒體之一:編譯ffmpeg

準(zhǔn)備ndk,并配好環(huán)境變量;準(zhǔn)備Android studio。

ffmpeg簡要分析

首先看下ffmpeg各個庫的作用,這樣簡單的函數(shù)根據(jù)函數(shù)名就能猜到作用是什么:

libavcodec encoding/decoding library
libavfilter graph-based frame editing library
libavformat I/O and muxing/demuxing library
libavdevice special devices muxing/demuxing library
libavutil common utility library
libswresample audio resampling, format conversion and mixing
libpostproc post processing library
libswscale color conversion and scaling library

接下來分析ffmpeg.c的main函數(shù)源碼,因為我們調(diào)用命令就是執(zhí)行main函數(shù):


main函數(shù)主要調(diào)用了以下函數(shù):

register_exit(ffmpeg_cleanup):注冊退出時清理函數(shù);

av_log_set_flags和parse_loglevel:跟log相關(guān)的,不管;

avcodec_register_all :注冊編碼解碼器;

avdevice_register_all:注冊特殊設(shè)備的封裝庫;

avfilter_register_all:注冊幀編輯庫;

av_register_all:注冊所有封裝和分離器;

avformat_network_init:初始化網(wǎng)絡(luò)組建,ffmpeg是支持拉取遠(yuǎn)程視頻流的;

show_banner:打印輸出FFmpeg版本信息(編譯時間,編譯選項,類庫信息等);

term_init:初始化對終端命令的響應(yīng);

ffmpeg_parse_options:解析輸入的參數(shù);

transcode:轉(zhuǎn)碼過程;

exit_progam:退出和清理。

編寫代碼

編寫jni代碼

既然執(zhí)行命令的入口是在main函數(shù),那我們在java代碼中定義一個native方法,并在相應(yīng)的c代碼中調(diào)用main函數(shù)不就可以了嗎。
新建一個android工程,我這里用包名org.mqstack.ffmpegjni,新建FFmpegJni.java,寫入:

public class FFmpegJni {

    static {
        System.loadLibrary("avutil");
        System.loadLibrary("swresample");
        System.loadLibrary("avcodec");
        System.loadLibrary("avformat");
        System.loadLibrary("swscale");
        System.loadLibrary("avfilter");
        System.loadLibrary("avdevice");
        System.loadLibrary("ffmpegjni");
    }
    
    public int ffmpegRunCommand(String command) {
        if (command.isEmpty()) {
            return 1;
        }
        String[] args = command.split(" ");
        for (int i = 0; i < args.length; i++) {
            Log.d("ffmpeg-jni", args[i]);
        }
        return run(args.length, args);
    }
    
    public native int run(int argc, String[] args);
}

使用javah生成頭文件:

javah -classpath path_to_FFmpegJni -jni class_name

生成的代碼類似如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_mqstack_ffmpegjni_FFmpegJni */

#ifndef _Included_org_mqstack_ffmpegjni_FFmpegJni
#define _Included_org_mqstack_ffmpegjni_FFmpegJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     org_mqstack_ffmpegjni_FFmpegJni
 * Method:    run
 * Signature: (I[Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_org_mqstack_ffmpegjni_FFmpegJni_run
        (JNIEnv *, jobject, jint, jobjectArray);

#ifdef __cplusplus
}
#endif
#endif

編寫相應(yīng)的c文件FFmpegJni.c,代碼很好理解:
#include "logjni.h"
#include "FFmpegJni.h"

#include <stdlib.h>
#include <stdbool.h>

int main(int argc, char **argv);

JNIEXPORT jint JNICALL Java_org_mqstack_ffmpegjni_FFmpegJni_run(JNIEnv *env, jobject obj, jint argc, jobjectArray args) {
    int i = 0;
    char **argv = NULL;
    jstring *strr = NULL;

    if (args != NULL) {
        argv = (char **) malloc(sizeof(char *) * argc);
        strr = (jstring *) malloc(sizeof(jstring) * argc);

        for (i = 0; i < argc; ++i) {
            strr[i] = (jstring)(*env)->GetObjectArrayElement(env, args, i);
            argv[i] = (char *)(*env)->GetStringUTFChars(env, strr[i], 0);
            LOGD("args: %s", argv[i]);
        }
    }

    LOGD("Run ffmpeg");
    int result = main(argc, argv);
    LOGD("ffmpeg result %d", result);

    for (i = 0; i < argc; ++i) {
        (*env)->ReleaseStringUTFChars(env, strr[i], argv[i]);
    }
    free(argv);
    free(strr);

    return result;
}

其中l(wèi)ogjni.h是我寫的一個android打印方法的封裝:
#ifndef LOGJAM_H
#define LOGJAM_H

#include <android/log.h>

#define LOGTAG "FFmpegJni"

#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOGTAG, __VA_ARGS__) 
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , LOGTAG, __VA_ARGS__) 
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO   , LOGTAG, __VA_ARGS__) 
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN   , LOGTAG, __VA_ARGS__) 
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOGTAG, __VA_ARGS__) 

#endif

將上述文件放到工程的jni目錄下,既然要執(zhí)行ffmpeg.c中的函數(shù),顯然ffmpeg.c和ffmpeg.h要拷貝到j(luò)ni目錄下。
再看ffmpeg.c代碼,里面include了一堆lib,
#include "libavformat/avformat.h"
#include "libavdevice/avdevice.h"
#include "libswresample/swresample.h"
#include "libavutil/opt.h"
#include "libavutil/channel_layout.h"
#include "libavutil/parseutils.h"
#include "libavutil/samplefmt.h"
#include "libavutil/fifo.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/dict.h"
#include "libavutil/mathematics.h"
#include "libavutil/pixdesc.h"
#include "libavutil/avstring.h"
#include "libavutil/libm.h"
#include "libavutil/imgutils.h"
#include "libavutil/timestamp.h"
#include "libavutil/bprint.h"
#include "libavutil/time.h"
#include "libavutil/threadmessage.h"
#include "libavformat/os_support.h"

那顯然要將之前編譯成的include文件夾放在jni目錄下,同時在jni目錄下新建prebuild/armeabi文件夾,將之前編譯的so拷貝進(jìn)去。

ffmpeg.c中還用到了不是lib中的c

cmdutils.c ffmpeg_opt.c ffmpeg_filter.c 

還有這些代碼中include到的h

cmdutils.h cmdutils_common_opts.h config.h

將這些文件全部拷貝到j(luò)ni文件夾。

修改main函數(shù)

由于在終端中輸入命令,ffmpeg可以新建進(jìn)程、執(zhí)行命令然后在銷毀進(jìn)程。但在java程序中,我們不想讓命令執(zhí)行完就直接退出。于是main函數(shù)中要做些改動:

將所有的異常的exit_progam,都改為return;

將打印lig的方法都改為android的打印;

將正常的exit_program改為ffmpeg_cleanup(0)。

/* parse options and open all input/output files */
ret = ffmpeg_parse_options(argc, argv);
    if (ret < 0) {
        LOGD("ffmpeg_parse_options err");
        return 1;
}

if (nb_output_files <= 0 && nb_input_files == 0) {
        show_usage();
LOGD("Use -h to get full help or, even better, run 'man %s'\n", program_name);
        return 1;
}

/* file converter / grab */
if (nb_output_files <= 0) {
        LOGD("At least one output file must be specified\n");
        return 1;
}

//     if (nb_input_files == 0) {
//         av_log(NULL, AV_LOG_FATAL, "At least one input file must be specified\n");
//         exit_program(1);
//     }

current_time = ti = getutime();
    if (transcode() < 0) {
        LOGD("failed~~");
        return 1;
}
    ti = getutime() - ti;
    if (do_benchmark) {
        LOGD("bench: utime=%0.3fs\n", ti / 1000000.0);
}
    LOGD("%"PRIu64" frames successfully decoded, %"PRIu64" decoding errors\n",
decode_error_stat[0], decode_error_stat[1]);
    if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])
        LOGD("log error");

ffmpeg_cleanup(0);
    return main_return_code;

還要在ffmpeg_cleanup函數(shù)的最后加上幾行,在進(jìn)程不銷毀的情況下,將一些變量初始化。

nb_filtergraphs = 0;
nb_output_files = 0;
nb_output_streams = 0;
nb_input_files = 0;
nb_input_streams = 0;

編寫mk文件

不多說了,參考官方說明

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE:= avcodec-prebuilt-armeabi
LOCAL_SRC_FILES:= prebuilt/armeabi/libavcodec.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= avdevice-prebuilt-armeabi
LOCAL_SRC_FILES:= prebuilt/armeabi/libavdevice.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= avfilter-prebuilt-armeabi
LOCAL_SRC_FILES:= prebuilt/armeabi/libavfilter.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= avformat-prebuilt-armeabi
LOCAL_SRC_FILES:= prebuilt/armeabi/libavformat.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE :=  avutil-prebuilt-armeabi
LOCAL_SRC_FILES := prebuilt/armeabi/libavutil.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swresample-prebuilt-armeabi
LOCAL_SRC_FILES := prebuilt/armeabi/libswresample.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swscale-prebuilt-armeabi
LOCAL_SRC_FILES := prebuilt/armeabi/libswscale.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := libffmpegjni

LOCAL_ARM_MODE := arm

LOCAL_SRC_FILES := FFmpegJni.c \
                   ffmpeg.c \
                   cmdutils.c \
                   ffmpeg_opt.c \
                   ffmpeg_filter.c

LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog -lz

LOCAL_SHARED_LIBRARIES:= avcodec-prebuilt-armeabi \
                         avdevice-prebuilt-armeabi \
                         avfilter-prebuilt-armeabi \
                         avformat-prebuilt-armeabi \
                         avutil-prebuilt-armeabi \
                         swresample-prebuilt-armeabi \
                         swscale-prebuilt-armeabi

LOCAL_C_INCLUDES += -L$(SYSROOT)/usr/include
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include

LOCAL_CFLAGS := -DUSE_ARM_CONFIG

include $(BUILD_SHARED_LIBRARY)

ndk-build

接下來就cd到j(luò)ni目錄,然后ndk-build編譯。編譯過程中,應(yīng)該會提示缺少一些頭文件如下,從ffmpeg工程中拷貝過來就可以了。

libavutil/libm.h
libavformat/os_support.h
libavformat/ffm.h network.h url.h
compat/va_copy.h
libavresample/avresample.h version.h
libpostproc/postprocess.h version.h

編譯結(jié)果在libs/armeabi目錄下,如下所示

將這些so拷貝到android工程的jniLibs或其他依賴的目錄中,跑一條命令試試看吧。

本文中代碼可在我的github中看到。

https://github.com/mqstack/FFmpegJni

更多

Android多媒體之一:編譯ffmpeg
Android多媒體之三:編譯并使用x264庫

參考

http://blog.csdn.net/leixiaohua1020/article/details/39760711

https://github.com/dxjia/ffmpeg-jni-sample

https://github.com/andynicholson/android-ffmpeg-x264

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容