您的位置:首页 > 移动开发 > Android开发

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文件,这里我们对其进行了重命名,需要修改以下几个地方,否则会报错:
(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. 结果演示

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android ndk ffmpeg mian JNI