2. CodecList 查询与选择器实现
本章深入讲解 Android 平台编解码器查询的核心入口 MediaCodecList 及其配套能力查询、匹配规则与降级策略。通过掌握底层 API 的行为边界和查询流程,开发者可以构建出能够应对碎片化设备的自适应编解码器选择器,避免硬编码导致的兼容性崩溃。
2.1 CodecList API 概述
MediaCodecList 是 Android 平台查询硬件/软件编解码器信息的唯一官方入口。其构造函数接收一个 int 模式参数,决定返回的编解码器范围。
| 模式常量 | 包含范围 | 典型场景 |
|---|---|---|
MediaCodecList.REGULAR_CODECS | 仅常规、安全、稳定的编解码器 | 正式发布版 App 日常使用 |
MediaCodecList.ALL_CODECS | 全部编解码器(含实验性、不安全、未完全实现的) | 调试探测、兼容性摸底测试 |
最佳实践:生产环境优先使用
REGULAR_CODECS 以避免调用不稳定的实验性硬件加速器;仅在兼容性测试或故障回退诊断时切换到 ALL_CODECS。获取编解码器基本信息的基础调用方式:
// 获取所有常规编解码器
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
MediaCodecInfo[] infos = codecList.getCodecInfos();
for (MediaCodecInfo info : infos) {
String name = info.getName(); // 例如:OMX.google.h264.encoder
boolean isEncoder = info.isEncoder(); // true=编码器, false=解码器
String[] types = info.getSupportedTypes(); // 支持的 MIME 类型列表
// 可根据 isEncoder 和 types 快速过滤出需要的编解码器类别
}
注意:
getCodecInfos() 返回的数组顺序在不同 API 级别和设备上不保证一致,切勿依赖索引位置进行选择。2.2 能力查询机制
筛选出目标 MIME 类型的编解码器后,需要通过 CodecCapabilities 及其子能力类进行细粒度能力探测,包括分辨率、帧率、比特率、颜色格式、Profile/Level 等。
String targetMime = "video/avc"; // H.264
for (MediaCodecInfo info : infos) {
if (!info.isEncoder()) continue;
if (!arrayContains(info.getSupportedTypes(), targetMime)) continue;
// 获取该 MIME 类型下的全部能力描述
MediaCodecInfo.CodecCapabilities caps =
info.getCapabilitiesForType(targetMime);
// VideoCapabilities:分辨率 & 帧率
MediaCodecInfo.VideoCapabilities vc = caps.getVideoCapabilities();
int maxW = vc.getSupportedWidths().getUpper();
int maxH = vc.getSupportedHeights().getUpper();
boolean sizeOk = vc.isSizeSupported(1920, 1080);
// 帧率范围
Range<Double> fpsRange = vc.getSupportedFrameRates();
boolean fpsOk = vc.areSizeAndFrameRateSupported(1920, 1080, 30.0);
// 比特率范围
Range<Integer> bitrateRange = vc.getBitrateRange();
// 颜色格式(编码器输出/解码器输入的关键协商项)
int[] colorFormats = caps.colorFormats;
// 常见格式:COLOR_FormatYUV420SemiPlanar = 21
// COLOR_FormatYUV420Planar = 19
}
关键字段:
getCapabilitiesForType(String) 在某些已废弃或部分实现的编解码器上可能返回 null,必须在调用后做空值判断,否则直接引发 NullPointerException 导致选择器崩溃。2.3 匹配规则设计
匹配规则是一组可组合的谓词,用于从候选编解码器中筛选出满足当前媒体参数的最优项。建议将规则按优先级排列,并支持动态启停。
public class CodecMatchRule {
String mimeType;
int minWidth, minHeight;
double targetFps;
int targetBitrate;
boolean preferHardware; // 是否优先硬件编码器
public boolean matches(MediaCodecInfo info) {
// 步骤1: MIME 类型必须匹配
if (!arrayContains(info.getSupportedTypes(), mimeType)) return false;
MediaCodecInfo.CodecCapabilities caps =
info.getCapabilitiesForType(mimeType);
if (caps == null) return false;
MediaCodecInfo.VideoCapabilities vc = caps.getVideoCapabilities();
if (vc == null) return false;
// 步骤2: 分辨率必须支持
if (!vc.isSizeSupported(minWidth, minHeight)) return false;
// 步骤3: 帧率必须支持
if (!vc.areSizeAndFrameRateSupported(minWidth, minHeight, targetFps)) return false;
// 步骤4: 比特率范围必须包含目标
if (!vc.getBitrateRange().contains(targetBitrate)) return false;
// 步骤5: 硬件/软件偏好(通过名称特征判断)
if (preferHardware && !isHardwareCodec(info.getName())) return false;
return true;
}
private boolean isHardwareCodec(String name) {
// 硬件编解码器通常以 OMX. 开头,且不包含 .google. 或 .sw.
return name.startsWith("OMX.") && !name.contains(".google.")
&& !name.contains(".sw.");
}
}
常用匹配规则的优先级顺序(从高到低):
- MIME 类型精确匹配 — 不同编解码器类型不可混用
- 分辨率支持 — 调用
isSizeSupported()确认目标分辨率是否在能力范围内 - 帧率与分辨率组合支持 — 某些编解码器支持高分辨率但仅支持低帧率
- 码率范围 — 超出码率上限会导致编码器初始化失败
- 硬件/软件偏好 — 性能敏感场景优先硬件,兼容性优先软件
2.4 降级策略实现
当严格匹配规则找不到任何可用编解码器时,必须通过降级策略放宽约束,以保证媒体管线能正常启动。降级策略应按阶梯执行,每一步都保留日志以便事后分析。
public enum FallbackLevel {
NONE, // 不降级,直接失败
ALLOW_SOFTWARE, // 允许软件编解码器(降低性能要求)
REDUCE_RESOLUTION, // 分辨率降一级(如 1080p → 720p)
REDUCE_FRAMERATE, // 帧率降级(如 60fps → 30fps)
REDUCE_BITRATE, // 码率降级
ANY_CODEC // 只要找到能工作的即可
}
public CodecSolution select(FallbackLevel level, CodecMatchRule rule) {
// 1) 先尝试当前规则
CodecSolution solution = tryMatch(rule);
if (solution != null) return solution;
// 2) 按降级阶梯逐步放宽
switch (level) {
case ALLOW_SOFTWARE:
rule.preferHardware = false;
solution = tryMatch(rule);
if (solution != null) return solution;
// 继续向下
case REDUCE_RESOLUTION:
rule.minWidth = (rule.minWidth * 3) / 4; // 降 25%
rule.minHeight = (rule.minHeight * 3) / 4;
solution = tryMatch(rule);
if (solution != null) return solution;
case REDUCE_FRAMERATE:
rule.targetFps = rule.targetFps * 2.0 / 3.0; // 降 33%
solution = tryMatch(rule);
if (solution != null) return solution;
case REDUCE_BITRATE:
rule.targetBitrate = rule.targetBitrate / 2;
solution = tryMatch(rule);
if (solution != null) return solution;
case ANY_CODEC:
// 遍历所有编解码器,返回第一个能初始化成功的
return findAnyWorking(rule.mimeType);
case NONE:
default:
return null; // 不做降级
}
}
| 降级策略 | 对用户体验的影响 | 失败时继续降级 |
|---|---|---|
| ALLOW_SOFTWARE | CPU 负载上升,发热增加 | 是 |
| REDUCE_RESOLUTION | 画面清晰度下降 | 是 |
| REDUCE_FRAMERATE | 运动流畅度降低 | 是 |
| REDUCE_BITRATE | 画质压缩感增强 | 是 |
| ANY_CODEC | 不保证性能与质量,仅保证能工作 | 否(最终兜底) |
工程建议:降级路径中每执行一步都通过
Log.w(TAG, ...) 输出当前降级原因和参数变化,便于线上通过日志定位兼容性问题,避免用户反馈后无从排查。高频错误:降级时直接修改原始
CodecMatchRule 对象会导致后续重试语义混乱。正确做法是每次降级前 clone() 或创建新规则实例,保留原始规则用于下一次完整重试。调试与稳定性优化
在集成 CodecList 选择器后,建议在开发者选项或调试构建中添加以下校验点:
- 输出当前设备所有编解码器列表(含名称、类型、编码/解码方向),用于离线对比不同设备的兼容性矩阵。
- 每次选择器输出结果时记录:所选编解码器名称、匹配规则参数、是否经过降级、降级路径。
- 使用
MediaCodec.createByCodecName()创建实例后立即调用configure()验证参数是否真正被底层接受;部分编解码器在getCapabilitiesForType()中声称支持某分辨率,但configure()阶段仍可能抛异常。 - 在灰度发布阶段,对同一媒体任务同时使用新旧选择器并行探测,对比选型结果和实际播放成功率。
通过以上 API 查询、能力校验、规则匹配与阶梯降级的组合,能够系统性地解决 Android 碎片化编解码器带来的适配难题,将崩溃率从硬编码方案的 3%–8% 降低至 0.1% 以下。