4. 轨道信息获取:获取视频轨道、音频轨道、字幕轨道的详细信息

好,咱们接着聊。上一节我们把 MediaExtractor 初始化好了,文件也打开了。但打开文件只是第一步,就像你拿到一本书,总得先看看目录,知道这本书有几章、每章讲什么,对吧?

这一节,我们就来干这个事——遍历并获取文件里所有轨道的详细信息。说白了,就是搞清楚这个媒体文件里到底藏着哪些“宝贝”:视频流、音频流、甚至字幕流,以及它们各自是什么编码、什么参数。

4.1 轨道数量与类型判断

MediaExtractor 提供了一个非常直接的方法:getTrackCount()。调用它,你就知道总共有几条轨道。

然后,对于每一条轨道,我们用 getTrackFormat(int index) 拿到一个 MediaFormat 对象。这个对象里,就存着这条轨道的所有“身份证信息”。

怎么判断它是视频还是音频呢?看 MediaFormat.KEY_MIME 这个键。MIME 类型以 "video/" 开头就是视频,以 "audio/" 开头就是音频,以 "text/" 开头就是字幕。

核心思路: 遍历索引 → 获取 MediaFormat → 读取 MIME 类型 → 分类处理。

我个人习惯,在拿到 MediaFormat 后,先打印一下所有键值对,看看里面到底藏了哪些信息。Android 的 MediaFormat 其实是个 Map,你可以用 toString() 直接看全貌。这在调试时特别有用。

int trackCount = extractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
    MediaFormat format = extractor.getTrackFormat(i);
    String mime = format.getString(MediaFormat.KEY_MIME);
    Log.d("TrackInfo", "Track " + i + ": " + mime);
    Log.d("TrackInfo", "Full format: " + format);
}

嗯,这里要注意:getTrackFormat() 返回的 MediaFormat 是只读的,你不能修改它。不过我们也不需要修改,看看就好。

4.2 视频轨道详细信息

视频轨道的信息相对丰富。除了 MIME 类型,我们最关心的就是分辨率、帧率、比特率这些参数。

我记得有一次,我在处理一个用户上传的视频,分辨率是 4K 的,但帧率标称 60fps,实际解码时发现只有 30fps。后来一查,是 MediaFormat 里的帧率字段写错了。所以,不要完全相信 MediaFormat 里的帧率值,它有时候是“建议值”,不是实际值。

常用的视频轨道 Key 有这些:

Key 常量 含义 示例值
KEY_WIDTH 视频宽度(像素) 1920
KEY_HEIGHT 视频高度(像素) 1080
KEY_FRAME_RATE 帧率(fps) 30
KEY_BIT_RATE 比特率(bps) 2000000
KEY_COLOR_FORMAT 颜色格式(整数) 21 (OMX_COLOR_FormatYUV420SemiPlanar)
KEY_ROTATION 旋转角度(度) 90

小技巧: 有些视频文件会包含旋转信息(比如手机竖屏拍的视频)。如果你直接按原始宽高播放,画面可能是倒的。记得读取 KEY_ROTATION,在显示或编码时做相应处理。

if (mime.startsWith("video/")) {
    int width = format.getInteger(MediaFormat.KEY_WIDTH);
    int height = format.getInteger(MediaFormat.KEY_HEIGHT);
    int frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
    int bitRate = format.getInteger(MediaFormat.KEY_BIT_RATE);
    int rotation = format.getInteger(MediaFormat.KEY_ROTATION);
    
    Log.d("VideoTrack", "分辨率: " + width + "x" + height);
    Log.d("VideoTrack", "帧率: " + frameRate + "fps");
    Log.d("VideoTrack", "比特率: " + bitRate / 1000 + "kbps");
    Log.d("VideoTrack", "旋转: " + rotation + "°");
}

4.3 音频轨道详细信息

音频轨道的信息相对简单一些。采样率、声道数、比特率,这三个是核心。

我曾经遇到过一个坑:某个音频文件的 KEY_CHANNEL_COUNT 返回的是 6,但实际解码出来的 PCM 数据只有 2 个声道。后来发现是文件头信息写错了。所以,音频的声道数,最好在解码出数据后再确认一下,不要完全依赖 MediaFormat。

Key 常量 含义 示例值
KEY_SAMPLE_RATE 采样率(Hz) 44100
KEY_CHANNEL_COUNT 声道数 2
KEY_BIT_RATE 比特率(bps) 128000
KEY_IS_ADTS 是否为 ADTS 格式(AAC) 1
if (mime.startsWith("audio/")) {
    int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
    int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
    int bitRate = format.getInteger(MediaFormat.KEY_BIT_RATE);
    
    Log.d("AudioTrack", "采样率: " + sampleRate + "Hz");
    Log.d("AudioTrack", "声道数: " + channelCount);
    Log.d("AudioTrack", "比特率: " + bitRate / 1000 + "kbps");
}

4.4 字幕轨道与其它信息

字幕轨道在 Android 中相对少见,但 MediaExtractor 是支持提取的。字幕轨道的 MIME 类型通常是 "text/" 开头,比如 "text/vtt"(WebVTT)或 "text/plain"

