29、兼容性适配:厂商差异(高通/联发科/华为/三星)、Android版本兼容、碎片化处理、白名单机制
做视频硬解码,最头疼的是什么?
不是算法,不是性能。是兼容性。
我做了这么多年多媒体开发,踩过的坑比走过的路还多。同一个视频,在小米上跑得飞起,换到华为就绿屏;高通芯片解码正常,联发科直接崩溃。嗯,这就是Android碎片化的真实写照。
今天我们就来聊聊,怎么搞定这些乱七八糟的兼容性问题。
一、厂商差异:四大家族各有脾气
Android手机芯片,说白了就是四家:高通、联发科、华为麒麟、三星Exynos。每家都有自己的解码器实现,行为差异很大。
核心观点:不要假设所有设备的MediaCodec行为一致。一定要做厂商级别的适配。
1. 高通(Qualcomm)
高通是我个人最熟悉的平台。它的OMX解码器叫OMX.qcom.video.decoder.avc,稳定性相对最好。
但有个坑:高通在某些老款芯片(比如骁龙625、660)上,对10bit视频支持很差。我遇到过解码10bit H.264直接返回INFO_OUTPUT_FORMAT_CHANGED后卡死的情况。
我的习惯:在高通平台上,我会主动检测芯片型号。如果是骁龙6系以下,强制走软解或者降级到8bit。
2. 联发科(MediaTek)
联发科的解码器名字通常是OMX.MTK.video.decoder.avc。它的特点是:对异常码流容忍度极低。
举个例子:一个H.264视频的SPS/PPS稍微不规范,高通能播,联发科直接崩。我曾经在MT6765上调试了三天,最后发现是码流里多了一个无效的NAL单元。
避坑指南:联发科平台一定要做码流预处理。建议在解码前用MediaExtractor重新封装一次,或者用ffmpeg过滤掉非法数据。
3. 华为(麒麟)
麒麟芯片的解码器叫OMX.hisi.video.decoder.avc。华为有个特点:对分辨率限制很严格。
我记得在麒麟990上,解码4K视频时如果宽高不是16的倍数,会直接报ERROR_CANNOT_CONNECT。其他平台可能只是花屏,华为直接罢工。
我的建议:在华为设备上,解码前先检查分辨率对齐。用(width % 16 == 0 && height % 16 == 0)做校验,不对齐就补黑边。
4. 三星(Exynos)
三星的Exynos芯片,解码器名字是OMX.Exynos.video.decoder.avc。它的问题是:对H.265(HEVC)支持参差不齐。
Exynos 9810之前的芯片,HEVC解码经常出现颜色偏差。我遇到过视频整体偏绿的情况,最后发现是三星的YUV输出顺序跟标准不一样。
厂商差异总结表:
| 厂商 | 解码器名称 | 常见问题 | 我的对策 |
|---|---|---|---|
| 高通 | OMX.qcom.video.decoder.avc | 10bit支持差 | 检测芯片型号,降级处理 |
| 联发科 | OMX.MTK.video.decoder.avc | 异常码流崩溃 | 码流预处理 |
| 华为 | OMX.hisi.video.decoder.avc | 分辨率对齐要求高 | 补黑边对齐 |
| 三星 | OMX.Exynos.video.decoder.avc | HEVC颜色偏差 | 检测Exynos版本,特殊处理 |
二、Android版本兼容:API的变迁史
Android版本从4.4到14,MediaCodec的API变化很大。你想想看,一个App要兼容这么多版本,不做适配怎么行?
1. API 19(Android 4.4)
这是MediaCodec的起点。但那时候的API很简陋,连onOutputFormatChanged都不稳定。我建议:低于5.0的设备,直接放弃硬解。
2. API 21(Android 5.0)
引入了MediaCodecInfo,可以查询解码器能力。但有个问题:isFeatureSupported方法在部分厂商实现里是假的。
注意:不要完全相信isFeatureSupported的返回值。我遇到过某厂商返回true,但实际解码时还是不支持的情况。
3. API 23(Android 6.0)
增加了KEY_LOW_LATENCY低延迟模式。但说实话,这个模式在大多数手机上效果不明显。
4. API 26(Android 8.0)
这是个大版本。引入了MediaCodecList的稳定版本,以及getCodecInfo方法。我个人建议:最低API级别设为26,这样能省掉很多兼容性代码。
5. API 29(Android 10)
增加了KEY_HDR10_PLUS支持。如果你做HDR视频,这个版本是分水岭。
版本兼容策略:
- API 19-20:放弃硬解,走软解
- API 21-25:硬解+降级策略
- API 26+:全面硬解,开启所有特性
三、碎片化处理:那些意想不到的坑
Android碎片化,说白了就是:同一段代码,在不同手机上表现完全不同。我总结了几类常见问题。
1. 解码器名称不一致
同样是H.264解码器,不同厂商命名不同:
- 高通:
OMX.qcom.video.decoder.avc - 联发科:
OMX.MTK.video.decoder.avc - 华为:
OMX.hisi.video.decoder.avc - 三星:
OMX.Exynos.video.decoder.avc - Google原生:
c2.android.avc.decoder
你不能写死解码器名字。要用MediaCodecList动态查找。
2. 输出格式不统一
解码器输出的YUV格式,有的用COLOR_FormatYUV420Planar,有的用COLOR_FormatYUV420SemiPlanar。甚至同一个厂商的不同芯片都不一样。
我的做法:在onOutputFormatChanged回调里,动态判断outputFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT),然后做对应的格式转换。
3. 输入缓冲区大小限制
有些设备对输入缓冲区有大小限制。比如华为某些机型,单个输入缓冲区不能超过2MB。如果你传一个大的关键帧,直接报错。
避坑指南:在dequeueInputBuffer之后,检查inputBuffer.capacity()。如果小于当前帧大小,需要做分片处理。
四、白名单机制:最后的防线
做了这么多适配,还是会有漏网之鱼。怎么办?上白名单机制。
白名单,说白了就是:我知道哪些设备能硬解,哪些不能。不能硬解的设备,直接走软解。
1. 白名单的构建
我习惯在服务器端维护一个白名单列表。格式如下:
{
"whitelist": [
{
"manufacturer": "Xiaomi",
"model": "Mi 10",
"chipset": "sm8250",
"codecs": ["avc", "hevc"]
},
{
"manufacturer": "Huawei",
"model": "P40",
"chipset": "kirin990",
"codecs": ["avc"]
}
],
"blacklist": [
{
"manufacturer": "Samsung",
"model": "J7",
"reason": "hevc_color_bug"
}
]
}
2. 本地缓存策略
每次启动App时,从服务器拉取白名单。如果网络不通,用本地缓存的版本。我建议:本地至少保留最近3个版本的白名单。
3. 动态降级
即使设备在白名单里,也可能出现解码失败。这时候要动态降级:
- 第一次失败:记录日志,尝试重新初始化
- 第二次失败:降级到软解,并上报服务器
- 第三次失败:加入本地黑名单,永久走软解
我的经验:白名单不是万能的。它只能解决已知问题。对于未知设备,我建议先走硬解,如果连续失败3次,自动降级。
五、知识体系总览
下面这张图,概括了本章的核心内容。你可以把它当作兼容性适配的检查清单。
嗯,以上就是兼容性适配的全部内容。说白了就是:别偷懒,每个厂商、每个版本、每台设备都要单独测试。白名单是你的最后一道防线,但不要完全依赖它。
记住一句话:在Android上做硬解码,永远要做好最坏的打算。
公众号:蓝海资料掘金营,微信deep3321