Android 如何判斷螢石雲視訊是否可以播放

rosuH發表於2018-09-27

文章發表於我的部落格如何判斷螢石雲視訊是否可以播放

最近遇到一個問題,需要判斷視訊檔案是否是真正的視訊檔案。 什麼意思呢?螢石的攝像頭是將視訊寫入 TF 卡的:

通過螢石雲視訊平臺將TF卡格式化後,程式會採用預佔空間的方式預先將1/4的空間作為視訊或者圖片的儲存空間。

然後他預寫入的檔案是.mp4字尾的,但是是不可播放的檔案。所以一旦播放器播放它,可能就會出錯了。為了避免這樣的情況發生,我們能否在檢索視訊的時候就識別出無法播放的視訊呢?

我一開始的思路是,能否通過判斷檔案型別的方式來判斷是否播放呢?有可能他預格式化的視訊檔案,雖然字尾是.mp4,但是本質上不是一個視訊檔案呢?

判斷檔案型別

在 Java 中,比較常見的用來判斷檔案型別的庫,就是Apache Tika。 引入依賴後,使用起來也十分簡單:

File file = new File(System.getProperty("user.home")+ "/Downloads/");
        if (file.exists()){
            for (File file1 : Objects.requireNonNull(file.listFiles())){
                if (file1.isDirectory()){
                    continue;
                }
                try {
                    String type = new Tika().detect(file1);
                    System.out.println(file1.getName() + " ======>>> " + type);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
複製程式碼

下面是輸出結果:

background.svg ======>>> image/svg+xml
new.json ======>>> application/json
mid_top_1.jpg ======>>> image/jpeg
.DS_Store ======>>> application/octet-stream
apache-maven-3.5.4-bin.tar.1.gz ======>>> application/x-gzip
這本來是張 png 圖片.mp4 ======>>> image/png
.localized ======>>> application/octet-stream
old.json ======>>> application/json
不學無數 — Java 中 IO 和 NIO - 掘金.pdf ======>>> application/pdf
Layout_Mobile_Whiteframe.ai ======>>> application/pdf
Snipaste_2018-09-22_22-36-21.png ======>>> image/png
background.png ======>>> image/png
hiv00014.mp4 ======>>> video/mp4
SeimiCrawler-master.1.zip ======>>> application/zip
cn_windows_10_business_editions_version_1803_updated_march_2018_x64_dvd_12063730.iso ======>>> application/x-iso9660-image
i_con_permission.png ======>>> image/png
錯誤.png ======>>> image/png
open_gapps-arm64-9.0-pico-20180923.zip ======>>> application/zip
forPush.sh ======>>> application/x-sh
adbIn.sh ======>>> application/x-sh
Havoc-OS-v2.0-20180923-oneplus3-Official.zip ======>>> application/zip
MockingBot.dmg ======>>> application/octet-stream
複製程式碼

可以看到,幾乎全部格式都識別出來了。而且我這裡有一個『渾水摸魚』的『傢伙』,那就是這本來是張 png 圖片.mp4,人如其名。 這個也識別出來了(octet-stream指的是二進位制檔案)。 那 Tika 究竟是怎麼做的呢?讓我們來看看原始碼唄。 篇幅所限,如果你對這個部分有興趣,可以移步我的另一篇文章Tika 原始碼淺析

這裡假設你已經看過了 Tika 原始碼...

現在我們知道了 Tika 通過檔案的首部位元組、檔案字尾判斷檔案的型別。但是這樣依舊無法判斷視訊是否可以播放。 因為在實際使用中,我發現,有一些個視訊檔案,雖然是無法播放的,但是它們的首部已經被寫入了,成為另一個『空視訊檔案』。 真的蛋疼。如果單純使用 Tika 的話,顯然會有誤差。 那麼有沒有其他應用層面的 trick 呢?

答案就是 FFmpegMediaMetadataRetriever

FFmpegMediaMetadataRetriever 是什麼?

之所以說是 trick...是因為 FFmpegMediaMetadataRetriever 是一個獲取媒體檔案資訊的庫。 我在使用時發現,如果一個視訊只被寫了頭部,但是沒有實際內容的話,該視訊是沒有編碼資訊的。 這個想法是來自MediaInfo這個軟體。因為我是先用這個軟體測試了一遍,發現是可行的。 然後我找到了在 Android 平臺可用的一個類似的庫,也就是 FFmpegMediaMetadataRetriever

使用

我們引入依賴

implementation 'com.github.wseemann:FFmpegMediaMetadataRetriever:1.0.14'
複製程式碼

然後開始使用:

public static List<String> getAvailableVideoList(String SDCardPath){
    if (TextUtils.isEmpty(SDCardPath)){
        return null;
    }

    List<String> pathList = new ArrayList<>();
    FFmpegMediaMetadataRetriever mediaMetadataRetriever = new FFmpegMediaMetadataRetriever();
    File fileList = new File(SDCardPath);
    if (fileList.exists()) {
        try {
            for (File file : fileList.listFiles()) {
                if (file.getName().endsWith("mp4")){
                    mediaMetadataRetriever.setDataSource(file.getPath());
                    mediaMetadataRetriever.extractMetadata(METADATA_KEY_VIDEO_CODEC);
                    pathList.add(file.getPath());
                }
            }
        } catch (IllegalArgumentException iae) {
            iae.printStackTrace();
        }
    }
    return pathList;
}
複製程式碼

如果視訊檔案無效的,FFmpegMediaMetadataRetriever 會丟擲一個異常:

java.lang.IllegalArgumentException: setDataSource failed: status = 0xFFFFFFFF
複製程式碼

然後我們捕獲這個異常,做一些其他的工作就可以。 這個方法就目前的使用來看,是最穩定和準確的方法。 缺點是:

  • setDataSource()過程有一些耗時,實際上測試,256MB 的視訊檔案,呼叫一次需要將近 1s 的時間

  • 會拋異常...

還有其他辦法嗎?

最一開始想的,其實是開一個 VideoPlayer,然後在onError()設一個監聽器,如果播放錯誤就說明該檔案無效。 但是經過我自己的評估,這樣的效能實際上更差。

也不知道是否有其他更優的方法,如果有的話,還請不吝賜教~


參看

相關文章