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_ROTATIONKEY_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 兜底。

知识体系总览

下面这张图,是我自己梳理的元数据提取流程。每次带新人时,我都会先让他们看这张图,心里有个全局概念。

MediaExtractor 元数据提取流程 MediaExtractor 遍历所有轨道 (getTrackCount) 视频轨道 (video/) 音频轨道 (audio/) 视频元数据 • 分辨率 (Width/Height) • 时长 (Duration) • 比特率 / 帧率 / 旋转角度 音频元数据 • 采样率 (Sample Rate) • 声道数 (Channel Count) • 时长 / 比特率

避坑指南:我踩过的几个雷

做元数据提取,看起来简单,但实际项目中坑不少。我把自己踩过的几个雷分享给你:

  • KEY_DURATION 的单位是微秒:很多人以为是毫秒,直接除以 1000 就对了。但如果你要显示给用户看,记得再除以 1000 转成秒。
  • 某些 MP4 文件没有 KEY_BIT_RATE:这时候可以自己算:文件大小(字节)* 8 / 时长(秒)。
  • 旋转角度只存在于视频轨道:别去音频轨道里找,找不到的。
  • 帧率字段缺失时不要硬取:用 containsKey 检查一下,或者像我前面那样用时间戳估算。

个人建议:把元数据提取封装成一个工具类,传入文件路径,返回一个包含所有字段的 DataClass。这样不管在哪个页面要用,一行代码就能搞定。

好了,关于元数据提取,核心内容就这些。你只要把 MediaExtractor 的轨道遍历和 MediaFormat 的键值对搞明白,后面做视频裁剪、转码、缩略图生成,都会顺手很多。

公众号:蓝海资料掘金营,微信deep3321