在安卓中用到的三方庫:https://github.com/arthenica/ffmpeg-kit
這個庫很強大,支持很多平臺,每個平臺都有各自的分支代碼,用了一段時間,穩定性挺好的,
找到安卓下的分支:FFmpegKit for Android FFmpegKit Android 版
引入項目:
repositories {mavenCentral()
}dependencies {implementation 'com.arthenica:ffmpeg-kit-full:6.0-2'
}
每個平臺下,又分為多個庫,每個庫包含不同的功能,因為功能越豐富,導入到項目中編譯的包體積越大,盡量選擇適合自己功能的庫進行使用。
📦 FFmpeg 編譯配置選項(庫依賴分類)
配置項 | 說明 | 包含的庫 |
---|---|---|
min | 最小化構建 | - |
min-gpl | 最小化構建并啟用 GPL 庫 | vid.stab , x264 , x265 , xvidcore |
https | 啟用 HTTPS 支持(使用非-GPL 依賴) | gmp , gnutls |
https-gpl | 啟用 HTTPS 支持(使用 GPL 兼容依賴) | gmp , gnutls , vid.stab , x264 , x265 , xvidcore |
audio | 啟用音頻相關編碼器/解碼器 | lame , libilbc , libvorbis , opencore-amr , opus , shine , soxr , speex , twolame , vo-amrwbenc |
video | 啟用視頻相關編碼器/解碼器 | dav1d , fontconfig , freetype , fribidi , kvazaar , libass , libiconv , libtheora , libvpx , libwebp , snappy , zimg |
full | 啟用所有非 GPL 第三方庫 | dav1d , fontconfig , freetype , fribidi , gmp , gnutls , kvazaar , libass , libiconv , libilbc , libtheora , libvorbis , libvpx , libwebp , libxml2 , opencore-amr , opus , shine , snappy , soxr , speex , twolame , vo-amrwbenc , zimg |
full-gpl | 啟用所有庫(包括 GPL) | dav1d , fontconfig , freetype , fribidi , gmp , gnutls , kvazaar , lame , libass , libiconv , libilbc , libtheora , libvorbis , libvpx , libwebp , libxml2 , opencore-amr , opus , shine , snappy , soxr , speex , twolame , vid.stab , vo-amrwbenc , x264 , x265 , xvidcore , zimg |
比如你如果只需要保存 rtsp 視頻流 和 推流的話,只需要導入 min-gpl
即可:
implementation 'com.arthenica:ffmpeg-kit-min-gpl:6.0-2'
如果你想要更多,比如添加水印,就涉及到 FFmpeg 濾鏡相關功能,就需要引入full-gpl
:
implementation 'com.arthenica:ffmpeg-kit-full-gpl:6.0-2'
具體需要什么功能,可以進去看說明,說明沒有涉及到的,并且你不想用全功能庫,你也可以一個一個試試,也許就能滿足你。
正文開始
由說明可知,使用方式為:
import com.arthenica.ffmpegkit.FFmpegKit;FFmpegSession session = FFmpegKit.execute("-i file1.mp4 -c:v mpeg4 file2.mp4");
if (ReturnCode.isSuccess(session.getReturnCode())) {// SUCCESS} else if (ReturnCode.isCancel(session.getReturnCode())) {// CANCEL} else {// FAILURELog.d(TAG, String.format("Command failed with state %s and rc %s.%s", session.getState(), session.getReturnCode(), session.getFailStackTrace()));}
或者異步調用:
FFmpegKit.executeAsync("-i file1.mp4 -c:v mpeg4 file2.mp4", new FFmpegSessionCompleteCallback() {@Overridepublic void apply(FFmpegSession session) {SessionState state = session.getState();ReturnCode returnCode = session.getReturnCode();// CALLED WHEN SESSION IS EXECUTEDLog.d(TAG, String.format("FFmpeg process exited with state %s and rc %s.%s", state, returnCode, session.getFailStackTrace()));}
}, new LogCallback() {@Overridepublic void apply(com.arthenica.ffmpegkit.Log log) {// CALLED WHEN SESSION PRINTS LOGS}
}, new StatisticsCallback() {@Overridepublic void apply(Statistics statistics) {// CALLED WHEN SESSION GENERATES STATISTICS}
});
與正常 ffmpeg 命令不同的是,在傳入命令時,前面不需要加 “ffmpeg” 關鍵字,只需傳入后面的具體命令即可,加上會報錯哦!
關鍵代碼:
搞了好久才湊齊的正確代碼,這東西真不能聽 AI 的一面之辭,不然就被 AI 一條路領到黑,
private static String buildWatermarkCommand(VideoFile file, VideoTimeRange timeRange, String outputPath, String fontPath) {FFmpegKitConfig.setFontconfigConfigurationPath(fontPath);String drawtextFilter = String.format("drawtext=text='%s':fontfile=%s:fontcolor=white:fontsize=20:x=0:y=30",DEFAULT_WATERMARK, fontPath);List<String> commandList = new ArrayList<>();commandList.add("-ss"); // 指定輸入文件的開始時間,格式:HH:MM:SScommandList.add(timeRange.startTime);commandList.add("-i"); // 輸入文件路徑commandList.add(file.filePath);commandList.add("-t"); // 指定持續時間,格式:HH:MM:SScommandList.add(timeRange.durationStr);commandList.add("-vf"); // 視頻濾鏡,用于添加水印文字commandList.add(drawtextFilter);commandList.add("-c:v"); // 視頻編碼器設置commandList.add(VIDEO_CODEC); // 使用H.264軟件編碼器commandList.add("-preset"); // 編碼速度預設commandList.add(VIDEO_PRESET); // ultrafast:最快編碼速度,文件稍大commandList.add("-crf"); // 恒定質量因子(0-51,越小質量越好)commandList.add(String.valueOf(VIDEO_CRF)); // 23:平衡質量和文件大小的推薦值commandList.add("-c:a"); // 音頻編碼器設置commandList.add("copy"); // 直接復制音頻流,不重新編碼commandList.add("-r");commandList.add("20"); // 每秒20幀commandList.add("-avoid_negative_ts"); // 避免負時間戳問題commandList.add("make_zero"); // 將負時間戳調整為0commandList.add(outputPath); // 輸出文件路徑return String.join(" ", commandList);}
簡單介紹下命令作用:對一段現有的視頻文件進行剪輯,-ss
指定開始時間,比如要剪切的原視頻時長為兩分鐘,所以開始時間到結束時間就是:00:00:00 - 00:02:00 , 假設要剪輯中間一分鐘的視頻,那么 -ss
指定的開始時間為:00:00:30 , -t
持續時間就是:00:01:00 , 截取的時間段為:00:00:30 - 00:01:30 ,-c:v
設置編碼器,一般 H.264 就夠了,如果設置其他的編碼器,要看你的設備支持不支持了,-r
設置幀率,如果你想要剪切的視頻大小小一點,一方面就可以通過降低幀率,另一方面就可以降低碼率來實現(上述命令碼率未指定默認按原視頻碼率)。
如果你指定的時間范圍,超過原視頻時長會報錯,這很正常,只是報錯內容可能看不懂,這是一個問題點!
設置 FFmpegKitConfig.setFontconfigConfigurationPath
的作用是 https://github.com/arthenica/ffmpeg-kit/wiki/Tips 參考第四條:ffmpeg 需要有效的 fontconfig 配置才能在使用 drawtext filter 時渲染文本。
這里指定一個存在的字體路徑即可 比如:/system/fonts/NotoSansCJK-Regular.ttc
注意:有的字體不支持中文,寫入中文水印的時候會亂碼!
視頻處理性能測試(A133,Android 10)
測試環境
- 設備平臺:A133
- 操作系統:Android 10
- 測試內容:視頻加水印 vs 無水印處理
- 加水印幀率:15fps
- 視頻格式:H.264 (寬高:720x576)(位率:512Kbps)
性能對比數據
視頻時長 | 處理方式 | 耗時(ms) | 耗時(秒) | 輸出文件大小 |
---|---|---|---|---|
30s | 加水印 | 5214 | 5.21 | 1.5M |
30s | 加水印(15幀) | 3813 | 3.81 | 1.5M |
30s | 不加水印 | 168 | 0.17 | 0.9M |
60s | 加水印 | 12013 | 12.01 | 2.5M |
60s | 加水印(15幀) | 7392 | 7.39 | 2.5M |
60s | 不加水印 | 208 | 0.21 | 1.5M |
120s | 加水印 | 22006 | 22.01 | 5.03M |
120s | 加水印(15幀) | 18404 | 18.40 | 5.03M |
120s | 不加水印 | 277 | 0.28 | 2.8M |
240s | 加水印 | 50547 | 50.55 | 10.57M |
240s | 加水印(15幀) | 37857 | 37.86 | 10.57M |
240s | 不加水印 | 448 | 0.45 | 5.24M |
在 A133 平臺上,加水印操作是性能瓶頸,視頻重編碼操作對cpu要求比較高。
補充
在低端設備使用FFmpeg處理視頻顯然是不推薦的,更是不明智的,除非無可選擇,可以選擇的替代方式為在手機或者在服務器進行視頻處理操作,這樣速度和體驗感更好。
在手機處理發現 不設置 FFmpegKitConfig.setFontconfigConfigurationPath
也可以正常添加水印,這個操作應該是可有可無的!或者在特殊地方才會使用到它,有待發現。
開發中發現,只有full-gpl
庫,才能進行加水印重編碼操作,其他庫均不行,沒辦法,只有一個加水印操作也要引入全功能,apk包體積大概增加13M左右,而且這只是在應用只支持arm64-v8a
的情況下,多一個就翻一倍!
而且full-gpl:6.0-2
最低要求sdk 24,如果你的應用之前最小sdk 小于23,然后改為24時你會發現,包體積大小劇增,這是因為minSdkVersion >= 23
默認不壓縮so 大小 ,導致apk體積會變大,大于23 就在清單文件的application
標簽下設置 android:extractNativeLibs="true"
壓縮so ,就正常了。