29、兼容性适配:碎片化问题、编解码器兼容、权限适配、Android版本适配

做音视频开发,最头疼的是什么?

不是算法难,不是API复杂。而是——同一套代码,在小米上跑得飞起,在华为上黑屏,在OPPO上没声音,在老的三星上直接闪退。

这就是Android碎片化。我做了这么多年音视频,踩过的坑能写满一本笔记本。今天咱们就聊聊,怎么跟这些碎片化问题和平共处。

29.1 碎片化问题的本质

Android碎片化体现在三个维度:

  • 硬件碎片化:芯片不同(高通、联发科、麒麟、Exynos),GPU不同,摄像头传感器不同
  • 系统碎片化:从Android 5.0到Android 14,每个版本API差异巨大
  • 厂商定制化:MIUI、ColorOS、EMUI、One UI……每家都改底层

说白了,你面对的不是一个Android,而是几百个Android。

核心原则:不要假设任何设备的行为是"标准的"。永远做防御性编程。

29.2 编解码器兼容:最痛的坑

编解码器兼容,是我个人认为音视频适配里最麻烦的部分。为什么?因为MediaCodec的行为,在不同芯片上完全不一样。

29.2.1 支持的编码格式差异

你以为所有设备都支持H.264硬编?太天真了。

编码格式 高通 联发科 麒麟 Exynos
H.264 ✅ 全支持 ✅ 全支持 ✅ 全支持 ✅ 全支持
H.265 ✅ 大部分 ⚠️ 部分低端不支持 ✅ 支持 ✅ 支持
VP8 ✅ 支持 ⚠️ 部分不支持 ❌ 不支持 ✅ 支持
AV1 ❌ 需软解 ❌ 需软解 ❌ 需软解 ❌ 需软解

我在项目中遇到过,某款联发科低端平板,硬编H.265出来的视频全是花屏。查了两天才发现,它的H.265编码器有bug,只能输出I帧,P帧全丢了。

怎么解决?我的习惯是:

  • 先查MediaCodecList,确认设备支持哪些编码器
  • 准备一个降级策略:硬编不行就软编,H.265不行就降级到H.264
  • 关键场景做编码器自测:编一个短片段,解码验证质量
// 查询设备支持的编码器
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
String[] supportedTypes = codecList.getSupportedTypes();

// 检查特定编码器是否支持
boolean isH264Supported = false;
for (String type : supportedTypes) {
    if (type.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) {
        isH264Supported = true;
        break;
    }
}

// 降级策略示例
if (!isH264Supported) {
    // 降级到软件编码
    useSoftwareEncoder();
}

29.2.2 解码器兼容问题

解码器的问题更隐蔽。我记得有一次,用户反馈说播放某个MP4文件,在小米10上正常,在红米Note 9上只有声音没有画面。

查了半天,原因是那个视频的H.264编码使用了High Profile的某些特性,红米的解码器不支持。嗯,这里要注意:

  • 不同厂商的解码器能力边界不一样
  • 有些解码器对B帧数量有限制
  • 参考帧数量超过4个,部分老解码器直接罢工

我的建议:编码时尽量用Baseline Profile或Main Profile,别用High Profile的奇奇怪怪特性。兼容性比画质重要。

29.3 权限适配:从M到14的进化史

权限问题,说白了就是Google一直在收紧,厂商一直在加戏。

29.3.1 运行时权限

Android 6.0(API 23)引入了运行时权限。如果你还在用targetSdkVersion 22以下,那当我没说。但新应用必须适配。

音视频开发涉及的权限:

  • CAMERA:摄像头采集
  • RECORD_AUDIO:麦克风录音
  • READ_EXTERNAL_STORAGE:读取媒体文件
  • WRITE_EXTERNAL_STORAGE:保存录制文件
// 请求权限的标准做法
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.CAMERA},
            REQUEST_CAMERA_PERMISSION);
}

29.3.2 Android 10+ 的存储限制

Android 10(API 29)开始,强制推行分区存储(Scoped Storage)。你不能再随便往/sdcard/里写文件了。

我曾经有个项目,升级targetSdkVersion到29后,录制的视频全保存失败。查了半天才发现,MediaStore的URI用错了。

避坑指南:Android 10以上,保存媒体文件要用MediaStore,别再用File API直接写路径了。Android 11更严格,连读取都需要MANAGE_EXTERNAL_STORAGE权限。

// Android 10+ 保存视频的正确方式
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DISPLAY_NAME, "video_" + System.currentTimeMillis() + ".mp4");
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES);

Uri uri = getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
// 通过uri写入数据

29.3.3 Android 12+ 的蓝牙权限

Android 12(API 31)把蓝牙权限拆成了BLUETOOTH_SCAN、BLUETOOTH_CONNECT、BLUETOOTH_ADVERTISE。如果你的应用需要连接蓝牙耳机或蓝牙麦克风,记得适配。

29.4 Android版本适配要点

不同Android版本,API行为差异很大。我整理了一张表,方便你对照:

Android版本 API Level 音视频相关变化 适配要点
6.0 23 运行时权限 必须动态申请权限
7.0 24 FileProvider 相机拍照用FileProvider
8.0 26 后台执行限制 后台录音需前台服务
9.0 28 非SDK接口限制 别用隐藏API
10 29 分区存储 用MediaStore
11 30 包可见性 查询其他应用需
12 31 蓝牙权限拆分 分场景申请
13 33 通知权限 需POST_NOTIFICATIONS
14 34 前台服务类型 声明foregroundServiceType

29.5 知识体系总览

下面这张图,是我对兼容性适配的整体理解。你想想看,适配工作其实就围绕这三个维度展开:

Android音视频兼容性适配 硬件碎片化 系统版本差异 厂商定制化 芯片差异(高通/联发科/麒麟) 编解码器能力不同 摄像头传感器差异 API行为变化 权限模型演进 存储策略变化 MIUI/ColorOS/EMUI 底层驱动修改 系统服务行为差异 解决方案:防御性编程 + 降级策略 + 充分测试

29.6 实战建议

说了这么多,最后给几条实在的建议:

  1. 测试设备要覆盖主流芯片:高通、联发科、麒麟各来一台。别只测旗舰机,中低端机才是用户量最大的。
  2. 建立兼容性清单:每发现一个兼容性问题,就记录下来。我自己的清单已经积累了200多条。
  3. 灰度发布:新功能先放5%的用户,观察崩溃率和兼容性反馈,没问题再全量。
  4. 云端降级:如果某个设备编解码有问题,服务端可以下发配置,强制走软解或降码率。

最后说一句:兼容性适配没有银弹。你只能靠经验积累和充分测试。但只要你把上面这些点都考虑到了,至少能覆盖90%的问题。


公众号:蓝海资料掘金营,微信deep3321