集成FFmepg
本文通过参考这篇博客,然后自己加工而成,原文地址:http://www.jianshu.com/p/e0c32c8b0ebc。
本文的demo:https://github.com/reverseAndroid/WaterMark
由于项目中的提出的需求,所以,才有了这篇文章。好,话不多说,直奔主题吧。(此文推荐菜鸟程序猿,如果是大神可以直接略过)
首先我们需要去官网下载FFmepg库。下载地址:http://www.ffmpeg.org/download.html。下载好之后,我们需要来编译一个libffmpeg.so文件。网上说了很多编译的方法。如果自己不想编译,可以用博主项目中的。(如果自己有兴趣想编译的这是地址:http://www.jianshu.com/p/dfd0de17601c)
接下来下载好libffmpeg.so文件后,在Android Studio中将这一栏切换成"Project"目录。
然后在main目录下新建一个JNI文件夹如下图
会弹出一个窗口,不要管直接finish;
接着把下载好的libffmpeg.so文件放到jni文件夹下。
此时这个文件全是乱码,不要在意,继续往下走。然后新建一个java类,FFmpegKit.java,写入下面这些代码:
package com.xjx.demo.utils;
import android.os.AsyncTask;
/**
* Created by Dell on 2017/11/17.
*/
public class FFmpegKit {
public interface KitInterface{
void onStart();
void onProgress(int progress);
void onEnd(int result);
}
static{
System.loadLibrary("ffmpeg");
System.loadLibrary("ffmpeginvoke");
}
public static int execute(String[] commands){
return run(commands);
}
public static void execute(String[] commands, final KitInterface kitIntenrface){
new AsyncTask(){
@Override
protected void onPreExecute() {
if(kitIntenrface != null){
kitIntenrface.onStart();
}
}
@Override
protected Integer doInBackground(String[]... params) {
return run(params[0]);
}
@Override
protected void onProgressUpdate(Integer... values) {
if(kitIntenrface != null){
kitIntenrface.onProgress(values[0]);
}
}
@Override
protected void onPostExecute(Integer integer) {
if(kitIntenrface != null){
kitIntenrface.onEnd(integer);
}
}
}.execute(commands);
}
public native static int run(String[] commands);
}
对于FFmpegKit.java这个类就是和FFmpeg这个库进行交互用的,说下个人的看法,这个我个人觉得这个类在某些时候可以理解成这就是个JNI,我们通过调用JNI来和FFmpeg交互,其实就是靠这个类去调用本文中下面说的那些c++的文件(作者比较菜只能这样理解了,大神可以纠正,轻喷)。
写入完代码,我们会发现public native static int run(String[] commands);
其中的"run"一直报红,但是却不报错,也没法解决,没事,不用管这东西,他就是这样,继续往下走。
接下来,有两种方法1.打开"cmd";2.在Android Studio中打开Terminal
之后切换到项目的"src/main/java"目录下,然后输入"javah com.xjx.demo.utils.FFmpegKit"。(这里的"com.xjx.demo.utils"需要换成你的FFmpegKit这个类的完整包名),输入完后回车,这时会停顿几秒,然后,你的项目中就会自动生成两个c++的文件。
这两个的文件名和你刚才输入的包名是一样的。(这里作者有些疑问,别的博客都是只生成一个"com_xjx_demo_utils_FFmpegKit.h"这个文件,但是我不管试多少次都会生成后面那个文件,不是知道你们是不是这样的,然后作者没办法了,只能不管这俩基友,把这俩文件一起移动到刚才建的jni文件夹下)
接下来把下载好的FFmpeg源码文件打开,复制ffmpeg.h, ffmpeg.c, ffmpeg_opt.c, ffmpeg_filter.c,cmdutils.c, cmdutils.h, cmdutils_common_opts.h到jni目录下。(这里要注意两个坑,第一源文件中并没有一个"logjam.h"这样的文件夹,但是那个博客却都有这个文件,而且我们如果不加的话,后面集成会有问题,所以我们新建一个,这个文件是一个c++的文件,第二博主照着复制了,最后却编译失败了,最后找到原因,他的项目中的这些文件,和他博客中说的更改的地方貌似少了很多,几乎可以说是他的文件改动了许多地方,但是他只说了改动某几个地方,不知道是博主自己水平问题还是什么,反正死活不行。后来博主自己花费了很长时间把这些文件都改好了,大家可以下载我的项目,直接把这几个文件导入进去)
建好"logjam.h"文件后,添加代码
#ifndefLOGJAM_H
#defineLOGJAM_H
#include
#define LOGTAG"FFmpegLog"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOGTAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , LOGTAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , LOGTAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN , LOGTAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOGTAG, __VA_ARGS__)
#endif
然后jni的文件是这样的
接下来在jni文件夹下新建一个c++文件,
文件名要和com_xjx_demo_utils_FFmpegKit.h一样,文件类型选择".c"类型。
然后把
#include
#include"com_xjx_demo_utils_FFmpegKit.h"
#include"ffmpeg.h"
JNIEXPORT jint JNICALL Java_com_xjx_demo_utils_FFmpegKit_run
(JNIEnv *env, jclass obj, jobjectArray commands){
//FFmpeg av_log() callback
intargc = (*env)->GetArrayLength(env, commands);
char*argv[argc];
LOGD("Kit argc %d\n", argc);
inti;
for(i =0; i < argc; i++) {
jstring js = (jstring) (*env)->GetObjectArrayElement(env, commands, i);
argv[i] = (char*) (*env)->GetStringUTFChars(env, js,0);
LOGD("Kit argv %s\n", argv[i]);
}
returnrun(argc, argv);
}
这段代码复制进去。
其中要注意的
接下来在jni文件夹下新建一个"Android.mk"文件,这里会让选择用什么样的格式查看,选择Text格式就行,只是为了方便我们编辑。然后
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg
LOCAL_SRC_FILES := libffmpeg.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeginvoke
LOCAL_SRC_FILES := com_xjx_demo_utils_FFmpegKit.c ffmpeg.c ffmpeg_opt.c cmdutils.c ffmpeg_filter.c
LOCAL_C_INCLUDES := C:\Users\Dell\Desktop\ffmpeg-3.2.4
LOCAL_LDLIBS := -llog -lz -ldl
LOCAL_SHARED_LIBRARIES := ffmpeg
include $(BUILD_SHARED_LIBRARY)
同样的,再次在jni文件夹下Application.mk文件,然后给里面编写
APP_ABI := armeabi-v7a
APP_PLATFORM := android-17
APP_BUILD_SCRIPT := Android.mk
其中APP_ABI的值是代表支持的cpu类型。要支持多种cpu的话,可以把类型写一起用空格隔开。就像这样"APP_ABI := armeabi-v7a x86"编辑完成后,修改高深的jni就告一段落了。
接下来我们需要打开"app下的build.gradle"文件,添加
sourceSets {
main {
jniLibs.srcDirs = ['src/main/libs']
jni.srcDirs=[]
}
}
完成后打开mainfest文件添加
这里的sdk版本号和你的"build.gradle"文件中的保持一致。(这里是作者踩过的坑)
添加完成后在Terminal或者"cmd"中,把当前目录切换到"src\main\jni"目录下,然后输入"ndk-build"然后等上几秒,当命令行出现这两句时
我们来看看文件夹,
当出现文件夹生成这样几个文件后,恭喜你,你就成功了(总算完成集成了,接下来就剩下调用牛比哄哄的jni了)。
如果输入"ndk-build"时没有生成相应的文件,而是显示异常了,那就再仔细核对下上述的步骤,看看哪一步没有做对。
如果没什么问题,我们就已经集成好FFmepg了,现在就是使用他的时候了,这里作者只给出演示添加水印的activity:
package com.demo.watermark;
import android.content.Intent;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.MediaController;
import android.widget.VideoView;
import com.demo.watermark.utils.FFmpegKit;
import com.demo.watermark.utils.ThreadPoolUtils;
import com.demo.watermark.view.WaitDialog;
import java.io.File;
import java.util.Date;
public class PlayActivity extends AppCompatActivity {
private static final String TAG = "PlayActivity";
private VideoView mVideoView;
private WaitDialog mWaitDialog;
private Handler mHandler;
private String videoAbsolutePath;
private String textAbsolutePath;
private String outputUrl;
private File videoFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play);
initData();
initView();
}
private void initData() {
Intent intent = getIntent();
videoAbsolutePath = intent.getStringExtra("path");
textAbsolutePath = intent.getStringExtra("imagePath");
//获取路径
String path = Environment.getExternalStorageDirectory() + File.separator + "video1";
//定义文件名
String fileName = new Date().getTime() + ".mp4";
videoFile = new File(path, fileName);
//文件夹不存在
if (!videoFile.getParentFile().exists()) {
videoFile.getParentFile().mkdirs();
}
outputUrl = videoFile.getAbsolutePath();
}
private void initView() {
mVideoView = (VideoView) findViewById(R.id.view);
//将视频和图片水印合成
makeVideo();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0) {
//视频播放的控制
playVideo();
}
}
};
}
private void makeVideo() {
mWaitDialog = new WaitDialog(PlayActivity.this, R.style.waitDialog);
mWaitDialog.setWaitText("正在生成视频...请不要退出(大概一分钟左右)");
mWaitDialog.show();
Runnable runnable = new Runnable() {
@Override
public void run() {
String[] commands = new String[12];
commands[0] = "ffmpeg";
commands[1] = "-i";
commands[2] = videoAbsolutePath;
commands[3] = "-i";
commands[4] = textAbsolutePath;
commands[5] = "-filter_complex";
//水印位置:(x,y)=(10,10)<=(left,top)距离左侧、底部各多少像素
commands[6]="overlay=main_w-overlay_w+75:main_h-overlay_h-1";
commands[7] = "-b"; //这个是为了使视频质量不掉的太严重
commands[8] = "2048k"; //这个是设置生成视频的大小
commands[9] = "-codec:a";
commands[10] = "copy";
commands[11] = outputUrl;
FFmpegKit.execute(commands, new FFmpegKit.KitInterface() {
@Override
public void onStart() {
Log.e("FFmpegLog LOGCAT", "FFmpeg 命令行开始执行了...");
}
@Override
public void onProgress(int progress) {
Log.e("FFmpegLog LOGCAT", "done com" + "FFmpeg 命令行执行进度..." + progress);
}
@Override
public void onEnd(int result) {
Log.e("FFmpegLog LOGCAT", "FFmpeg 命令行执行完成...");
Message msg = new Message();
msg.what = 0;
mHandler.sendMessage(msg);
mWaitDialog.dismiss();
}
});
}
};
ThreadPoolUtils.execute(runnable);
}
private void playVideo() {
mVideoView.setVideoPath(outputUrl);
mVideoView.setMediaController(new MediaController(PlayActivity.this));
if (!mVideoView.isPlaying()) {
mVideoView.start();
} else {
mVideoView.pause();
}
}
}
上面代码中,makeVideo()这个方法就是使用FFmepg的方法,由于FFmepg库是需要使用命令行操作的,所以,我们的方法里加了一个数组,用来编写命令行,具体的命令行怎么操作和输入,大家只能百度去找了。这里推荐一篇博客,作者是参考这个改的:http://blog.csdn.net/weiyuefei/article/details/52808890(这里要提示一点,如果命令行输错的话,程序会崩溃,但是不会打印日志。)
好了,这篇文章,终于完成了,由于作者比较菜,所以文章写的烂,求大佬忽略就好。