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

来源:互联网 发布:准确的平特一肖算法 编辑:程序博客网 时间:2024/06/06 04:01

  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文件,这里我们对其进行了重命名,需要修改以下几个地方,否则会报错:
[cpp] view plain copy
  1. (1) 注释65行:#include "libavcodec/mathops.h"  
  2. (2) 注释1073行:  
  3. //        nb0_frames = nb_frames = mid_pred(ost->last_nb0_frames[0],  
  4. //                                          ost->last_nb0_frames[1],  
  5. //                                          ost->last_nb0_frames[2]);  
  6. (3) 重命名main(int argc,char **argv)为ffmpegmain(int argc,char **argv),并根据以下代码修改  
  7. int ffmpegmain(int argc, char **argv){    
  8. ......  
  9. //     if (nb_input_files == 0) {  
  10. //         av_log(NULL, AV_LOG_FATAL, "At least one input file must be specified\n");  
  11. //         exit_program(1);  
  12. //     }  
  13.           ......  
  14.       //    if (do_benchmark) {  
  15.       //        av_log(NULL, AV_LOG_INFO, "bench: utime=%0.3fs\n", ti / 1000000.0);  
  16.       //    }  
  17.       //    av_log(NULL, AV_LOG_DEBUG, "%"PRIu64" frames successfully decoded, %"PRIu64"              
  18.       //           decode_error_stat[0], decode_error_stat[1]);  
  19.       //    if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])  
  20.       //        exit_program(69);  
  21.        //   exit_program(received_nb_signals ? 255 : main_return_code);  
  22.            ffmpeg_cleanup(0);  
  23.            return main_return_code;  
  24.        }  
  25. (4)  1711行,添加打印日志            
  26. 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头文件中。
[cpp] view plain copy
  1. #include <android/log.h>  
  2. #define LOG_TAG "libffmpeg"  
  3. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,"%s",__VA_ARGS__)  
  4. #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,"%s",__VA_ARGS__)  
  5. #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,"%s",__VA_ARGS__)  
  6. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,"%s",__VA_ARGS__)  
  7. #define LOG_I(format,...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,format,__VA_ARGS__)  
  8. #define LOG_D(format,...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,format,__VA_ARGS__)  
  9. #define LOG_W(format,...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,format,__VA_ARGS__)  
  10. #define LOG_E(format,...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,format, __VA_ARGS__)  
  11. // 末尾声明(#endif之前)  
  12. 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方法

[java] view plain copy
  1. /** 处理视频native方法工具类 
  2.  *  
  3.  * @author Created by jianddongguo on 2017年6月26日下午11:14:27 
  4.  * @blogs http://blog.csdn.net/andrexpert 
  5.  */  
  6. public class VideoFixUtils {  
  7.       
  8.     /** 获得指定视频的角度 
  9.      * @param videoPath 视频路径 
  10.      * @return 拍摄角度值 
  11.      */  
  12.     public native static int getVideoAngle(String videoPath);  
  13.       
  14.     /** 执行ffmpeg命令 
  15.      * @param argc 命令个数 
  16.      * @param cmdLines 命令 
  17.      * @return 
  18.      */  
  19.     public native static int excuteFFMPEGCmd(int argc , String[] cmdLines);  
  20.       
  21.     static {  
  22.         // 加载自定义动态库  
  23.         System.loadLibrary("FFMPEG4Android");  
  24.         // 加载ffmpeg相关动态库  
  25.         System.loadLibrary("avcodec-57");  
  26.         System.loadLibrary("avdevice-57");  
  27.         System.loadLibrary("avfilter-6");  
  28.         System.loadLibrary("avformat-57");  
  29.         System.loadLibrary("avutil-55");  
  30.         System.loadLibrary("swscale-4");  
  31.         System.loadLibrary("swresample-2");  
  32.         System.loadLibrary("postproc-54");  
  33.     }     
  34. }  
讲解一下:
     excuteFFMPEGCmd(int argc , String[] cmdLines)之所以传递argc、cmdLines两个参数,是因为在执行ffmpeg.c中ffmpegmain(int argc, char **argv)函数时需要这两个参数,前者表示命令条数,后者表示具体命令的字符串数组。
