视频优化:解码器选择、硬解与软解、缓冲策略、HDR 优化
视频播放,是 Android 系统里最吃性能的场景之一。我见过不少应用,UI 流畅度做得不错,但一播视频就发热、掉帧、甚至花屏。说白了,视频优化没做好,用户的第一印象就崩了。
今天咱们就聊聊视频这条链路里的几个关键点:解码器怎么选、硬解和软解到底怎么权衡、缓冲策略怎么调、以及 HDR 这个“新贵”该怎么伺候。
解码器选择:MediaCodec 与底层博弈
Android 的视频解码,核心 API 是 MediaCodec。但你别以为调个 API 就完事了。底层解码器的选择,直接决定了功耗和画质。
我个人习惯,在初始化解码器时,先通过 MediaCodecList 遍历所有可用的解码器。你会发现,同一个 MIME 类型(比如 video/avc),系统可能注册了多个解码器:一个硬件解码器(比如 OMX.qcom.video.decoder.avc),一个软件解码器(比如 OMX.google.h264.decoder)。
这里有个坑:不要直接选第一个。我曾经在一个项目里,直接用了 findDecoderForFormat,结果在某个低端芯片上,它给我返回了一个软解实例,播放 1080p 视频直接卡成 PPT。
我的建议:优先选择名称中包含硬件厂商标识(如 qcom、exynos、mtk)的解码器。如果找不到,再 fallback 到 Google 的软解实现。
// 伪代码示例:优先选择硬件解码器
MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
for (MediaCodecInfo info : codecList.getCodecInfos()) {
if (!info.isEncoder()) {
for (String type : info.getSupportedTypes()) {
if (type.equals(mimeType)) {
// 检查是否是硬件解码器
if (info.getName().contains("qcom") || info.getName().contains("mtk")) {
// 优先使用
return info.getName();
}
}
}
}
}
// 兜底:使用 Google 软解
return "OMX.google." + mimeType + ".decoder";
硬解码与软解码:不是非黑即白
很多人觉得硬解一定比软解好。其实不一定。硬解功耗低、性能好,但兼容性差;软解兼容性好,但吃 CPU。
我遇到过最典型的情况:某款手机硬解 H.265 10bit 视频,画面出现绿色条纹。查了半天,是芯片解码器的一个 bug。最后只能降级到软解。
所以我的策略是:默认硬解,异常降级。具体来说:
- 启动硬解,监听
MediaCodec.Callback的onError回调。 - 如果连续出现 3 次解码错误,自动切换到软解。
- 在切换时,保留当前播放进度,不要让用户感觉到“断流”。
小技巧:硬解模式下,Surface 的格式建议用 SurfaceView 而不是 TextureView。TextureView 会多一次 GPU 拷贝,增加延迟和功耗。我实测过,同样 4K 视频,TextureView 的帧渲染时间比 SurfaceView 多了 3-5ms。
缓冲策略:别让“水龙头”忽大忽小
视频播放的流畅度,很大程度上取决于缓冲策略。缓冲太小,容易卡顿;缓冲太大,启动慢、内存高。
我习惯把缓冲分为三个阶段:
- 启动阶段:快速填充缓冲区,让首帧尽快显示。一般预加载 500ms 的数据就够了。
- 稳定阶段:维持一个动态缓冲区。根据网络波动,缓冲区大小在 2s 到 5s 之间浮动。
- 低水位阶段:当缓冲区低于 1s 时,暂停解码,等待数据填充到 3s 再继续。
这里有个细节:不要用固定大小的缓冲区。我曾经在一个直播项目里,固定了 4MB 的缓冲区。结果在弱网环境下,缓冲区一直填不满,导致播放器频繁进入“等待-播放-等待”的死循环。
避坑指南:我曾经在调整缓冲大小时,直接修改了 MediaCodec 的 queueInputBuffer 的时机。结果发现,如果输入队列积压太多,解码器会直接丢帧。正确的做法是:通过 getOutputBuffers 的返回状态来判断解码器的处理能力,而不是盲目塞数据。
HDR 优化:从“能播”到“播好”
HDR 视频,说白了就是让亮的地方更亮,暗的地方更暗,色彩更丰富。但 Android 上的 HDR 支持,碎片化非常严重。
我遇到过最头疼的问题:同一段 HLG 视频,在 Pixel 上色彩正常,在小米上却发灰。原因是小米的 ColorOS 对 HDR 的 tone mapping 实现不同。
我的优化思路分三步:
- 第一步:检测能力。通过
Display.getHdrCapabilities()获取设备支持的 HDR 类型(HDR10、HLG、Dolby Vision)。如果设备不支持,直接降级到 SDR。 - 第二步:设置正确的色彩空间。在
MediaCodec的configure方法中,传入MediaFormat.KEY_COLOR_TRANSFER和MediaFormat.KEY_COLOR_STANDARD。很多开发者漏了这一步,导致解码器用默认的 BT.709 去渲染 BT.2020 的内容,颜色当然不对。 - 第三步:Surface 的 HDR 适配。如果是
SurfaceView,需要调用surfaceView.setSecure(true)(针对 DRM 内容),并且确保 Surface 的像素格式是RGBA_1010102或RGBA_FP16。
// 设置 HDR 色彩空间
MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height);
format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_HLG);
format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT2020);
format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
核心观点:HDR 优化不是让所有设备都显示 HDR,而是让不支持 HDR 的设备也能“正常”显示 SDR 版本。我建议在应用层做一次 tone mapping 的 fallback:如果设备不支持 HDR,用 OpenGL 或 Vulkan 做一次色彩空间转换,把 HDR 内容映射到 SDR 范围。虽然会损失一些动态范围,但至少不会出现“灰蒙蒙”的画面。
知识体系总览
下面这张图,是我对视频优化核心逻辑的总结。你可以把它当作一个检查清单:
嗯,视频优化这块,说白了就是“平衡”二字。平衡功耗与画质,平衡兼容性与性能,平衡启动速度与流畅度。没有银弹,只有根据实际场景不断调优。
我最后想说的是:不要迷信某个“最佳实践”。同样的策略,在骁龙 8 Gen 3 上跑得飞起,在联发科 G99 上可能就翻车。多测试、多埋点、多分析,才是正道。