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解码的完整链路画出来了。你仔细看,每个环节都有坑。

HDR视频解码完整链路 HDR视频码流 解封装(提取视频轨) MediaCodec 硬解码 输出YUV + 提取HDR元数据 色彩空间转换(BT.2020 → 显示色域) SurfaceView / TextureView 显示 元数据解析 HDR10: SEI NAL HLG: 无显式元数据 Dolby Vision: RPU → 亮度/色域映射

16.6 实战建议

嗯,最后说几点我个人的经验:

  • 测试设备要全:HDR效果在不同手机上差异巨大。我手头常备5台不同品牌的测试机,专门用来验证HDR显示。
  • 降级策略要备好:如果设备不支持HDR,要能优雅地降级到SDR。别让用户看到一片灰蒙蒙的画面。
  • 性能监控别忘:HDR解码的功耗比SDR高30%-50%。记得用 PowerProfile 监控,防止手机过热降频。

避坑指南:我曾经在适配某款折叠屏时发现,展开和折叠状态下HDR能力不一样。后来加了 DisplayListener 动态监听,才搞定这个问题。

HDR解码这条路,说难不难,说简单也不简单。关键是把每个环节都吃透,尤其是元数据传递和色彩转换——这两个地方最容易出幺蛾子。希望今天的分享能帮你少走些弯路。

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