2.  FFMPEG4Android.c,Java本地方法C实现
[java] view plain copy
  1. /** Java层native方法对应的原型函数实现 
  2.  * 
  3.  * @author Created by jianddongguo on 2017年6月26日下午11:14:27 
  4.  * @blogs http://blog.csdn.net/andrexpert 
  5.  */  
  6. #include <jni.h>  
  7. #include <stdlib.h>  
  8. #include <string.h>  
  9. #include "com_jiangdg_ffmepg4android_VideoFixUtils.h"  
  10. #include "ffmpeg.h"  
  11.   
  12.   
  13. JNIEXPORT jint JNICALL Java_com_jiangdg_ffmepg4android_VideoFixUtils_getVideoAngle  
  14.   (JNIEnv *env, jclass jcls, jstring j_videoPath){  
  15.     const char *c_videoPath = (*env)->GetStringUTFChars(env,j_videoPath,NULL);  
  16.     //1. 注册所有组件  
  17.     av_register_all();  
  18.     //2. 打开视频、获取视频信息,  
  19.     //  其中,fmtCtx为封装格式上下文  
  20.     AVFormatContext *fmtCtx = avformat_alloc_context();  
  21.     avformat_open_input(&fmtCtx,c_videoPath,NULL,NULL);  
  22.     //3. 获取视频流的索引位置  
  23.     int i;  
  24.     int v_stream_idx = -1;  
  25.     for(i=0 ; i<fmtCtx->nb_streams ; i++){  
  26.         if(fmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){  
  27.             v_stream_idx = i;  
  28.             break;  
  29.         }  
  30.     }  
  31.     // 4. 获取旋转角度,元数据  
  32.     AVDictionaryEntry *tag = NULL;  
  33.     tag = av_dict_get(fmtCtx->streams[v_stream_idx]->metadata,"rotate",tag,NULL);  
  34.     int angle = -1;  
  35.     if(tag != NULL){  
  36.         // 将char *强制转换为into类型  
  37.         angle = atoi(tag->value);  
  38.     }  
  39.     // 5.释放封装格式上下文  
  40.     avformat_free_context(fmtCtx);  
  41.     (*env)->ReleaseStringUTFChars(env,j_videoPath,c_videoPath);  
  42.     LOGI("get video angle");  
  43.     return angle;  
  44. }  
  45.   
  46.   
  47. JNIEXPORT jint JNICALL Java_com_jiangdg_ffmepg4android_VideoFixUtils_excuteFFMPEGCmd  
  48.   (JNIEnv *env, jclass jcls, jint cmdNum, jobjectArray jcmdLines){  
  49.     LOGI("start......");  
  50.     int argc = cmdNum;  
  51.     // 开辟一个C字符串数组  
  52.     char** argv = (char **)malloc(sizeof(char *) * argc);  
  53.     // 读取jcmdLines,为每个字符串元素赋值  
  54.     int i;  
  55.     for(i=0 ; i<argc ;i++){  
  56.         jstring j_cmd = (*env)->GetObjectArrayElement(env,jcmdLines,i);  
  57.         const char* c_cmd = (*env)->GetStringUTFChars(env,j_cmd,NULL);  
  58.         argv[i] = (char *)malloc(sizeof(char) * 1024);  
  59.         strcpy(argv[i],c_cmd);  
  60.   (*env)->ReleaseStringUTFChars(env,j_cmd,c_cmd);  
  61.         LOG_D("argc=%d,argv=%s",argc,c_cmd);  
  62.     }  
  63.     // 执行ffmpeg命令  
  64.     LOGI("start excute ffmpeg commands");  
  65.     ffmpegmain(argc, argv);  
  66.     // 释放内存,防止溢出  
  67.     for(i=0 ; i<argc ;i++){  
  68.         free(argv[i]);  
  69.     }  
  70.     free(argv);  
  71.     LOGI("end.....");  
  72.     return 0;  
  73. }  
3. Android.mk
[java] view plain copy
  1. LOCAL_PATH := $(call my-dir)  
  2. #ffmpeg prebuilt lib  
  3. include $(CLEAR_VARS)  
  4. LOCAL_MODULE    := avcodec_prebuilt  
  5. LOCAL_SRC_FILES := libavcodec-57.so  
  6. include $(PREBUILT_SHARED_LIBRARY)  
  7.   
  8.   
  9. include $(CLEAR_VARS)  
  10. LOCAL_MODULE    := avdevice_prebuilt  
  11. LOCAL_SRC_FILES := libavdevice-57.so  
  12. include $(PREBUILT_SHARED_LIBRARY)  
  13.   
  14.   
  15. include $(CLEAR_VARS)  
  16. LOCAL_MODULE    := avfilter_prebuilt  
  17. LOCAL_SRC_FILES := libavfilter-6.so  
  18. include $(PREBUILT_SHARED_LIBRARY)  
  19.   
  20.   
  21. include $(CLEAR_VARS)  
  22. LOCAL_MODULE    := avformat_prebuilt  
  23. LOCAL_SRC_FILES := libavformat-57.so  
  24. include $(PREBUILT_SHARED_LIBRARY)  
  25.   
  26.   
  27. include $(CLEAR_VARS)  
  28. LOCAL_MODULE    := avutil_prebuilt  
  29. LOCAL_SRC_FILES := libavutil-55.so  
  30. include $(PREBUILT_SHARED_LIBRARY)  
  31.   
  32.   
  33. include $(CLEAR_VARS)  
  34. LOCAL_MODULE    := swresample_prebuilt  
  35. LOCAL_SRC_FILES := libswresample-2.so  
  36. include $(PREBUILT_SHARED_LIBRARY)  
  37.   
  38.   
  39. include $(CLEAR_VARS)  
  40. LOCAL_MODULE    := swscale_prebuilt  
  41. LOCAL_SRC_FILES := libswscale-4.so  
  42. include $(PREBUILT_SHARED_LIBRARY)  
  43.   
  44.   
  45. include $(CLEAR_VARS)  
  46. LOCAL_MODULE    := postproc_prebuilt  
  47. LOCAL_SRC_FILES := libpostproc-54.so  
  48. include $(PREBUILT_SHARED_LIBRARY)  
  49.   
  50.   
  51. #myapp lib  
  52. include $(CLEAR_VARS)  
  53. LOCAL_MODULE    := FFMPEG4Android  
  54. LOCAL_SRC_FILES := FFMPEG4Android.c cmdutils.c ffmpeg_filter.c ffmpeg_opt.c ffmpeg_mod.c  
  55. LOCAL_C_INCLUDES +=$(LOCAL_PATH)/include  
  56. LOCAL_LDLIBS := -llog -lz  
  57. LOCAL_SHARED_LIBRARIES := avcodec_prebuilt avdevice_prebuilt avfilter_prebuilt avformat_prebuilt avutil_prebuilt swresample_prebuilt swscale_prebuilt postproc_prebuilt  
  58. include $(BUILD_SHARED_LIBRARY)  
