16、HDR视频解码:HDR10/HLG/Dolby Vision、色彩空间转换、元数据传递、显示适配
HDR视频,说白了就是让亮的地方更亮,暗的地方更暗,细节更丰富。我刚开始接触HDR时,觉得不就是多几个颜色嘛,后来踩了不少坑才明白——这里面的水很深。
今天咱们就聊聊Android上怎么搞定HDR视频解码。我会把HDR10、HLG、Dolby Vision这三种主流格式,以及色彩空间转换、元数据显示适配这些关键环节,掰开揉碎了讲清楚。
核心要点:HDR解码不只是解码器的事,它涉及从码流解析、元数据提取、色彩转换到最终显示的一整条链路。任何一个环节出问题,画面都会翻车。
16.1 HDR格式三兄弟:HDR10 / HLG / Dolby Vision
先说说这三种格式的区别。我当年在项目中同时支持三种格式时,差点被搞疯——它们的元数据结构完全不同。
| 特性 | HDR10 | HLG | Dolby Vision |
|---|---|---|---|
| 元数据 | 静态(SMPTE ST 2086) | 无显式元数据 | 动态(每帧/场景) |
| 传输函数 | PQ(ST 2084) | HLG(ARIB STD-B67) | PQ(ST 2084) |
| 色域 | BT.2020 | BT.2020 | BT.2020(可扩展) |
| 位深 | 10-bit | 10-bit | 10-bit / 12-bit |
| 兼容性 | 最广泛 | 兼容SDR显示 | 需授权 |
16.2 色彩空间转换:从BT.2020到显示色域
解码出来的YUV数据是BT.2020色域的,但你的手机屏幕可能只覆盖DCI-P3甚至sRGB。这时候就需要做色彩空间转换。
我个人习惯用 MediaCodec 配合 SurfaceView 时,让系统层自动处理转换。但如果你要用 TextureView 做后处理,就得自己写转换矩阵。
// 色彩空间转换矩阵示例:BT.2020 -> DCI-P3
// 注意:这只是一个简化版本,实际项目中需要精确的3x3矩阵
float[] colorMatrix = {
1.6605f, -0.5876f, -0.0728f, 0f,
-0.1246f, 1.1329f, -0.0083f, 0f,
-0.0182f, -0.1006f, 1.1187f, 0f,
0f, 0f, 0f, 1f
};
注意:千万不要在GPU里用简单的线性插值做色域转换。我曾经这么干过,结果画面偏色严重,调试了两天才发现是转换矩阵精度不够。
16.3 元数据传递:HDR的灵魂
HDR元数据决定了画面怎么映射到你的屏幕上。HDR10的静态元数据包含在SEI NAL单元里,Dolby Vision则是通过RPU(Reference Processing Unit)传递。
在Android上,MediaCodec 会通过 MediaFormat 把元数据暴露出来。你可以这样获取:
MediaFormat format = mediaCodec.getOutputFormat();
ByteBuffer hdrStaticInfo = format.getByteBuffer("hdr-static-info");
if (hdrStaticInfo != null) {
// 解析SMPTE ST 2086元数据
// 包含:显示主色、白点、最大/最小亮度
HdrStaticInfo info = parseHdrStaticInfo(hdrStaticInfo);
float maxLuminance = info.getMaxLuminance(); // 单位:cd/m²
}
小技巧:对于Dolby Vision,建议使用 DolbyVisionHelper 类来解析RPU数据。自己解析的话,字节对齐问题会让你怀疑人生。
16.4 显示适配:让HDR画面正确呈现
显示适配是最后一步,也是最容易出问题的一步。Android从API 24开始支持HDR显示,但不同厂商的实现差异很大。
我建议你这样做:
- 用
Display.getHdrCapabilities()查询设备支持的HDR类型 - 用
Window.setColorMode()切换到HDR模式 - 用
SurfaceView而不是TextureView,减少一层颜色处理
// 检查并启用HDR显示
Display display = getWindowManager().getDefaultDisplay();
Display.HdrCapabilities hdrCaps = display.getHdrCapabilities();
int[] supportedHdrTypes = hdrCaps.getSupportedHdrTypes();
boolean supportsHdr10 = false;
for (int type : supportedHdrTypes) {
if (type == Display.HdrCapabilities.HDR_TYPE_HDR10) {
supportsHdr10 = true;
break;
}
}
if (supportsHdr10) {
getWindow().setColorMode(ActivityInfo.COLOR_MODE_HDR);
}
踩坑记录:我曾经在小米某款手机上发现,即使 setColorMode 返回成功,实际画面还是SDR。后来发现是系统UI的亮度条没有同步调整——HDR模式下亮度必须拉到最高才行。
16.5 知识体系总览
下面这张图把HDR解码的完整链路画出来了。你仔细看,每个环节都有坑。
16.6 实战建议
嗯,最后说几点我个人的经验:
- 测试设备要全:HDR效果在不同手机上差异巨大。我手头常备5台不同品牌的测试机,专门用来验证HDR显示。
- 降级策略要备好:如果设备不支持HDR,要能优雅地降级到SDR。别让用户看到一片灰蒙蒙的画面。
- 性能监控别忘:HDR解码的功耗比SDR高30%-50%。记得用
PowerProfile监控,防止手机过热降频。
避坑指南:我曾经在适配某款折叠屏时发现,展开和折叠状态下HDR能力不一样。后来加了 DisplayListener 动态监听,才搞定这个问题。
HDR解码这条路,说难不难,说简单也不简单。关键是把每个环节都吃透,尤其是元数据传递和色彩转换——这两个地方最容易出幺蛾子。希望今天的分享能帮你少走些弯路。