2. CodecList 查询与选择器实现

本章深入讲解 Android 平台编解码器查询的核心入口 MediaCodecList 及其配套能力查询、匹配规则与降级策略。通过掌握底层 API 的行为边界和查询流程,开发者可以构建出能够应对碎片化设备的自适应编解码器选择器,避免硬编码导致的兼容性崩溃。

CodecList 查询与选择器工作流程 CodecList 查询与选择器工作流程 应用层请求 MIME / 分辨率 / 帧率 / 码率 MediaCodecList ALL_CODECS / REGULAR_CODECS 能力查询 枚举 CodecCapabilities 匹配规则 MIME / 分辨率 / 码率 / 帧率 降级策略 HW→SW / 分辨率降级 / 码率降级 最终编解码器选择 输出最优 Codec 实例 查询流程自上而下逐级过滤:请求 → 列表 → 能力 → 规则 → 降级 → 选型

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.");
    }
}

常用匹配规则的优先级顺序(从高到低):

  1. MIME 类型精确匹配 — 不同编解码器类型不可混用
  2. 分辨率支持 — 调用 isSizeSupported() 确认目标分辨率是否在能力范围内
  3. 帧率与分辨率组合支持 — 某些编解码器支持高分辨率但仅支持低帧率
  4. 码率范围 — 超出码率上限会导致编码器初始化失败
  5. 硬件/软件偏好 — 性能敏感场景优先硬件,兼容性优先软件

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_SOFTWARECPU 负载上升,发热增加是
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% 以下。