21、提取元数据:获取时长、比特率、帧率、旋转角度等信息
做音视频开发这几年,我越来越觉得——元数据才是视频文件的"身份证"。
你想想看,一个视频文件摆在你面前,时长多少?分辨率多少?比特率多高?旋转角度对不对?这些信息如果不提前拿到手,后面做裁剪、转码、播放适配,全都得踩坑。
我个人习惯是,拿到一个媒体文件,第一件事就是先把它的元数据扒干净。就像做菜前先看食材保质期一样,心里有底,后面才敢放手干。
MediaExtractor 能拿到哪些元数据?
MediaExtractor 这个类,说白了就是 Android 系统给我们开的一扇窗。通过它,我们能窥见媒体文件内部的各种属性。
常用的元数据包括:
- 时长(Duration):视频总长度,单位微秒
- 比特率(Bitrate):每秒数据量,单位 bps
- 帧率(Frame Rate):每秒多少帧画面
- 旋转角度(Rotation):视频拍摄时的设备方向
- 分辨率(Width/Height):视频宽高像素值
- 编码格式(MIME Type):如 video/avc、audio/mp4a-latm
这些信息都藏在 MediaFormat 对象里。我们只需要从 MediaExtractor 中拿到轨道,再调用 getTrackFormat() 就能读取。
核心代码:一行一行扒元数据
先看一个完整的示例。这段代码我反复用过很多次,基本覆盖了日常开发中 90% 的需求。
MediaExtractor extractor = new MediaExtractor();
try {
extractor.setDataSource(videoPath);
int trackCount = extractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("video/")) {
// 视频轨道
int width = format.getInteger(MediaFormat.KEY_WIDTH);
int height = format.getInteger(MediaFormat.KEY_HEIGHT);
long duration = format.getLong(MediaFormat.KEY_DURATION);
int bitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
int rotation = 0;
if (format.containsKey(MediaFormat.KEY_ROTATION)) {
rotation = format.getInteger(MediaFormat.KEY_ROTATION);
}
float frameRate = 0;
if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
frameRate = format.getFloat(MediaFormat.KEY_FRAME_RATE);
}
Log.d("Metadata", "视频轨道 " + i + ":");
Log.d("Metadata", " 分辨率: " + width + "x" + height);
Log.d("Metadata", " 时长: " + (duration / 1000) + " ms");
Log.d("Metadata", " 比特率: " + bitrate + " bps");
Log.d("Metadata", " 旋转角度: " + rotation + "°");
Log.d("Metadata", " 帧率: " + frameRate + " fps");
} else if (mime.startsWith("audio/")) {
// 音频轨道
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
long duration = format.getLong(MediaFormat.KEY_DURATION);
int bitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
Log.d("Metadata", "音频轨道 " + i + ":");
Log.d("Metadata", " 采样率: " + sampleRate + " Hz");
Log.d("Metadata", " 声道数: " + channelCount);
Log.d("Metadata", " 时长: " + (duration / 1000) + " ms");
Log.d("Metadata", " 比特率: " + bitrate + " bps");
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
extractor.release();
}
这段代码的逻辑其实很简单:遍历所有轨道,根据 MIME 类型区分视频和音频,然后分别提取对应的元数据。
关键点:KEY_ROTATION 和 KEY_FRAME_RATE 并不是所有视频文件都包含。调用 containsKey() 检查一下再取值,能避免很多崩溃。
旋转角度:一个容易被忽略的坑
我记得有一次,一个同事反馈说视频播放出来是倒着的。排查了半天,发现是手机竖屏拍摄的视频,但播放器没有读取旋转角度。
旋转角度通常存储在视频轨道的元数据中,值一般是 0、90、180、270。它表示视频画面需要顺时针旋转多少度才能正常显示。
举个例子:
- 手机横屏拍摄 → 旋转角度 0°
- 手机竖屏拍摄 → 旋转角度 90° 或 270°
- 手机倒置拍摄 → 旋转角度 180°
我曾经在做一个相册应用时,就因为这个旋转角度没处理好,导致用户上传的视频在列表里全是歪的。后来加了一行读取旋转角度的逻辑,问题就解决了。
小技巧:如果你用 MediaCodec 做视频编解码,记得把旋转角度传给 Surface 或者渲染器。否则画面方向会错乱。
帧率:不是所有视频都有这个字段
帧率这个字段,说实话有点"玄学"。很多 MP4 文件在封装时,并没有把帧率写入元数据。这时候 KEY_FRAME_RATE 就取不到值。
那怎么办?
我一般会退而求其次,用另一种方式估算帧率:
// 估算帧率:取前两个关键帧的时间戳差值
extractor.selectTrack(videoTrackIndex);
extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
long firstPts = 0;
long secondPts = 0;
int frameCount = 0;
while (frameCount < 2) {
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
int sampleSize = extractor.readSampleData(buffer, 0);
if (sampleSize < 0) break;
long pts = extractor.getSampleTime();
if (frameCount == 0) {
firstPts = pts;
} else if (frameCount == 1) {
secondPts = pts;
}
frameCount++;
extractor.advance();
}
if (secondPts > firstPts) {
float estimatedFps = 1000000f / (secondPts - firstPts);
Log.d("Metadata", "估算帧率: " + estimatedFps + " fps");
}
这个方法虽然不够精确,但在大多数场景下已经够用了。你想想看,用户上传的视频,帧率差个一两帧,肉眼根本看不出来。
比特率:决定视频质量的"硬指标"
比特率这个值,直接决定了视频的清晰度和文件大小。比特率越高,画质越好,但文件也越大。
常见的视频比特率参考:
| 视频质量 | 分辨率 | 比特率(约) |
|---|---|---|
| 标清 | 480p | 1-2 Mbps |
| 高清 | 720p | 3-5 Mbps |
| 全高清 | 1080p | 8-15 Mbps |
| 4K | 2160p | 35-50 Mbps |
我在项目中遇到过一个问题:用户上传了一个比特率极低的视频,播放时全是马赛克。后来我们在上传前加了一道检测,如果比特率低于某个阈值,就提示用户视频质量可能不佳。
注意:比特率这个字段,有些编码器在封装时可能不写入。如果 getInteger(KEY_BIT_RATE) 抛异常,记得用 try-catch 兜底。
知识体系总览
下面这张图,是我自己梳理的元数据提取流程。每次带新人时,我都会先让他们看这张图,心里有个全局概念。
避坑指南:我踩过的几个雷
做元数据提取,看起来简单,但实际项目中坑不少。我把自己踩过的几个雷分享给你:
- KEY_DURATION 的单位是微秒:很多人以为是毫秒,直接除以 1000 就对了。但如果你要显示给用户看,记得再除以 1000 转成秒。
- 某些 MP4 文件没有 KEY_BIT_RATE:这时候可以自己算:文件大小(字节)* 8 / 时长(秒)。
- 旋转角度只存在于视频轨道:别去音频轨道里找,找不到的。
- 帧率字段缺失时不要硬取:用 containsKey 检查一下,或者像我前面那样用时间戳估算。
个人建议:把元数据提取封装成一个工具类,传入文件路径,返回一个包含所有字段的 DataClass。这样不管在哪个页面要用,一行代码就能搞定。
好了,关于元数据提取,核心内容就这些。你只要把 MediaExtractor 的轨道遍历和 MediaFormat 的键值对搞明白,后面做视频裁剪、转码、缩略图生成,都会顺手很多。