ffmpeg开发之旅(6):详解ffmpeg命令在Android平台上的使用

来源:互联网 发布:常见web前后端数据交互 编辑:程序博客网 时间:2024/06/05 12:41

  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文件,这里我们对其进行了重命名,需要修改以下几个地方,否则会报错:
(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 libinclude $(CLEAR_VARS)LOCAL_MODULE    := avcodec_prebuiltLOCAL_SRC_FILES := libavcodec-57.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE    := avdevice_prebuiltLOCAL_SRC_FILES := libavdevice-57.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE    := avfilter_prebuiltLOCAL_SRC_FILES := libavfilter-6.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE    := avformat_prebuiltLOCAL_SRC_FILES := libavformat-57.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE    := avutil_prebuiltLOCAL_SRC_FILES := libavutil-55.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE    := swresample_prebuiltLOCAL_SRC_FILES := libswresample-2.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE    := swscale_prebuiltLOCAL_SRC_FILES := libswscale-4.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE    := postproc_prebuiltLOCAL_SRC_FILES := libpostproc-54.soinclude $(PREBUILT_SHARED_LIBRARY)#myapp libinclude $(CLEAR_VARS)LOCAL_MODULE    := FFMPEG4AndroidLOCAL_SRC_FILES := FFMPEG4Android.c cmdutils.c ffmpeg_filter.c ffmpeg_opt.c ffmpeg_mod.cLOCAL_C_INCLUDES +=$(LOCAL_PATH)/includeLOCAL_LDLIBS := -llog -lzLOCAL_SHARED_LIBRARIES := avcodec_prebuilt avdevice_prebuilt avfilter_prebuilt avformat_prebuilt avutil_prebuilt swresample_prebuilt swscale_prebuilt postproc_prebuiltinclude $(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;@Overrideprotected 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(){@Overridepublic 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(){@Overridepublic 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. 结果演示



原创粉丝点击