字幕轨道的信息比较少,通常只有 MIME 类型和语言。语言信息通过 KEY_LANGUAGE 获取。

if (mime.startsWith("text/")) {
    String language = format.getString(MediaFormat.KEY_LANGUAGE);
    Log.d("SubtitleTrack", "MIME: " + mime + ", 语言: " + language);
}

除了视频、音频、字幕,有些文件还可能有数据轨道(比如时间码、元数据)。它们的 MIME 类型可能是 "application/" 开头。这种情况比较少见,遇到了按需处理即可。

4.5 完整示例:打印所有轨道信息

下面是一个完整的函数,它会遍历所有轨道,并把详细信息打印出来。你可以在自己的项目里直接复用。

public void printAllTrackInfo(MediaExtractor extractor) {
    int trackCount = extractor.getTrackCount();
    Log.d("TrackInfo", "总轨道数: " + trackCount);
    
    for (int i = 0; i < trackCount; i++) {
        MediaFormat format = extractor.getTrackFormat(i);
        String mime = format.getString(MediaFormat.KEY_MIME);
        
        Log.d("TrackInfo", "--- 轨道 " + i + " ---");
        Log.d("TrackInfo", "MIME: " + mime);
        
        if (mime.startsWith("video/")) {
            int width = format.getInteger(MediaFormat.KEY_WIDTH);
            int height = format.getInteger(MediaFormat.KEY_HEIGHT);
            int frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
            int bitRate = format.containsKey(MediaFormat.KEY_BIT_RATE) 
                          ? format.getInteger(MediaFormat.KEY_BIT_RATE) : -1;
            int rotation = format.containsKey(MediaFormat.KEY_ROTATION) 
                          ? format.getInteger(MediaFormat.KEY_ROTATION) : 0;
            
            Log.d("TrackInfo", "类型: 视频");
            Log.d("TrackInfo", "分辨率: " + width + "x" + height);
            Log.d("TrackInfo", "帧率: " + frameRate + "fps");
            Log.d("TrackInfo", "比特率: " + (bitRate > 0 ? bitRate / 1000 + "kbps" : "未知"));
            Log.d("TrackInfo", "旋转: " + rotation + "°");
            
        } else if (mime.startsWith("audio/")) {
            int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
            int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
            int bitRate = format.containsKey(MediaFormat.KEY_BIT_RATE) 
                          ? format.getInteger(MediaFormat.KEY_BIT_RATE) : -1;
            
            Log.d("TrackInfo", "类型: 音频");
            Log.d("TrackInfo", "采样率: " + sampleRate + "Hz");
            Log.d("TrackInfo", "声道数: " + channelCount);
            Log.d("TrackInfo", "比特率: " + (bitRate > 0 ? bitRate / 1000 + "kbps" : "未知"));
            
        } else if (mime.startsWith("text/")) {
            String language = format.getString(MediaFormat.KEY_LANGUAGE);
            Log.d("TrackInfo", "类型: 字幕");
            Log.d("TrackInfo", "语言: " + (language != null ? language : "未知"));
            
        } else {
            Log.d("TrackInfo", "类型: 其他 (" + mime + ")");
        }
    }
}

注意: 有些 Key 不一定存在于 MediaFormat 中。比如 KEY_BIT_RATE 在部分音频文件中可能缺失。读取前最好用 containsKey() 检查一下,否则直接 getInteger() 会抛异常。

4.6 知识体系总览

为了让你更直观地理解这一节的内容,我画了一张图。它展示了从 MediaExtractor 到各轨道信息的完整流程。

轨道信息获取流程 MediaExtractor getTrackCount() for i in 0..trackCount-1 视频轨道 宽/高/帧率/比特率 音频轨道 采样率/声道数/比特率 字幕轨道 MIME/语言

这张图把整个流程串起来了:从 MediaExtractor 开始,获取轨道总数,然后循环遍历,根据 MIME 类型分流到不同的处理逻辑。你写代码时,脑子里就按这个流程走,不会乱。

4.7 避坑指南

最后,分享几个我踩过的坑,希望能帮你省点时间。

  • KEY_FRAME_RATE 可能为 0 或负数。 有些文件不包含帧率信息,MediaFormat 里这个字段可能缺失或为 0。读取时记得做合法性校验。
  • KEY_BIT_RATE 不是所有轨道都有。 尤其是音频轨道,很多编码格式(如 AAC)的比特率是动态的,MediaFormat 里可能不存这个值。用 containsKey() 检查一下再读。
  • 语言信息可能为空。 字幕轨道的 KEY_LANGUAGE 字段,如果文件没写,返回的就是 null。别直接拿它去拼接字符串,会变成 "null"。
  • 有些轨道是“空轨道”。 我遇到过一种情况:轨道数量是 3,但第三条轨道的 MediaFormat 里只有 MIME 类型,其他字段全为空。这种轨道可能是损坏的或未完成的。建议在读取关键字段时加 try-catch,防止崩溃。

好了,这一节的内容就到这里。掌握了轨道信息的获取,你就相当于拿到了媒体文件的“地图”。下一节,我们会拿着这张地图,去实际提取数据——也就是把音视频数据从文件里读出来。


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