android端採用FFmpeg進行視頻剪切、轉碼與添加水印

前兩篇文章介紹過FFmpeg進行音頻處理、音視頻處理:android端採用FFmpeg進行音頻混合與拼接剪切 android端採用FFmpeg進行音視頻合成與分離。關於FFmpeg涉及文件導入以及cmake配置,可查看第一篇文章。如今接着探討視頻相關處理:視頻轉碼、視頻剪切、視頻截圖、添加水印、視頻轉Gif動圖、圖片合成視頻。java

一、視頻轉碼

視頻轉碼包括格式、碼率、尺寸大小等轉換,-f表明強制轉換格式,-b表明碼率,-s表明尺寸,-r表明幀率:android

/**
     * 使用ffmpeg命令行進行視頻轉碼
     * @param srcFile 源文件
     * @param targetFile 目標文件(後綴指定轉碼格式)
     * @return 轉碼後的文件
     */
    public static String[] transformVideo(String srcFile, String targetFile){
        String transformVideoCmd = "ffmpeg -i %s -r 25 -b 200 -s 1080x720 %s";
        transformVideoCmd = String.format(transformVideoCmd, srcFile, targetFile);
        return transformVideoCmd.split(" ");//以空格分割爲字符串數組
    }

二、視頻剪切

視頻剪切是從視頻指定開始時間,剪切指定時長視頻,-ss表明開始時間,-t表明時長:git

/**
     * 使用ffmpeg命令行進行視頻剪切
     * @param srcFile 源文件
     * @param startTime 剪切的開始時間(單位爲秒)
     * @param duration 剪切時長(單位爲秒)
     * @param targetFile 目標文件
     * @return 剪切後的文件
     */
    public static  String[] cutVideo(String srcFile, int startTime, int duration, String targetFile){
        String cutVideoCmd = "ffmpeg -i %s -ss %d -t %d %s";
        cutVideoCmd = String.format(cutVideoCmd, srcFile, startTime, duration, targetFile);
        return cutVideoCmd.split(" ");//以空格分割爲字符串數組
    }

三、視頻截圖

視頻截圖是從視頻截取當前一幀畫面,保存爲指定格式的圖片,使用image2工具:github

/**
     * 使用ffmpeg命令行進行視頻截圖
     * @param srcFile 源文件
     * @param size 圖片尺寸大小
     * @param targetFile 目標文件
     * @return 截圖後的文件
     */
    public static  String[] screenShot(String srcFile, String size, String targetFile){
        String screenShotCmd = "ffmpeg -i %s -f image2 -t 0.001 -s %s %s";
        screenShotCmd = String.format(screenShotCmd, srcFile, size, targetFile);
        return screenShotCmd.split(" ");//以空格分割爲字符串數組
    }

四、添加水印

給視頻添加水印包括:文字和圖片,使用到filter_complex濾波器,overlay指定圖片在視頻的位置:數組

/**
     * 使用ffmpeg命令行給視頻添加水印
     * @param srcFile 源文件
     * @param waterMark 水印文件路徑
     * @param targetFile 目標文件
     * @return 添加水印後的文件
     */
    public static  String[] addWaterMark(String srcFile, String waterMark, String targetFile){
        String waterMarkCmd = "ffmpeg -i %s -i %s -filter_complex overlay=0:0 %s";
        waterMarkCmd = String.format(waterMarkCmd, srcFile, waterMark, targetFile);
        return waterMarkCmd.split(" ");//以空格分割爲字符串數組
    }
添加圖片水印效果:

添加文字水印,實際上是把文字轉成圖片,最終都是以圖片的形式添加的,文字轉圖片過程以下:

/**
     * 文本轉成Bitmap
     * @param text 文本內容
     * @param context 上下文
     * @return 圖片的bitmap
     */
    private static Bitmap textToBitmap(String text , Context context) {
        float scale = context.getResources().getDisplayMetrics().scaledDensity;
        TextView tv = new TextView(context);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);
        tv.setLayoutParams(layoutParams);
        tv.setText(text);
        tv.setTextSize(scale * TEXT_SIZE);
        tv.setGravity(Gravity.CENTER_HORIZONTAL);
        tv.setDrawingCacheEnabled(true);
        tv.setTextColor(TEXT_COLOR);
        tv.setBackgroundColor(Color.WHITE);
        tv.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
        tv.layout(0, 0, tv.getMeasuredWidth(), tv.getMeasuredHeight());
        tv.buildDrawingCache();
        Bitmap bitmap = tv.getDrawingCache();
        int rate = bitmap.getHeight() / 20;
        return Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/rate, 20, false);
    }

    /**
     * 文字生成圖片
     * @param filePath filePath
     * @param text text
     * @param context context
     * @return 生成圖片是否成功
     */
    public static boolean textToPicture(String filePath, String text , Context context){
        Bitmap bitmap = textToBitmap(text , context);
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(filePath);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }finally {
            try {
                if(outputStream != null){
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
添加文字水印的效果:


五、視頻截取GIF動圖

從視頻截取一段畫面,以GIF格式保存,使用到GIF工具,用-ss指定開始時間,-t指定截取時長:

/**
     * 使用ffmpeg命令行進行視頻轉成Gif動圖
     * @param srcFile 源文件
     * @param startTime 開始時間
     * @param duration 截取時長
     * @param targetFile 目標文件
     * @return Gif文件
     */
    public static  String[] generateGif(String srcFile, int startTime, int duration, String targetFile){
        //String screenShotCmd = "ffmpeg -i %s -vframes %d -f gif %s";
        String screenShotCmd = "ffmpeg -i %s -ss %d -t %d -s 320x240 -f gif %s";
        screenShotCmd = String.format(screenShotCmd, srcFile, startTime, duration, targetFile);
        return screenShotCmd.split(" ");//以空格分割爲字符串數組
    }
截取出來的GIF動圖效果:


六、圖片合成視頻

圖片合成視頻是把文件夾的有序命名圖片,使用-image2合成視頻,用-r指定幀率,這裏指定一秒播放一幀:app

/**
     * 使用ffmpeg命令行進行圖片合成視頻
     * @param srcFile 源文件
     * @param targetFile 目標文件(mpg格式)
     * @return 合成的視頻文件
     */
    public static  String[] pictureToVideo(String srcFile, String targetFile){
        //-f image2:表明使用image2格式,須要放在輸入文件前面
        String combineVideo = "ffmpeg -f image2 -r 1 -i %simg#d.jpg -vcodec mpeg4 %s";
        combineVideo = String.format(combineVideo, srcFile, targetFile);
        combineVideo = combineVideo.replace("#", "%");
        Log.i("VideoHandleActivity", "combineVideo=" + combineVideo);
        return combineVideo.split(" ");//以空格分割爲字符串數組
    }
因爲合成的視頻沒法上傳到這裏,我使用FFmpeg把視頻再轉成GIF動圖:


拼接好命令行後,調用FFmpeg的native方法處理:
ide

/**
     * 調用ffmpeg處理視頻
     * @param handleType handleType
     */
    private void doHandleVideo(int handleType){
        String[] commandLine = null;
        switch (handleType){
            case 0://視頻轉碼
                String transformVideo = PATH + File.separator + "transformVideo.wmv";
                commandLine = FFmpegUtil.transformVideo(srcFile, transformVideo);
                break;
            case 1://視頻剪切
                String cutVideo = PATH + File.separator + "cutVideo.mp4";
                int startTime = 0;
                int duration = 20;
                commandLine = FFmpegUtil.cutVideo(srcFile, startTime, duration, cutVideo);
                break;
            case 2://視頻合併
                break;
            case 3://視頻截圖
                String screenShot = PATH + File.separator + "screenShot.jpg";
                String size = "1080x720";
                commandLine = FFmpegUtil.screenShot(srcFile, size, screenShot);
                break;
            case 4://視頻添加水印
                //一、圖片
                String photo = PATH + File.separator + "launcher.png";
                String photoMark = PATH + File.separator + "photoMark.mp4";
                commandLine = FFmpegUtil.addWaterMark(appendVideo, photo, photoMark);
                //二、文字
                //String text = "Hello,FFmpeg";
                //String textPath = PATH + File.separator + "text.jpg";
                //boolean result = BitmapUtil.textToPicture(textPath, text, this);
                //Log.i(TAG, "text to pitcture result=" + result);
                //String textMark = PATH + File.separator + "textMark.mp4";
                //commandLine = FFmpegUtil.addWaterMark(appendVideo, photo, textMark);
                break;
            case 5://視頻轉成gif
                String Video2Gif = PATH + File.separator + "Video2Gif.gif";
                int gifStart = 30;
                int gifDuration = 5;
                commandLine = FFmpegUtil.generateGif(srcFile, gifStart, gifDuration, Video2Gif);
                break;
            case 6://屏幕錄製
                break;
            case 7://圖片合成視頻
                //圖片所在路徑,圖片命名格式img+number.jpg
                String picturePath = PATH + File.separator + "img/";
                String combineVideo = PATH + File.separator + "combineVideo.mp4";
                commandLine = FFmpegUtil.pictureToVideo(picturePath, combineVideo);
                break;
            default:
                break;
        }
        executeFFmpegCmd(commandLine);
    }
FFmpeg處理的回調與更新界面:

/**
     * 執行ffmpeg命令行
     * @param commandLine commandLine
     */
    private void executeFFmpegCmd(final String[] commandLine){
        if(commandLine == null){
            return;
        }
        FFmpegCmd.execute(commandLine, new FFmpegCmd.OnHandleListener() {
            @Override
            public void onBegin() {
                Log.i(TAG, "handle video onBegin...");
                mHandler.obtainMessage(MSG_BEGIN).sendToTarget();
            }

            @Override
            public void onEnd(int result) {
                Log.i(TAG, "handle video onEnd...");

                mHandler.obtainMessage(MSG_FINISH).sendToTarget();
            }
        });
    }
最終調用的FFmpeg的run方法:

JNIEXPORT jint JNICALL Java_com_frank_ffmpeg_FFmpegCmd_handle
(JNIEnv *env, jclass obj, jobjectArray commands){
    int argc = (*env)->GetArrayLength(env, commands);
    char **argv = (char**)malloc(argc * sizeof(char*));
    int i;
    int result;
    for (i = 0; i < argc; i++) {
        jstring jstr = (jstring) (*env)->GetObjectArrayElement(env, commands, i);
        char* temp = (char*) (*env)->GetStringUTFChars(env, jstr, 0);
        argv[i] = malloc(1024);
        strcpy(argv[i], temp);
        (*env)->ReleaseStringUTFChars(env, jstr, temp);
    }
    //執行ffmpeg命令
    result =  run(argc, argv);
    //釋放內存
    for (i = 0; i < argc; i++) {
        free(argv[i]);
    }
    free(argv);
    return result;
}

好了,使用FFmpeg進行視頻剪切、轉碼、截圖、添加水印、截取GIF圖等介紹完畢。若是各位有什麼問題或者建議,歡迎交流。工具

源碼:https://github.com/xufuji456/FFmpegAndroid。若是對您有幫助,麻煩fork和star。ui