讲解一下:
       Android.mk是用来描述要编译某个具体的模块,所需要的一些资源,包括要编译的源码、要链接的库等等。由于本项目jni目录下添加了cmdutils.c ffmpeg_filter.c ffmpeg_opt.c ffmpeg_mod.c源文件,因此,我们还需要在该文件的LOCAL_SRC_FILES变量中进行添加,否则会编译不通过。
4. Application.mk
[cpp] view plain copy
  1. APP_LDFLAGS := -latomic  
  2. #指定so支持的平台  
  3. 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命令
[java] view plain copy
  1. /** 主界面 
  2.  *  
  3.  * @author Created by jianddongguo on 2017年6月26日下午11:14:27 
  4.  * @blogs http://blog.csdn.net/andrexpert 
  5.  */  
  6. public class MainActivity extends Activity {  
  7.     private String rootPath;  
  8.   
  9.   
  10.     @Override  
  11.     protected void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.activity_main);  
  14.         rootPath = Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator ;  
  15.     }  
  16.   
  17.   
  18.     public void onAddWaterClick(View v) {  
  19.           
  20.         new Thread(new Runnable(){  
  21.   
  22.   
  23.             @Override  
  24.             public void run() {  
  25.                 String input = rootPath + "20170627_145524.mp4";  
  26.                 String output = rootPath + "20170627_145524_output_addwater.mp4";  
  27.                 File inFile = new File(input);  
  28.                 if (!inFile.exists()) {  
  29.                     return;  
  30.                 }  
  31.                 File outFile = new File(output);  
  32.                 if(outFile.exists()){  
  33.                     outFile.delete();  
  34.                 }  
  35.                 String watermark = rootPath + "luchibao.jpg";  
  36.                 String addWaterCmd = String.format("ffmpeg -i %s -i %s -filter_complex overlay=150:50 %s", input,watermark,output);  
  37.                 String[] argv = addWaterCmd.split(" ");  
  38.                 VideoFixUtils.excuteFFMPEGCmd(argv.length, argv);  
  39.             }         
  40.         }).start();       
  41.     }  
  42.       
  43.     public void onRotateClick(View v){  
  44.         new Thread(new Runnable(){  
  45.   
  46.   
  47.             @Override  
  48.             public void run() {  
  49.                 String input = rootPath + "20170627_145524.mp4";  
  50.                 String output = rootPath + "20170627_145524_output_rotate.mp4";  
  51.                 File inFile = new File(input);  
  52.                 if (!inFile.exists()) {  
  53.                     return;  
  54.                 }  
  55.                 File outFile = new File(output);  
  56.                 if(outFile.exists()){  
  57.                     outFile.delete();  
  58.                 }  
  59.                 // 得到视频旋转角度  
  60.                 int rotationOfVideo = VideoFixUtils.getVideoAngle(inFile.getAbsolutePath());  
  61.                 // 计算调整的弧度  
  62.                 double rotate = rotationOfVideo * Math.PI / 180;  
  63.                 String rotateCmd = String.format("ffmpeg -i %s -filter_complex rotate=%f %s", input,rotate,output);  
  64.                 String[] argv = rotateCmd.split(" ");  
  65.                 VideoFixUtils.excuteFFMPEGCmd(argv.length, argv);  
  66.             }         
  67.         }).start();   
  68.     }  
  69. }  
讲解一下:
        关于ffmpeg命令的使用,这里先不做详解,后期补上,其使用规则:
                      ffmpeg [[options][`-i' input_file]]... {[options] output_file}.
6. ffmpeg命令执行日志

7. 结果演示
阅读全文
0 0
原创粉丝点击