jni中使用ffmpeg
来源:互联网 发布:java线程池例子 编辑:程序博客网 时间:2024/05/16 06:12
http://www.jianshu.com/p/f047c147cf49
Android多媒体之二:jni调用ffmpeg命令
FFmpeg除了提供了强大的编解码库之外,也提供了一些命令行工具ffmpeg、ffplay、ffprobe、ffserver。如果对lib不太熟悉,而要实现的功能也比较简单,可以直接调用ffmpeg的命令实现。
准备
以ffmpeg2.5为例,准备ffmpeg2.5源码,以及编译好的lib和include。编译方法见:
Android多媒体之一:编译ffmpeg
准备ndk,并配好环境变量;准备Android studio。
ffmpeg简要分析
首先看下ffmpeg各个库的作用,这样简单的函数根据函数名就能猜到作用是什么:
libavcodec encoding/decoding librarylibavfilter graph-based frame editing librarylibavformat I/O and muxing/demuxing librarylibavdevice special devices muxing/demuxing librarylibavutil common utility librarylibswresample audio resampling, format conversion and mixinglibpostproc post processing librarylibswscale color conversion and scaling library
接下来分析ffmpeg.c的main函数源码,因为我们调用命令就是执行main函数:
main函数主要调用了以下函数:
register_exit(ffmpeg_cleanup):注册退出时清理函数;
av_log_set_flags和parse_loglevel:跟log相关的,不管;
avcodec_register_all :注册编码解码器;
avdevice_register_all:注册特殊设备的封装库;
avfilter_register_all:注册帧编辑库;
av_register_all:注册所有封装和分离器;
avformat_network_init:初始化网络组建,ffmpeg是支持拉取远程视频流的;
show_banner:打印输出FFmpeg版本信息(编译时间,编译选项,类库信息等);
term_init:初始化对终端命令的响应;
ffmpeg_parse_options:解析输入的参数;
transcode:转码过程;
exit_progam:退出和清理。
编写代码
编写jni代码
既然执行命令的入口是在main函数,那我们在java代码中定义一个native方法,并在相应的c代码中调用main函数不就可以了吗。
新建一个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 __cplusplusextern "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
编写相应的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;}
其中logjni.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目录下,既然要执行ffmpeg.c中的函数,显然ffmpeg.c和ffmpeg.h要拷贝到jni目录下。
再看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拷贝进去。
ffmpeg.c中还用到了不是lib中的c
cmdutils.c ffmpeg_opt.c ffmpeg_filter.c
还有这些代码中include到的h
cmdutils.h cmdutils_common_opts.h config.h
将这些文件全部拷贝到jni文件夹。
修改main函数
由于在终端中输入命令,ffmpeg可以新建进程、执行命令然后在销毁进程。但在java程序中,我们不想让命令执行完就直接退出。于是main函数中要做些改动:
将所有的异常的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函数的最后加上几行,在进程不销毁的情况下,将一些变量初始化。
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-armeabiLOCAL_SRC_FILES:= prebuilt/armeabi/libavcodec.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE:= avdevice-prebuilt-armeabiLOCAL_SRC_FILES:= prebuilt/armeabi/libavdevice.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE:= avfilter-prebuilt-armeabiLOCAL_SRC_FILES:= prebuilt/armeabi/libavfilter.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE:= avformat-prebuilt-armeabiLOCAL_SRC_FILES:= prebuilt/armeabi/libavformat.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE := avutil-prebuilt-armeabiLOCAL_SRC_FILES := prebuilt/armeabi/libavutil.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE := swresample-prebuilt-armeabiLOCAL_SRC_FILES := prebuilt/armeabi/libswresample.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE := swscale-prebuilt-armeabiLOCAL_SRC_FILES := prebuilt/armeabi/libswscale.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE := libffmpegjniLOCAL_ARM_MODE := armLOCAL_SRC_FILES := FFmpegJni.c \ ffmpeg.c \ cmdutils.c \ ffmpeg_opt.c \ ffmpeg_filter.cLOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog -lzLOCAL_SHARED_LIBRARIES:= avcodec-prebuilt-armeabi \ avdevice-prebuilt-armeabi \ avfilter-prebuilt-armeabi \ avformat-prebuilt-armeabi \ avutil-prebuilt-armeabi \ swresample-prebuilt-armeabi \ swscale-prebuilt-armeabiLOCAL_C_INCLUDES += -L$(SYSROOT)/usr/includeLOCAL_C_INCLUDES += $(LOCAL_PATH)/includeLOCAL_CFLAGS := -DUSE_ARM_CONFIGinclude $(BUILD_SHARED_LIBRARY)
ndk-build
接下来就cd到jni目录,然后ndk-build编译。编译过程中,应该会提示缺少一些头文件如下,从ffmpeg工程中拷贝过来就可以了。
libavutil/libm.hlibavformat/os_support.hlibavformat/ffm.h network.h url.hcompat/va_copy.hlibavresample/avresample.h version.hlibpostproc/postprocess.h version.h
编译结果在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
- jni中使用ffmpeg
- Ubuntu下,在Eclipse中使用JNI调用ffmpeg
- 在Android中通过jni方式使用编译好的FFmpeg库-Android中使用FFmpeg媒体库(二)
- 在Android中通过jni方式使用编译好的FFmpeg库-Android中使用FFmpeg媒体库(二)
- 在Android中通过jni方式使用编译好的FFmpeg库-Android中使用FFmpeg媒体库
- 在Android中通过jni方式使用编译好的FFmpeg库-Android中使用FFmpeg媒体库(二)
- 在Android中通过jni方式使用编译好的FFmpeg库-Android中使用FFmpeg媒体库
- 在Android中通过jni方式使用编译好的FFmpeg库-Android中使用FFmpeg媒体库(二)
- ffmpeg 中hello-jni demo 示例
- android 编译好ffmpeg 3.0+ 后再jni中使用遇到的问题
- 3. Android工程中使用FFmpeg的so库 -- JNI头文件的生成
- 4. Android工程中使用FFmpeg的so库 -- JNI头文件定义方法的实现
- android中使用JNI
- Android中jni使用
- Java中使用JNI
- VS2010中使用ffmpeg
- iOS中使用ffmpeg
- FFmpeg - C++中使用ffmpeg库
- MATLAB对数据的操作
- USACO-Section2.1 Hamming Codes
- Hive基本操作(三)
- dao.duplicatekeyException
- codeforces-305A Strange Addition(思维+模拟)
- jni中使用ffmpeg
- 1、Spring框架-IoC与DI
- TQ E9开发板的二次开发
- poj1637 Sightseeing tour
- 第一次接触oracle
- 开始写博客
- 模板方法模式
- hadoop学习之MapReduce笔记
- 质数无上界的证明