Android直播开发之旅(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. 结果演示


 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值