Android入门使用FFmepg添加水印

集成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"目录。

Android入门使用FFmepg添加水印_第1张图片
image

然后在main目录下新建一个JNI文件夹如下图

Android入门使用FFmepg添加水印_第2张图片
image

会弹出一个窗口,不要管直接finish;

接着把下载好的libffmpeg.so文件放到jni文件夹下。

Android入门使用FFmepg添加水印_第3张图片
image

此时这个文件全是乱码,不要在意,继续往下走。然后新建一个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

Android入门使用FFmepg添加水印_第4张图片
image

之后切换到项目的"src/main/java"目录下,然后输入"javah com.xjx.demo.utils.FFmpegKit"。(这里的"com.xjx.demo.utils"需要换成你的FFmpegKit这个类的完整包名),输入完后回车,这时会停顿几秒,然后,你的项目中就会自动生成两个c++的文件。

Android入门使用FFmepg添加水印_第5张图片
image

这两个的文件名和你刚才输入的包名是一样的。(这里作者有些疑问,别的博客都是只生成一个"com_xjx_demo_utils_FFmpegKit.h"这个文件,但是我不管试多少次都会生成后面那个文件,不是知道你们是不是这样的,然后作者没办法了,只能不管这俩基友,把这俩文件一起移动到刚才建的jni文件夹下)

Android入门使用FFmepg添加水印_第6张图片
image

接下来把下载好的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的文件是这样的

Android入门使用FFmepg添加水印_第7张图片
image

接下来在jni文件夹下新建一个c++文件,

Android入门使用FFmepg添加水印_第8张图片
image
Android入门使用FFmepg添加水印_第9张图片
image

文件名要和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);

}

这段代码复制进去。

其中要注意的

Android入门使用FFmepg添加水印_第10张图片
image

接下来在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)

Android入门使用FFmepg添加水印_第11张图片
image

同样的,再次在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"然后等上几秒,当命令行出现这两句时

image

我们来看看文件夹,

Android入门使用FFmepg添加水印_第12张图片
image

当出现文件夹生成这样几个文件后,恭喜你,你就成功了(总算完成集成了,接下来就剩下调用牛比哄哄的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(这里要提示一点,如果命令行输错的话,程序会崩溃,但是不会打印日志。)
好了,这篇文章,终于完成了,由于作者比较菜,所以文章写的烂,求大佬忽略就好。

你可能感兴趣的:(Android入门使用FFmepg添加水印)