1. 程式人生 > >Android ffmpeg解析和合成gif

Android ffmpeg解析和合成gif

概述

Android專案中使用ffmpeg3.0.9通過命令列呼叫的方式解析和合成gif。簡單學習總結一下。

解析gif,由gif得到一堆png

/**
 * 解析gif圖片
 * @param gifPath gif圖片地址
 * @param pngDir 解析後圖片儲存目錄
 * @return
 */
public static String[] gifToPng(String gifPath, String pngDir) {
    final int count = 6;
    String[] commands = new String[count];
    int index = 0;
    commands[index++] = "ffmpeg"
; commands[index++] = "-i"; commands[index++] = gifPath; commands[index++] = "-c:v"; commands[index++] = "png"; commands[index++] = pngDir + File.separator + "image%d.png"; if (TestLog.isDebug()) { TestLog.d(TAG, "index=" + index + ",count=" + count); for (int
i = 0 ; i < index; i++) { TestLog.d(TAG, "commend=" + commands[i]); } } return commands; }

解析後的圖片儲存到指定的檔案目錄下,命名規則image%d.png,如下圖所示:

QQ截圖20180308193203.png

一堆png合成gif

/**
 * png圖片合成gif
 * @param pngDir png資料夾地址 該資料夾下的png圖片命名需要滿足正則image%d.png
 * @param gifPath 生成的gif地址
 * @param
delay 幀間隔 ms * @param threadNum 執行緒數 0為預設最佳 * @return */
public static String[] pngToGif(String pngDir, String gifPath, long delay, int threadNum) { final int count = 11; String[] commands = new String[count]; int index = 0; commands[index++] = "ffmpeg"; commands[index++] = "-r"; commands[index++] = String.valueOf(1000.0f / delay); commands[index++] = "-threads"; commands[index++] = String.valueOf(threadNum); commands[index++] = "-c:v"; commands[index++] = "png"; commands[index++] = "-i"; commands[index++] = pngDir + File.separator + "image%d.png"; commands[index++] = "-y"; commands[index++] = gifPath; if (TestLog.isDebug()) { TestLog.d(TAG, "index=" + index + ",count=" + count); for (int i = 0 ; i < index; i++) { TestLog.d(TAG, "commend=" + commands[i]); } } return commands; }

合成後發現gif圖片質量很差,對比如下:

原圖.gif合成後.gif

實際使用中還發現合成前後部分顏色前後不一致,如之前顯示黑色,合成後顯示黃色。類似問題還有很多,這裡不在舉例。

優化gif合成

原因分析

GIF僅限於256色的調色盤。預設情況下,FFmpeg 只使用一個通用調色版去嘗試覆蓋所有的顏色區域,以此來支援含有大量內容的檔案。這就解釋了上述方法生成的gif檔案為何效果很差。

優化

FFmpeg 只使用一個通用調色版去嘗試覆蓋所有的顏色區域,所以我們可以針對特定的GIF圖片,提供特定的調色盤即重新定義一個調色盤來替代FFmpeg提供的通用調色盤。

為特定的gif生成調色盤

使用 palettegen 濾波器對每一幀的所有顏色製作一個直方圖,並且基於這些生成一個調色盤(以PNG檔案的形式儲存)。

/**
 * 特定gif生成調色盤
 * @param gifPath 輸入gif圖片檔案地址
 * @param globalPalettePicPath 輸出gif圖片檔案地址
 * @return
 */
public static String[] getglobalPalettePicPath(String gifPath, String globalPalettePicPath) {
    int count = 7;

    String[] commands = new String[count];
    int index = 0;
    commands[index++] = "ffmpeg";

    commands[index++] = "-i";
    commands[index++] = gifPath;
    /*commands[index++] = "-i";
    commands[index++] = waterImageUrl;*/
    commands[index++] = "-vf";
    commands[index++] = "palettegen";

    commands[index++] = "-y";
    commands[index++] = globalPalettePicPath;

    if (TestLog.isDebug()) {
        TestLog.d(TAG, "index=" + index + ",count=" + count);
        for (int i = 0 ; i < index; i++) {
            TestLog.d(TAG, "commend=" + commands[i]);
        }
    }
    return commands;
}

下圖是生成的調色盤:

gif_paletter.png

替換通用的調色盤

生成全域性調色盤後,就可以使用 paletteuse 濾波器替換通用的調色盤將顏色效果對映到顏色輸出流中。它將會使用這個調色盤來生成最終的量化顏色流,它的任務是在生成的調色盤中找出最合適的顏色來表示輸入的顏色。

/**
 * png圖片合成gif
 * @param pngDir png資料夾地址 該資料夾下的png圖片命名需要滿足正則image%d.png
 * @param gifPath 生成的gif地址
 * @param globalPalettePicPath 全域性顏色列表png圖地址
 * @param delay 幀間隔 ms
 * @param threadNum 執行緒數 0為預設最佳
 * @return
 */
public static String[] pngToGif(String pngDir, String gifPath, String globalPalettePicPath, long delay, int threadNum) {
    final int count = 15;
    String[] commands = new String[count];
    int index = 0;
    commands[index++] = "ffmpeg";
    commands[index++] = "-r";
    commands[index++] = String.valueOf(1000.0f / delay);
    commands[index++] = "-threads";
    commands[index++] = String.valueOf(threadNum);
    commands[index++] = "-c:v";
    commands[index++] = "png";
    commands[index++] = "-i";
    commands[index++] = pngDir + File.separator + "image%d.png";

    commands[index++] = "-i";
    commands[index++] = globalPalettePicPath;
    commands[index++] = "-lavfi";
    commands[index++] = "paletteuse=bayer";

    commands[index++] = "-y";
    commands[index++] = gifPath;
    if (TestLog.isDebug()) {
        TestLog.d(TAG, "index=" + index + ",count=" + count);
        for (int i = 0 ; i < index; i++) {
            TestLog.d(TAG, "commend=" + commands[i]);
        }
    }
    return commands;
}

參考