第 3 章 设备兼容性矩阵与适配策略
3.1 为什么需要设备兼容性矩阵
Android 多媒体管线中的 Surface 边界既受 AOSP 框架版本制约,又被厂商定制行为深度影响。任何无法在版本与厂商间正确映射的适配策略都会导致黑屏、花屏、绿屏或 SurfaceFlinger 死锁。兼容性矩阵是适配工作的出发点:它把 API 版本差异、厂商定制行为 和 common/vendor 层边界 对齐到一个可执行的决策表上。
3.2 不同 Android 版本 API 差异(关键边界)
以下表格列出影响 Surface 分配的 API 级别变化,这些差异直接影响 MediaCodec / MediaPlayer / SurfaceView / TextureView 的选择策略。
| API 级别 | Android 版本 | 关键变更点 | Surface 边界影响 |
|---|---|---|---|
| 23 (M) | 6.0 | MediaCodec.createPersistentInputSurface() 引入 |
准备持久输入 Surface,但部分 OEM 实现不完整 |
| 26 (Oreo) | 8.0 | Surface.getBuffersSize() 废弃;getAvailableImages() 替代 |
厂商需更新 Gralloc 实现,否则 BufferQueue 计数错误 |
| 29 (Q) | 10 | MediaCodec.getOutputSurface() 改为 createInputSurface() 同步模型 |
部分华为 EMUI 10 仍使用旧异步模型,导致 dequeueOutputBuffer 超时 |
| 30 (R) | 11 | SurfaceControl 引入;SurfaceView 必须通过 WindowManager 创建 |
OEM 自定义 Launcher 若跳过 SurfaceControl 会引发 BufferQueue 泄漏 |
| 31 (S) | 12 | MediaCodec 新增 PARAMETER_KEY_SUSPEND;Surface 必须支持多缓冲 |
小米 MIUI 13 的 SurfaceFlinger 对 SUSPEND 支持不完整 |
| 33 (T) | 13 | SurfaceView.setSecure() 改为 setProtectedContent();DRM 路径变化 |
OV 定制 ROM 的 Secure Surface 层次与 AOSP 不一致 |
适配策略提示: 不要仅依赖
Build.VERSION.SDK_INT 条件判断;必须结合 Build.MANUFACTURER 与 Build.BRAND 进行二次路由,因为同一 API 级别在不同厂商的实现差异可能大于跨版本差异。
3.3 主流厂商定制行为矩阵(华为/小米/OV/三星)
以下四家 OEM 的定制行为具体到 Surface 层的差异记录在 /vendor/etc/ 和 /system/etc/ 配置中。
| 厂商 | 典型定制行为 | 导致问题 | 解决策略 |
|---|---|---|---|
| 华为 (EMUI/HarmonyOS) |
|
dequeueInputBuffer 返回 -1 时,应用进入死循环 |
在 configure 前设置 MediaFormat.KEY_MAX_INPUT_SIZE 为预估帧大小 |
| 小米 (MIUI/HyperOS) |
|
播放非 DRM 内容时崩溃:java.lang.IllegalStateException: drm not supported |
在 configure 之前捕获 MediaCodec.CodecException 并 fallback 到 MediaPlayer |
| OPPO/vivo (ColorOS/OriginOS) |
|
在弱光/低电量场景下 SurfaceTexture.updateTexImage 频繁超时 |
降低 setDefaultBufferSize 的宽高到 1080p 以内,并使用 GLSurfaceView 替代 TextureView |
| 三星 (One UI) |
|
宽高不是 16 倍数时渲染黑屏 | MediaCodec 配置时计算 width=ceil(width/16)*16 |
3.4 Common 层与 Vendor 层边界
Android 多媒体管线中的 Surface 边界落在 common 层(AOSP 框架) 与 vendor 层(HAL + OEM 驱动) 之间。
- common 层:
android.hardware.graphics.composer(HWC) 的通用实现、libstagefright解码器、android.media.MediaCodecJava 接口。 - vendor 层: 厂商提供的
gralloc实现、hwcomposer私有扩展、Codec2驱动的私有input-buffer策略。
适配核心在于 在 common 层提供最大兼容的配置,同时在 vendor 层注入运行时修正。
3.5 适配策略实现流程
- 运行时检测: 使用
Build.VERSION.SDK_INT确定基础 API 级别;使用Build.MANUFACTURER与Build.BRAND确定厂商。 - 特性查询: 调用
MediaCodecList.getCodecInfoAt(index).getCapabilitiesForType()获取 colorFormats、setFeatureEnabled()等厂商扩展。 - 资源隔离: 在
res/xml/下按厂商 + API 级别放置media_codecs.xml覆盖文件(仅限 AOSP 平台,OEM 定制 ROM 需在vendor/etc/中配置)。 - 注入适配参数: 在创建
MediaCodec之前根据矩阵表设置MediaFormat键值对。
3.6 代码示例:多条件匹配适配
// 适配核心:根据 SDK_INT 和厂商注入参数
public static MediaFormat adjustForDevice(MediaFormat fmt) {
int api = Build.VERSION.SDK_INT;
String manufacturer = Build.MANUFACTURER.toLowerCase();
// 三星 16 对齐
if (manufacturer.contains("samsung")) {
int w = fmt.getInteger(MediaFormat.KEY_WIDTH);
int h = fmt.getInteger(MediaFormat.KEY_HEIGHT);
w = (int) Math.ceil(w / 16.0f) * 16;
h = (int) Math.ceil(h / 16.0f) * 16;
fmt.setInteger(MediaFormat.KEY_WIDTH, w);
fmt.setInteger(MediaFormat.KEY_HEIGHT, h);
}
// 华为 buffer 数量修正
if (manufacturer.contains("huawei")) {
fmt.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1920 * 1080 * 3 / 2);
// 覆盖默认 buffer 策略
}
// 小米 DRM 检查修正
if (manufacturer.contains("xiaomi") && api >= 31) {
fmt.setString(MediaFormat.KEY_FEATURE_DRM, "false");
}
// OV 超时修正:降低默认 buffer size
if (manufacturer.contains("oppo") || manufacturer.contains("vivo")) {
fmt.setInteger(MediaFormat.KEY_MAX_WIDTH, 1920);
fmt.setInteger(MediaFormat.KEY_MAX_HEIGHT, 1080);
}
return fmt;
}
常见错误: 部分开发者在
MediaCodec 配置后调用 format.setInteger(),这会被 configure() 忽略。所有适配参数必须在 configure() 调用前设置完毕。若需要在解码过程中调整 Surface 大小,应使用 SurfaceHolder.setFixedSize() 配合 surfaceChanged() 回调。
3.7 调试与验证方法
- 抓取厂商日志: 在
logcat中使用libstagefright和gralloc标签:adb logcat -s libstagefright:V gralloc:V *:S - 检查 Surface 状态:
adb shell dumpsys SurfaceFlinger | grep -A 5 "BufferQueue" - 验证对齐: 通过
adb shell getprop获取厂商特定属性(如ro.hardware.gralloc、ro.vendor.sf.bq_size)。
3.8 性能与稳定性优化
- 缓存适配矩阵: 不要每次
configure()都重新查询Build值;在 Application 启动时一次性读取并存为静态DeviceProfile。 - 异步 fallback: 厂商私有参数注入失败时使用
MediaCodec.Callback异步通知主线程切换解码器类型(如降级到MediaPlayer)。 - 最小化配置差异: 对于难以统一处理的三星对齐问题,在
SurfaceView创建时使用WindowManager.LayoutParams的setFormat(PixelFormat.RGBA_8888)强制像素格式,绕过 Gralloc 的对齐校验。