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.ccmdutils.hffmpeg_filter.cffmpeg_opt.ccmdutils_common_opts.hconfig.hffmpeg.hffmpeg_mod.c另外,将ffmpeg_mod.c为源码目录下的ffmpeg.c文件,这里我们对其进行了重命名,需要修改以下几个地方,否则会报错:- (1) 注释65行:#include "libavcodec/mathops.h"
- (2) 注释1073行:
-
-
-
- (3) 重命名main(int argc,char **argv)为ffmpegmain(int argc,char **argv),并根据以下代码修改
- int ffmpegmain(int argc, char **argv){
- ......
-
-
-
-
- ......
-
-
-
-
-
-
-
-
- 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__)
-
- int ffmpegmain(int argc, char **argv);
第三步:从ffmpeg-3.3.2源码中拷贝头文件到jni/include目录对应文件夹下compat/va_copy.hlibavresample/avresample.hlibavresample/version.hlibavformat/ffm.hlibavformat/network.hlibavformat/os_support.hlibavformat/url.hlibavutil/libm.hlibavutil/internal.hlibavutil/timer.h (注释:'arm/timer.h)注:为什么要拷贝这些头文件?其实我也不知道有什么用,你自己ndk-build一下就知道了....二、在Android平台上的使用ffmpeg命令
1. VideoFixUtils.class,添加执行ffmpeg命令nativie方法-
-
-
-
-
- public class VideoFixUtils {
-
-
-
-
-
- public native static int getVideoAngle(String videoPath);
-
-
-
-
-
-
- public native static int excuteFFMPEGCmd(int argc , String[] cmdLines);
-
- static {
-
- System.loadLibrary("FFMPEG4Android");
-
- 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实现-
-
-
-
-
- #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);
-
- av_register_all();
-
-
- AVFormatContext *fmtCtx = avformat_alloc_context();
- avformat_open_input(&fmtCtx,c_videoPath,NULL,NULL);
-
- 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;
- }
- }
-
- AVDictionaryEntry *tag = NULL;
- tag = av_dict_get(fmtCtx->streams[v_stream_idx]->metadata,"rotate",tag,NULL);
- int angle = -1;
- if(tag != NULL){
-
- angle = atoi(tag->value);
- }
-
- 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;
-
- char** argv = (char **)malloc(sizeof(char *) * argc);
-
- 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);
- }
-
- 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命令-
-
-
-
-
- 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. 结果演示