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 开始,获取轨道总数,然后循环遍历,根据 MIME 类型分流到不同的处理逻辑。你写代码时,脑子里就按这个流程走,不会乱。
4.7 避坑指南
最后,分享几个我踩过的坑,希望能帮你省点时间。
- KEY_FRAME_RATE 可能为 0 或负数。 有些文件不包含帧率信息,MediaFormat 里这个字段可能缺失或为 0。读取时记得做合法性校验。
- KEY_BIT_RATE 不是所有轨道都有。 尤其是音频轨道,很多编码格式(如 AAC)的比特率是动态的,MediaFormat 里可能不存这个值。用
containsKey()检查一下再读。 - 语言信息可能为空。 字幕轨道的
KEY_LANGUAGE字段,如果文件没写,返回的就是 null。别直接拿它去拼接字符串,会变成 "null"。 - 有些轨道是“空轨道”。 我遇到过一种情况:轨道数量是 3,但第三条轨道的 MediaFormat 里只有 MIME 类型,其他字段全为空。这种轨道可能是损坏的或未完成的。建议在读取关键字段时加 try-catch,防止崩溃。
好了,这一节的内容就到这里。掌握了轨道信息的获取,你就相当于拿到了媒体文件的“地图”。下一节,我们会拿着这张地图,去实际提取数据——也就是把音视频数据从文件里读出来。