還是老規矩先上效果圖:
GIF.gif
一、使用 FFmpegMediaMetadataRetriever 初步解決問題
Android 原生的 SDK 提供的 api 中并沒有提供在線獲取縮略圖的接口(只有本地的)
本地獲取音視頻縮略圖代碼(溫習一下):
private InputStream getAudioInPutream(String imageUri) throws IOException {
InputStream ret = null;
MediaMetadataRetriever fmmr;
try {
fmmr = new MediaMetadataRetriever();
fmmr.setDataSource(imageUri);
//先獲取封面
byte[] embedPic = fmmr.getEmbeddedPicture();
ret = new ByteArrayInputStream(embedPic);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if(fmmr != null) {
fmmr.release();
}
}
if (ret != null) {
return ret;
} else {
throw new IOException("Request video thumb failed by ffmpeg");
}
}
靈機一動的我,好像 ffmpeg 可以獲取音視頻縮略圖。 趕緊找找有沒有人這么干了,結果還真有在 github 上找到一個 開源項目
https://github.com/wseemann/FFmpegMediaMetadataRetriever
大神終究還是大神。666。好了廢話不說直接用起來:
private Bitmap getVideoThumBitmap(String imageUri) throws IOException {
ImageInputStream ret = null;
Bitmap ret = null;
try {
fmmr = new FFmpegMediaMetadataRetriever();
fmmr.setDataSource(imageUri);
//獲取視頻的時長
String value = fmmr.extractMetadata(FFmpegMediaMetadataRetriever.METADATA_KEY_DURATION);
long frameTime = -1;
if (value != null) {
try {
frameTime = Integer.parseInt(value);
} catch (NumberFormatException e) {
}
}
if (frameTime > 0) {
frameTime = (frameTime / 2) * 1000;
} else {
frameTime = 4000000;
}
//獲取視頻縮略圖
ret = fmmr.getFrameAtTime(frameTime, FFmpegMediaMetadataRetriever.OPTION_CLOSEST_SYNC)
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
if (fmmr != null) {
fmmr.release();
}
} catch (Throwable e) {
}
}
return ret;
}
用的感覺還不錯。過了幾天老板把我叫過去懟了一頓,加了這視頻縮略圖 app 用著用著怎么閃退了。而且還怎么慢,切換界面還不能取消之前請求...(心中一千萬個草擬嗎奔騰而過..)
好吧來看看 log,尼瑪 ffmpeg 的 log 一點也沒有打印(原因是編譯so庫的時候 沒有--enable-debug 因為不加 log 可以減少 so 的體積)... 好吧看來這個庫還是不完美,還是的自己填上這個坑。看了一下作者這個庫,ffmpeg 庫已經三年沒有更新了在這日新月異科技迅猛發達的時代,這是大件事.. 意思就是要我編譯一個 最新的版本的 ffmpeg 換上?
二、優化 FFmpegMediaMetadataRetriever —— 替換新版本 ffmpeg
來吧動手起來,先到官網下載最新的
ffmpeg 源碼 ,
搭建 VMware + Ubuntu 環境(這個網上自己找下教程),下載 linux 平臺下的 ndk。 搞定!!
接下來就是寫好編譯腳本放 ffmpeg 源碼的根目錄,腳本語言 :build_android.sh
代碼 。
接下來編譯:
- ctrl+alt+t 打開
ubuntu
終端 - cd 到
build_android.sh
文件所在的目錄 - sudo ./build_android.sh (此處用超級權限去執行)
- 等3分鐘..
- 編譯結束
- make
- sudo make install (打包 so 和頭文件.h )
好了,居然出現錯誤(尼瑪我這是官網的代碼還出錯) :
libavcodec/hevc_mvs.c: In function 'derive_spatial_merge_candidates':
libavcodec/hevc_mvs.c:368:23: error: 'y0000000' undeclared (first use in this function)
libavcodec/hevc_mvs.c:368:23: note: each undeclared identifier is reported only once for each function it appears in
libavcodec/hevc_mvs.c:368:23: error: 'x0000000' undeclared (first use in this function)
其實是宏定義的問題,解決方法:
這個文件中 ffmpeg\libavcodec\hevc_mvs.c 里面
TAB_MVF_PU(B0)
換成TAB_MVF_PU(0)
為什么呢?因為編譯的時候,B0 = 0000000 通過
TAB_MVF_PU
宏定義里面用了連接符##
,得出的變量名為x0000000
或者y0000000
,其實 ffmepg 團隊寫這代碼想要的是x0
和y0
這兩個變量名。
好了解決,后面有這個問題直接按照上面的方法 把B0
換成 0 即可。好了so 出來了
廢了我大半天功夫,終于搞出來了。同一份 so 只要名字最長的那個,因為最后的鏈接都是鏈接到名字最長的那個 so,重命名 libavcodec.so.57.107.100
為 libavcodec.so
(后面三個 so 同樣去掉版本號) 然后替換到原工程FFmpegMediaMetadataRetriever
相同位置下,以及把 include
文件夾替換掉,好了一跑,果然不閃退了,視頻縮略圖獲取更加快了。新版本果然優化了很多東西,至于是什么,后面有空再去研究..
三、優化 FFmpegMediaMetadataRetriever —— 加入中斷機制
終于替換掉了舊版本 ffmpeg,可是還是無法中斷縮略圖的獲取,原項目里面沒有 取消的縮略圖的 api ,畢竟視頻獲取縮略圖還是挺耗時的,因為要去解析幀數據。如果大量請求 視頻縮略圖,切換界面后,或者在一個 RecyclerView
快速上拉又不能取消請求。只能等著前面的縮略圖一個個加載出來,才能加載到當前界面,這體驗簡直不能忍。
為了解決這問題,就要去修改原作者的 jni 代碼 。動起手來!!
找到耗時的接口:
- avformat_open_input // 超級耗時接口,有時候能耗時10s+
- avformat_find_stream_info // 第二耗時接口
- av_read_frame // 第三耗時接口
java 層加入取消接口:
nativeCancelRequest();
接口具體的實現是通過:
1. 設置一個結構體
typedef struct {
time_t lasttime; //記錄請求開始當下時間
int timeout; //超時時間 單位 秒
int interrupt; //1 表示退出請求,0 繼續請求
} Runner;
2.通過 ffmpeg
回調的接口,不斷訪問 Runner
是否需要取消
// 回調函數
int interruptCallback(void *p) {
Runner *r = (Runner *) p;
if (r->lasttime > 0) {
if ((time(NULL) - r->lasttime > r->timeout)
|| (r->interrupt == 1)) {
return 1;
}
}
return 0;
}
state->pFormatCtx->interrupt_callback.callback = interruptCallback;