rosu 的博客

如何判断萤石云视频是否可以播放

2018-09-26

最近遇到一个问题,需要判断视频文件是否是真正的视频文件。
什么意思呢?萤石的摄像头是将视频写入 TF 卡的:

通过萤石云视频平台将 TF 卡格式化后,程序会采用预占空间的方式预先将 1/4 的空间作为视频或者图片的存储空间。

然后他预写入的文件是 .mp4 后缀的,但是是不可播放的文件。所以一旦播放器播放它,可能就会出错了。为了避免这样的情况发生,我们能否在检索视频的时候就识别出无法播放的视频呢?

我一开始的思路是,能否通过判断文件类型的方式来判断是否播放呢?有可能他预格式化的视频文件,虽然后缀是.mp4,但是本质上不是一个视频文件呢?

判断文件类型

在 Java 中,比较常见的用来判断文件类型的库,就是 Apache Tika
引入依赖后,使用起来也十分简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
}
}

下面是输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

使用

我们引入依赖

1
implementation 'com.github.wseemann:FFmpegMediaMetadataRetriever:1.0.14'

然后开始使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 会抛出一个异常:

1
java.lang.IllegalArgumentException: setDataSource failed: status = 0xFFFFFFFF

然后我们捕获这个异常,做一些其他的工作就可以。
这个方法就目前的使用来看,是最稳定和准确的方法。
缺点是:

  • setDataSource()过程有一些耗时,实际上测试,256MB 的视频文件,调用一次需要将近 1s 的时间

  • 会抛异常…

还有其他办法吗?

最一开始想的,其实是开一个 VideoPlayer,然后在 onError() 设一个监听器,如果播放错误就说明该文件无效。
但是经过我自己的评估,这样的性能实际上更差。

也不知道是否有其他更优的方法,如果有的话,还请不吝赐教~


参看

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章