ffmpeg开发之旅(6):详解ffmpeg命令在Android平台上的使用
2017-06-30 23:06
1166 查看
ffmpeg开发之旅(6):详解ffmpeg命令在Android平台上的使用
(码字不易,转载请声明出处:http://blog.csdn.net/andrexpert/article/details/74015671)
上一篇文章讲解如何在linux系统环境下编译so共享库,并将其移植到Android平台上使用。基于此,本文将着重讲解如果通过移植main函数,使Android平台支持直接使用ffmpeg命令实现对音视频的处理,就像PC端一样直接、方便。
一、移植main函数执行ffmpeg命令
1. 项目结构
2. 移植main函数
第一步:将ffmpeg-3.3.3源码目录下下列文件拷贝到Android工程的jni目录下
cmdutils.c
cmdutils.h
ffmpeg_filter.c
ffmpeg_opt.c
cmdutils_common_opts.h
config.h
ffmpeg.h
ffmpeg_mod.c
另外,将ffmpeg_mod.c为源码目录下的ffmpeg.c文件,这里我们对其进行了重命名,需要修改以下几个地方,否则会报错:
如果希望安卓终端能够像PC端一样执行ffmpeg命令,那么ffmpeg.c中的ffmpegmain函数就是其唯一入口。也就是说,我们在JNI中执行ffmpeg命令时,接受命令的关键函数就是ffmpegmain函数,因此,我们还需要在ffmpeg.h中对ffmpegmain函数进行声明,否则,JNI函数中调用时会提示undefine错误。另外,这里我还想知道执行ffmpeg命令时的动态详情,就将log日志一并定义在ffmpeh.h头文件中。
compat/va_copy.h
libavresample/avresample.h
libavresample/version.h
libavformat/ffm.h
libavformat/network.h
libavformat/os_support.h
libavformat/url.h
libavutil/libm.h
libavutil/internal.h
libavutil/timer.h (注释:'arm/timer.h)
注:为什么要拷贝这些头文件?其实我也不知道有什么用,你自己ndk-build一下就知道了....
二、在Android平台上的使用ffmpeg命令
1. VideoFixUtils.class,添加执行ffmpeg命令nativie方法
excuteFFMPEGCmd(int argc , String[] cmdLines)之所以传递argc、cmdLines两个参数,是因为在执行ffmpeg.c中ffmpegmain(int argc, char **argv)函数时需要这两个参数,前者表示命令条数,后者表示具体命令的字符串数组。
2. FFMPEG4Android.c,Java本地方法C实现
Android.mk是用来描述要编译某个具体的模块,所需要的一些资源,包括要编译的源码、要链接的库等等。由于本项目jni目录下添加了cmdutils.c ffmpeg_filter.c ffmpeg_opt.c ffmpeg_mod.c源文件,因此,我们还需要在该文件的LOCAL_SRC_FILES变量中进行添加,否则会编译不通过。
4. Application.mk
相比上篇文章,Application.mk中多了个APP_LDFLAGS变量,之所以要该变量是因为我们在ndk-build编译时,ffmpeg_mod.c报"ffmpeg_mod.c:461: error: undefined reference to '__atomic_load_4'"错误。atomic_load是一种原子操作,ffmpeg_mod.c无法找到atomic相关函数的定义,这里就使用APP_LDFLAGS变量当链接应用是屏蔽它。
5.MainActivity.class,调用native方法,执行ffmpeg命令
关于ffmpeg命令的使用,这里先不做详解,后期补上,其使用规则:
ffmpeg [[options][`-i' input_file]]... {[options] output_file}.
6. ffmpeg命令执行日志
7. 结果演示
(码字不易,转载请声明出处:http://blog.csdn.net/andrexpert/article/details/74015671)
上一篇文章讲解如何在linux系统环境下编译so共享库,并将其移植到Android平台上使用。基于此,本文将着重讲解如果通过移植main函数,使Android平台支持直接使用ffmpeg命令实现对音视频的处理,就像PC端一样直接、方便。
一、移植main函数执行ffmpeg命令
1. 项目结构
2. 移植main函数
第一步:将ffmpeg-3.3.3源码目录下下列文件拷贝到Android工程的jni目录下
cmdutils.c
cmdutils.h
ffmpeg_filter.c
ffmpeg_opt.c
cmdutils_common_opts.h
config.h
ffmpeg.h
ffmpeg_mod.c
另外,将ffmpeg_mod.c为源码目录下的ffmpeg.c文件,这里我们对其进行了重命名,需要修改以下几个地方,否则会报错:
(1) 注释65行:#include "libavcodec/mathops.h" (2) 注释1073行: // nb0_frames = nb_frames = mid_pred(ost->last_nb0_frames[0], // ost->last_nb0_frames[1], // ost->last_nb0_frames[2]); (3) 重命名main(int argc,char **argv)为ffmpegmain(int argc,char **argv),并根据以下代码修改 int ffmpegmain(int argc, char **argv){ ...... // if (nb_input_files == 0) { // av_log(NULL, AV_LOG_FATAL, "At least one input file must be specified\n"); // exit_program(1); // } ...... // if (do_benchmark) { // av_log(NULL, AV_LOG_INFO, "bench: utime=%0.3fs\n", ti / 1000000.0); // } // av_log(NULL, AV_LOG_DEBUG, "%"PRIu64" frames successfully decoded, %"PRIu64" // decode_error_stat[0], decode_error_stat[1]); // if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1]) // exit_program(69); // exit_program(received_nb_signals ? 255 : main_return_code); ffmpeg_cleanup(0); return main_return_code; } (4) 1711行,添加打印日志 LOG_I("frame_number---->%d\n",frame_number);第二步:修改ffmpeg.h,声明ffmpegmain(int argc,char **argv)
如果希望安卓终端能够像PC端一样执行ffmpeg命令,那么ffmpeg.c中的ffmpegmain函数就是其唯一入口。也就是说,我们在JNI中执行ffmpeg命令时,接受命令的关键函数就是ffmpegmain函数,因此,我们还需要在ffmpeg.h中对ffmpegmain函数进行声明,否则,JNI函数中调用时会提示undefine错误。另外,这里我还想知道执行ffmpeg命令时的动态详情,就将log日志一并定义在ffmpeh.h头文件中。
#include <android/log.h> #define LOG_TAG "libffmpeg" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,"%s",__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,"%s",__VA_ARGS__) #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,"%s",__VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,"%s",__VA_ARGS__) #define LOG_I(format,...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,format,__VA_ARGS__) #define LOG_D(format,...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,format,__VA_ARGS__) #define LOG_W(format,...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,format,__VA_ARGS__) #define LOG_E(format,...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,format, __VA_ARGS__) // 末尾声明(#endif之前) int ffmpegmain(int argc, char **argv);第三步:从ffmpeg-3.3.2源码中拷贝头文件到jni/include目录对应文件夹下
compat/va_copy.h
libavresample/avresample.h
libavresample/version.h
libavformat/ffm.h
libavformat/network.h
libavformat/os_support.h
libavformat/url.h
libavutil/libm.h
libavutil/internal.h
libavutil/timer.h (注释:'arm/timer.h)
注:为什么要拷贝这些头文件?其实我也不知道有什么用,你自己ndk-build一下就知道了....
二、在Android平台上的使用ffmpeg命令
1. VideoFixUtils.class,添加执行ffmpeg命令nativie方法
/** 处理视频native方法工具类 * * @author Created by jianddongguo on 2017年6月26日下午11:14:27 * @blogs http://blog.csdn.net/andrexpert */ public class VideoFixUtils { /** 获得指定视频的角度 * @param videoPath 视频路径 * @return 拍摄角度值 */ public native static int getVideoAngle(String videoPath); /** 执行ffmpeg命令 * @param argc 命令个数 * @param cmdLines 命令 * @return */ public native static int excuteFFMPEGCmd(int argc , String[] cmdLines); static { // 加载自定义动态库 System.loadLibrary("FFMPEG4Android"); // 加载ffmpeg相关动态库 System.loadLibrary("avcodec-57"); System.loadLibrary("avdevice-57"); System.loadLibrary("avfilter-6"); System.loadLibrary("avformat-57"); System.loadLibrary("avutil-55"); System.loadLibrary("swscale-4"); System.loadLibrary("swresample-2"); System.loadLibrary("postproc-54"); } }讲解一下:
excuteFFMPEGCmd(int argc , String[] cmdLines)之所以传递argc、cmdLines两个参数,是因为在执行ffmpeg.c中ffmpegmain(int argc, char **argv)函数时需要这两个参数,前者表示命令条数,后者表示具体命令的字符串数组。
2. FFMPEG4Android.c,Java本地方法C实现
/** Java层native方法对应的原型函数实现 * * @author Created by jianddongguo on 2017年6月26日下午11:14:27 * @blogs http://blog.csdn.net/andrexpert */ #include <jni.h> #include <stdlib.h> #include <string.h> #include "com_jiangdg_ffmepg4android_VideoFixUtils.h" #include "ffmpeg.h" JNIEXPORT jint JNICALL Java_com_jiangdg_ffmepg4android_VideoFixUtils_getVideoAngle (JNIEnv *env, jclass jcls, jstring j_videoPath){ const char *c_videoPath = (*env)->GetStringUTFChars(env,j_videoPath,NULL); //1. 注册所有组件 av_register_all(); //2. 打开视频、获取视频信息, // 其中,fmtCtx为封装格式上下文 AVFormatContext *fmtCtx = avformat_alloc_context(); avformat_open_input(&fmtCtx,c_videoPath,NULL,NULL); //3. 获取视频流的索引位置 int i; int v_stream_idx = -1; for(i=0 ; i<fmtCtx->nb_streams ; i++){ if(fmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){ v_stream_idx = i; break; } } // 4. 获取旋转角度,元数据 AVDictionaryEntry *tag = NULL; tag = av_dict_get(fmtCtx->streams[v_stream_idx]->metadata,"rotate",tag,NULL); int angle = -1; if(tag != NULL){ // 将char *强制转换为into类型 angle = atoi(tag->value); } // 5.释放封装格式上下文 avformat_free_context(fmtCtx); (*env)->ReleaseStringUTFChars(env,j_videoPath,c_videoPath); LOGI("get video angle"); return angle; } JNIEXPORT jint JNICALL Java_com_jiangdg_ffmepg4android_VideoFixUtils_excuteFFMPEGCmd (JNIEnv *env, jclass jcls, jint cmdNum, jobjectArray jcmdLines){ LOGI("start......"); int argc = cmdNum; // 开辟一个C字符串数组 char** argv = (char **)malloc(sizeof(char *) * argc); // 读取jcmdLines,为每个字符串元素赋值 int i; for(i=0 ; i<argc ;i++){ jstring j_cmd = (*env)->GetObjectArrayElement(env,jcmdLines,i); const char* c_cmd = (*env)->GetStringUTFChars(env,j_cmd,NULL); argv[i] = (char *)malloc(sizeof(char) * 1024); strcpy(argv[i],c_cmd); (*env)->ReleaseStringUTFChars(env,j_cmd,c_cmd); LOG_D("argc=%d,argv=%s",argc,c_cmd); } // 执行ffmpeg命令 LOGI("start excute ffmpeg commands"); ffmpegmain(argc, argv); // 释放内存,防止溢出 for(i=0 ; i<argc ;i++){ free(argv[i]); } free(argv); LOGI("end....."); return 0; }3. Android.mk
LOCAL_PATH := $(call my-dir) #ffmpeg prebuilt lib include $(CLEAR_VARS) LOCAL_MODULE := avcodec_prebuilt LOCAL_SRC_FILES := libavcodec-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avdevice_prebuilt LOCAL_SRC_FILES := libavdevice-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avfilter_prebuilt LOCAL_SRC_FILES := libavfilter-6.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avformat_prebuilt LOCAL_SRC_FILES := libavformat-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avutil_prebuilt LOCAL_SRC_FILES := libavutil-55.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := swresample_prebuilt LOCAL_SRC_FILES := libswresample-2.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := swscale_prebuilt LOCAL_SRC_FILES := libswscale-4.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := postproc_prebuilt LOCAL_SRC_FILES := libpostproc-54.so include $(PREBUILT_SHARED_LIBRARY) #myapp lib include $(CLEAR_VARS) LOCAL_MODULE := FFMPEG4Android LOCAL_SRC_FILES := FFMPEG4Android.c cmdutils.c ffmpeg_filter.c ffmpeg_opt.c ffmpeg_mod.c LOCAL_C_INCLUDES +=$(LOCAL_PATH)/include LOCAL_LDLIBS := -llog -lz LOCAL_SHARED_LIBRARIES := avcodec_prebuilt avdevice_prebuilt avfilter_prebuilt avformat_prebuilt avutil_prebuilt swresample_prebuilt swscale_prebuilt postproc_prebuilt include $(BUILD_SHARED_LIBRARY)讲解一下:
Android.mk是用来描述要编译某个具体的模块,所需要的一些资源,包括要编译的源码、要链接的库等等。由于本项目jni目录下添加了cmdutils.c ffmpeg_filter.c ffmpeg_opt.c ffmpeg_mod.c源文件,因此,我们还需要在该文件的LOCAL_SRC_FILES变量中进行添加,否则会编译不通过。
4. Application.mk
APP_LDFLAGS := -latomic #指定so支持的平台 APP_ABI := armeabi讲解一下:
相比上篇文章,Application.mk中多了个APP_LDFLAGS变量,之所以要该变量是因为我们在ndk-build编译时,ffmpeg_mod.c报"ffmpeg_mod.c:461: error: undefined reference to '__atomic_load_4'"错误。atomic_load是一种原子操作,ffmpeg_mod.c无法找到atomic相关函数的定义,这里就使用APP_LDFLAGS变量当链接应用是屏蔽它。
5.MainActivity.class,调用native方法,执行ffmpeg命令
/** 主界面 * * @author Created by jianddongguo on 2017年6月26日下午11:14:27 * @blogs http://blog.csdn.net/andrexpert */ public class MainActivity extends Activity { private String rootPath; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); rootPath = Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator ; } public void onAddWaterClick(View v) { new Thread(new Runnable(){ @Override public void run() { String input = rootPath + "20170627_145524.mp4"; String output = rootPath + "20170627_145524_output_addwater.mp4"; File inFile = new File(input); if (!inFile.exists()) { return; } File outFile = new File(output); if(outFile.exists()){ outFile.delete(); } String watermark = rootPath + "luchibao.jpg"; String addWaterCmd = String.format("ffmpeg -i %s -i %s -filter_complex overlay=150:50 %s", input,watermark,output); String[] argv = addWaterCmd.split(" "); VideoFixUtils.excuteFFMPEGCmd(argv.length, argv); } }).start(); } public void onRotateClick(View v){ new Thread(new Runnable(){ @Override public void run() { String input = rootPath + "20170627_145524.mp4"; String output = rootPath + "20170627_145524_output_rotate.mp4"; File inFile = new File(input); if (!inFile.exists()) { return; } File outFile = new File(output); if(outFile.exists()){ outFile.delete(); } // 得到视频旋转角度 int rotationOfVideo = VideoFixUtils.getVideoAngle(inFile.getAbsolutePath()); // 计算调整的弧度 double rotate = rotationOfVideo * Math.PI / 180; String rotateCmd = String.format("ffmpeg -i %s -filter_complex rotate=%f %s", input,rotate,output); String[] argv = rotateCmd.split(" "); VideoFixUtils.excuteFFMPEGCmd(argv.length, argv); } }).start(); } }讲解一下:
关于ffmpeg命令的使用,这里先不做详解,后期补上,其使用规则:
ffmpeg [[options][`-i' input_file]]... {[options] output_file}.
6. ffmpeg命令执行日志
7. 结果演示
相关文章推荐
- ffmpeg开发之旅(6):详解ffmpeg命令在Android平台上的使用
- ffmpeg开发之旅(5):详解ffmpeg编译与在Android平台上的移植
- ffmpeg开发之旅(5):详解ffmpeg编译与在Android平台上的移植
- Ffmpeg快速命令使用 Ffmpeg选项详解 Ffmepg格式详解 常见视频文件格式详解
- Android 开发之使用Eclipse Debug调试详解
- Android多媒体开发(3)————使用Android NKD编译havlenapetr-FFMpeg-7c27aa2
- Android 开发之使用Eclipse Debug调试详解
- 实例详解快捷搭建Android手机开发平台
- Android 开发之使用Eclipse Debug调试详解
- Android开发平台振动器系统详解
- Android开发_WebView组件使用详解_LoadUrl直接显示网页内容
- Android多媒体开发(2)————使用Android NKD编译原版FFmpeg
- android ktv 开发过程4-android 使用ffmpeg
- 使用Google Map Api在Android平台上开发地图应用3
- Android 开发之使用Eclipse Debug调试详解
- Visual C# 2008 实用开发详解--14.3.1 使用SqlCommand提交增、删、改命令
- 使用Google Map Api在Android平台上开发地图应用4
- Android 软件开发之如何使用Eclipse Debug调试程序详解(十二)
- 在Android使用新浪微博的开发平台API
- ffmpeg命令使用详解