音影片點播服務基礎系列(Fmpeg常用命令)
- 2021 年 6 月 25 日
- 筆記
- ffmpeg, Serverless
前言
公司業務中有一些場景需要用到服務端音影片剪輯技術,最開始為了快速上線使用的是某公有雲的商用解決方案,但由於費用太高所以我們團隊經過一個星期的衝刺,給出了一個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 表示輸出文件路徑