音影片點播服務基礎系列(Fmpeg常用命令)

前言

  公司業務中有一些場景需要用到服務端音影片剪輯技術,最開始為了快速上線使用的是某公有雲的商用解決方案,但由於費用太高所以我們團隊經過一個星期的衝刺,給出了一個FFmpeg+Serverless的解決方案,更好地滿足了業務方的影片剪輯需求。經過統計,自研方案成功地將剪輯失敗率降到了萬分之一左右,並且將費用成本降到了原本的15%左右,是一個非常大的突破。現在我們計劃把該平台做得更加通用化,讓更多的業務可以無縫接入,通過任務編排來實現更多訂製化需求。

  題外話,為什麼我們會選擇Serverless來實現該業務呢,因為我們的業務高峰期特別明顯,時效性要求也高,在使用某公有雲解決方案期間經常觸發系統QPS限制,經過多次溝通也只能臨時調整,而且對方技術半夜還打電話來問我是否可以限制QPS,作為使用方肯定是不願意的,所以除了成本外性能也是我們下決心自研系統的原因之一。Serverless在使用過程中遇到的問題我也會在後續時間裡面記錄下來。

  本編部落格主要是記錄在整個過程中涉及到的FFmpeg常見命令,以及一些坑,分享給大家使用,若有謬誤請批評指正。

 

基本命令

音影片剪切

String.format(“%s -i %s -vcodec libx264  -ss %s -to %s %s -y”, ffmpegCommandPath, sourceFilePath, startTime, endTime, targetFilePath)

ffmpegCommandPath    表示FFmpeg執行命令的路徑

sourceFilePath              表示源文件路徑

startTime                     表示剪切的起始點,格式為 “00:00:00”, 例如 “00:00:15” 表示剪切從第15秒開始

endTime                      表示剪切的終止點,格式為 “00:00:00”, 例如 “00:00:20” 表示剪切截止到第20秒

targetFilePath               表示剪切後的輸出文件

-y                               表示輸出文件若存在則覆蓋  

 

音頻/影片簡單拼接

String.format(“%s -f concat -safe 0 -i %s -c copy %s”, ffmpegCommandPath, concatListFilePath, destinationFilePath)

ffmpegCommandPath    表示FFmpeg執行命令的路徑

concatListFilePath         表示拼接的配置文件,內容格式為

file ‘filePath1.audio’

file ‘filePath2.audio’

destinationFilePath        表示拼接後的輸出文件

使用限制:該方式不涉及到文件的解碼、編碼,所以速度極快,但如果待處理文件的編碼格式不同則請勿使用,否則輸出的文件可能無法正常播放(或者只能播放一部分)。如果編碼格式不同,請參考下文中的音頻拼接/影片拼接方式,會更加可靠,當會更加消耗資源。

 

音頻拼接

由於涉及到到參數拼接,所以直接上程式碼(Java方式)。

/**
 * 音頻文件拼接
 * @param files                     音頻文件資源路徑數組
 * @param destinationFilePath       處理後輸出文件路徑
 */
public static void audioConcat(String[] files, String destinationFilePath) {
    // command list
    List<String> commandList = new ArrayList<>();
    commandList.add("ffmpeg");

    // input_options
    for (String file : files) {
        commandList.add("-i");
        commandList.add(file);
    }

    // filter_complex
    StringBuilder filterComplexOptions = new StringBuilder();
    for (int i = 0; i < files.length; i++) {
        filterComplexOptions.append(String.format("[%s:0]", i));
    }
    filterComplexOptions.append(String.format("concat=n=%s:v=0:a=1[out]", files.length));
    commandList.add("-filter_complex");
    commandList.add(filterComplexOptions.toString());
    commandList.add("-map");
    commandList.add("[out]");
    commandList.add(destinationFilePath);
    Runtime.getRuntime().exec(commandList.toArray(new String[0]));

    // next process
}

 

影片拼接

由於涉及到到參數拼接,所以直接上程式碼(Java方式)。

/**
 * 影片拼接
 * @param files                  音頻文件資源路徑數組
 * @param destinationFilePath    處理後輸出文件路徑
 * @param outputWidth            輸出影片的寬度
 * @param outputHeight           輸出影片的高度
 */
public static void videoConcat(String[] files, String destinationFilePath, Integer outputWidth, Integer outputHeight) {
    // command list
    List<String> commandList = buildFfmpegCommand();
    commandList.add("ffmpeg");

    // input_options
    for (String file : files) {
        commandList.add("-i");
        commandList.add(file);
    }

    // filter_complex
    StringBuilder filterComplexOptions = new StringBuilder();
    StringBuilder streamsOptions = new StringBuilder();
    for (int i = 0; i < files.length; i++) {
        filterComplexOptions.append(String.format("[%s:v]scale=w=%s:h=%s,setsar=1/1[v%s];", i, outputWidth, outputHeight, i));
        streamsOptions.append(String.format("[v%s][%s:a]", i, i));
    }
    streamsOptions.append(String.format("concat=n=%s:v=1:a=1 [vv] [aa]", files.length));
    commandList.add("-filter_complex");
    commandList.add(String.format("%s%s", filterComplexOptions.toString(), streamsOptions.toString()));
    Collections.addAll(commandList, "-map", "[vv]", "-map", "[aa]", "-c:v", "libx264", "-x264-params",
        "profile=main:level=3.1", "-crf", "18", "-y", "-vsync", "vfr");
    commandList.add(destinationFilePath);
    Runtime.getRuntime().exec(commandList.toArray(new String[0]));

    // next process
}

踩坑經驗: 我們在拼接過程中遇到了影片拼接出錯的情況,但數量比較少,通過FFprobe命令分析,發現這種情況出現在其中某個影片無音軌的情況,找了很多解決方案,最後採用的方式是為這個影片配一個音軌,相當於先把素材標準化處理,為影片注入音軌的方式見下文 “無音軌影片配音“。

 

音影片混合

String.format(“%s -i %s -i %s -filter_complex amix -map 0:v -map 0:a -map 1:a -shortest -y %s”, ffmpegCommandPath, videoFilePath, audioFilePath, targetFilePath)

ffmpegCommandPath    表示FFmpeg執行命令的路徑

videoFilePath                表示影片文件路徑

audioFilePath                表示音頻文件路徑

targetFilePath               表示輸出文件路徑

 

無音軌影片配音

String.format(“%s -i %s -f lavfi -i aevalsrc=0 -shortest -y %s”, ffmpegCommandPath, videoFilePath, targetFilePath)

ffmpegCommandPath    表示FFmpeg執行命令的路徑

videoFilePath                表示影片文件路徑

targetFilePath               表示輸出文件路徑