Java調用FFmpeg進行視頻處理及Builder設計模式的應用


一、FFmpeg是什麼

FFmpeg( https://www.ffmpeg.org)是一套能夠用來記錄、轉換數字音頻、視頻,並能將其轉化爲流的開源計算機程序。它用來幹嘛呢?視頻採集、視頻格式轉化、視頻截圖、視頻添加水印、視頻切片(m3u八、ts)、視頻錄製、視頻推流、更改音視頻參數(編碼方式、分辨率、碼率、比特率等)功能,等等...

下載下來解壓完了呢是這個樣子:

bin中文件夾有個 ffmpeg.exe,點開,是的,一閃而逝並無什麼用,由於ffmpeg靠命令行來調用:

如上圖命令讀取 《小星星》.mp4 文件的文件信息,下面的一大片輸出就是文件的信息輸出了。

二、Java調用FFmpeg

如今咱們須要把目標視頻在第10秒處截圖,怎麼作呢,先看看用命令行是這樣:

C:\workspace\project\greejoy\picManager\web\tools\ffmpeg\bin\ffmpeg.exe -i C:\Users\Dulk\Desktop\ukulele\01\《小星星》.mp4 -f image2 -ss 10 -t 0.001 -s 320*240 C:\Users\Dulk\Desktop\ukulele\01\littleStar.jpg
其中:
  • C:\workspace\project\greejoy\picManager\web\tools\ffmpeg\bin\ffmpeg.exe 指 ffmpeg.exe 的路徑
  • C:\Users\Dulk\Desktop\ukulele\01\《小星星》.mp4 指源視頻路徑
  • C:\Users\Dulk\Desktop\ukulele\01\littleStar.jpg 指截圖的輸出路徑
  • -i 表示輸入文件的命令
  • -f 表示輸出格式,image2表示輸出爲圖片
  • -ss 表示指定的起始位置,這裏設置爲10,即10s處
  • -t 表示設置錄製/轉碼時長,既然截圖就0.001就足夠了
  • -s 表示size,設置幀大小
(更多的 ffmpeg命令行參數詳解

如今咱們看看用Java調用FFmpeg來實現相同的目的,要用到 ProcessBuilder 這個類,該類用於建立操做系統進程,執行本地命令或腳本等工做,其屬性command是一個字符串列表(用以輸入命令),而後經過start()方法啓動執行,但須要注意的是, 該方法調用會啓動新的進程執行操做,因此並非和原Java代碼同步執行的

public class FFmpegTest {

    public static void main(String[] args) {

        String ffmpegExePath = "C:\\workspace\\project\\greejoy\\picManager\\web\\tools\\ffmpeg\\bin\\ffmpeg.exe";
        String inputFilePath = "C:\\Users\\Dulk\\Desktop\\ukulele\\01\\《小星星》.mp4";
        String outputFilePath = "C:\\Users\\Dulk\\Desktop\\ukulele\\01\\littleStarJava.jpg";

        List<String> command = new ArrayList<String>();
        command.add(ffmpegExePath);
        command.add("-i");
        command.add(inputFilePath);
        command.add("-f");
        command.add("image2");
        command.add("-ss");
        command.add("10");
        command.add("-t");
        command.add("0.001");
        command.add("-s");
        command.add("320*240");
        command.add(outputFilePath);

        ProcessBuilder builder = new ProcessBuilder();
        builder.command(command);
        //正常信息和錯誤信息合併輸出
        builder.redirectErrorStream(true);
        try {
            //開始執行命令
            Process process = builder.start();

            //若是你想獲取到執行完後的信息,那麼下面的代碼也是須要的
            StringBuffer sbf = new StringBuffer();
            String line = null;
            BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
            while ((line = br.readLine()) != null) {
                sbf.append(line);
                sbf.append(" ");
            }
            String resultInfo = sbf.toString();
            System.out.println(resultInfo);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

能夠看到,拼接命令實際上就是把參數和值按序傳入一個List,而後將該List傳給 ProcessBuilder 類,而後執行 start() 方法便可。能夠看到控制檯輸出的信息以下:

須要注意的是:
  • 操做執行信息的讀取是分爲正常信息和錯誤信息,分別須要使用 getInputStream() 和 getErrorStream() 讀取,上例使用了 builder.redirectErrorStream(true); 將二者合併輸出
  • 若是要使用 Process.waitFor() 則須要當心阻塞問題,此處不展開(FFmpeg在JAVA中的使用以及Process.waitFor()引起的阻塞問題

三、Builder設計模式的應用

若是你每次使用都要如上例中add()一大堆參數,我估計也是頭疼:
List<String> command = new ArrayList<String>();
command.add(ffmpegExePath);
command.add("-i");
command.add(inputFilePath);
command.add("-f");
command.add("image2");
command.add("-ss");
command.add("10");
command.add("-t");
command.add("0.001");
command.add("-s");
command.add("320*240");
command.add(outputFilePath);

後來一想,這簡直就是絕佳的使用builder模式的示例,先把命令封裝成一個類:
/**
 * FFmpeg命令的封裝類
 */
public class FFmpegCommand {

    private List<String> command;

    public FFmpegCommand(List<String> command) {
        this.command = command == null ? new ArrayList<String>() : command;
    }

    public List<String> getCommand() {
        return command;
    }

    public void setCommand(List<String> command) {
        this.command = command;
    }

    /**
     * 開始執行命令
     *
     * @param callback 回調
     * @return 命令的信息輸出
     * @throws FFmpegCommandException
     */
    public String start(FFmpegCallback callback) throws FFmpegCommandException {
        BufferedReader br = null;
        StringBuffer sbf = new StringBuffer();
        String resultInfo = null;
        try {
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(command);
            //正常信息和錯誤信息合併輸出
            builder.redirectErrorStream(true);
            //開啓執行子線程
            Process process = builder.start();

            String line = null;
            br = new BufferedReader(new InputStreamReader(process.getInputStream()));
            while ((line = br.readLine()) != null) {
                sbf.append(line);
                sbf.append(" ");
            }
            resultInfo = sbf.toString();

            //等待命令子線程執行完成
            int exitValue = process.waitFor();
            //完成後執行回調
            if (exitValue == 0 && callback != null) {
                callback.complete(resultInfo);
            }
            //銷燬子線程
            process.destroy();

        } catch (IOException e) {
            e.printStackTrace();
            throw new FFmpegCommandException(e.getMessage());
        } catch (InterruptedException e) {
            e.printStackTrace();
            throw new FFmpegCommandException("線程阻塞異常:" + e.getMessage());
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultInfo;
    }

}

天然,要有builder類:
public class FFmpegCommandBuilder {

    List<String> command = new ArrayList<String>();

    public FFmpegCommandBuilder(String exePath) {
        if (exePath == null) {
            throw new FFmpegCommandRuntimeException("ffmpeg.exe 路徑不得爲空");
        }
        //添加命令的exe執行文件位置
        command.add(exePath);
    }

    /**
     * 添加輸入文件的路徑
     *
     * @param inputFilePath
     */
    public FFmpegCommandBuilder input(String inputFilePath) {
        if (inputFilePath != null) {
            command.add("-i");
            command.add(inputFilePath);
        }
        return this;
    }

    /**
     * 添加輸出文件的路徑
     *
     * @param outputFilePath
     */
    public FFmpegCommandBuilder output(String outputFilePath) {
        if (outputFilePath != null) {
            command.add(outputFilePath);
        }
        return this;
    }

    /**
     * 覆蓋輸出文件
     */
    public FFmpegCommandBuilder override() {
        command.add("-y");
        return this;
    }

    /**
     * 強制輸出格式
     *
     * @param format 輸出格式
     */
    public FFmpegCommandBuilder format(FFmpegCommandFormatEnum format) {
        if (format != null) {
            command.add("-f");
            command.add(format.getValue());
        }
        return this;
    }

    /**
     * 設置錄製/轉碼的時長
     *
     * @param duration 形如 0.001 表示0.001秒,hh:mm:ss[.xxx]格式的記錄時間也支持
     */
    public FFmpegCommandBuilder duration(String duration) {
        if (duration != null) {
            command.add("-t");
            command.add(duration);
        }
        return this;
    }

    /**
     * 搜索到指定的起始時間
     *
     * @param position 形如 17 表示17秒,[-]hh:mm:ss[.xxx]的格式也支持
     */
    public FFmpegCommandBuilder position(String position) {
        if (position != null) {
            command.add("-ss");
            command.add(position);
        }
        return this;
    }

    /**
     * 設置幀大小
     *
     * @param size 形如 xxx*xxx
     * @return
     */
    public FFmpegCommandBuilder size(String size) {
        if (size != null) {
            command.add("-s");
            command.add(size);
        }
        return this;
    }

    /**
     * 建立FFmpegCommand命令封裝類
     *
     * @return FFmpegCommand
     */
    public FFmpegCommand build() {
        return new FFmpegCommand(command);
    }

}

那麼使用builder模式,仍是剛纔的目的,咱們的代碼就變成了:
public class FFmpegBuilderTest {

    public static void main(String[] args) {

        String ffmpegExePath = "C:\\workspace\\project\\greejoy\\picManager\\web\\tools\\ffmpeg\\bin\\ffmpeg.exe";
        String inputFilePath = "C:\\Users\\Dulk\\Desktop\\ukulele\\01\\《小星星》.mp4";
        String outputFilePath = "C:\\Users\\Dulk\\Desktop\\ukulele\\01\\littleStarJavaBuilder.jpg";

        FFmpegCommandBuilder builder = new FFmpegCommandBuilder(ffmpegExePath);
        builder.input(inputFilePath).format(FFmpegCommandFormatEnum.IMAGE)
               .position("10").duration("0.001").size("320*240").output(outputFilePath);
        FFmpegCommand command = builder.build();
        try {
            String result = command.start(null);
            System.out.println(result);
        } catch (FFmpegCommandException e) {
            e.printStackTrace();
        }
    }

}
相關文章
相關標籤/搜索