HDR详解

HDR详解

从物理原理到 Android 播放器实现的完整链路。


一、核心概念:一个像素是怎么发光的


像素的物理结构

复制代码
一个像素 = 三个子像素(Sub-pixel)
┌─────────────────┐
│  R  │  G  │  B  │  ← 三个独立的发光单元
└─────────────────┘
  • LCD:三个子像素共享背光,液晶+滤光片控制透光量
  • OLED:三个子像素各自独立发光,电流控制亮度

RGB 值的真正含义

RGB 值不是"绝对颜色",而是三个旋钮的比例。要还原出真实视觉效果,必须知道三件事:

参数 决定什么 类比
色域 (Color Primaries) 旋钮背后接的是什么灯泡(子像素发什么颜色的光) 尺子的单位
传输函数 (Transfer Function) 旋钮刻度和实际亮度的对应关系 尺子的量程
位深 (Bit Depth) 旋钮有多少格刻度 尺子的精度

从 RGB 值到子像素发光

复制代码
RGB 编码值 (如 10bit: 0~1023)
    ↓ EOTF 曲线(传输函数)
线性光强度 (0~1 的比例)
    ↓ × 子像素最大发光能力
物理发光 (nits)

三个通道独立计算,互不影响。EOTF 是同一条曲线套三遍。


二、高画质的五个要素

要素 含义 SDR (BT.709) HDR (BT.2100)
分辨率 像素数量 1920×1080 3840×2160+
位深 每通道编码精度 8bit (1677万色) 10/12bit (10亿+色)
帧率 时间维度清晰度 30/60fps 60/120fps
色域 能表示的颜色范围 BT.709 (~35%可见色) BT.2020 (~75%可见色)
亮度(动态范围) 最暗到最亮的范围 0~100 nits 0~10000 nits

HDR 的核心突破是第5项------前4项在 BT.2020 中已标准化,唯独亮度一直停留在 100 nits。


三、色域详解

马蹄图与三角形

人眼能看到的所有颜色构成马蹄形区域。任何一套 RGB 系统在马蹄图上画一个三角形(三个顶点 = 三个基色的色度坐标),三角形内的颜色能显示,外面的不行。

主流色域对比

色域 R 色度 G 色度 B 色度 覆盖
BT.709/sRGB (0.640, 0.330) (0.300, 0.600) (0.150, 0.060) ~35%
DCI-P3 (0.680, 0.320) (0.265, 0.690) (0.150, 0.060) ~45%
BT.2020 (0.708, 0.292) (0.170, 0.797) (0.131, 0.046) ~75%

关键理解

同样的 RGB(255,0,0),在不同色域的屏幕上发出的红光不一样:

  • BT.709 屏幕的红色子像素 → 发色度 (0.640, 0.330) 的红(偏橙)
  • BT.2020 屏幕的红色子像素 → 发色度 (0.708, 0.292) 的红(更纯)

色域是硬件物理决定的,出厂就定了,软件改不了。如果视频色域 ≠ 屏幕色域,需要做矩阵转换。

色域转换

复制代码
源 RGB → XYZ(用源色域矩阵)→ 目标 RGB(用目标色域逆矩阵)

BT.709 RGB → XYZ:
| 0.4124  0.3576  0.1805 |   | R |   | X |
| 0.2126  0.7152  0.0722 | × | G | = | Y |
| 0.0193  0.1192  0.9505 |   | B |   | Z |

XYZ 是设备无关的绝对色彩空间,作为中间桥梁。


四、传输函数(EOTF)详解

为什么需要非线性编码

人眼对亮度的感知是对数的(韦伯-费希纳定律):

  • 1→2 nits:感觉"明显变亮"
  • 1000→1001 nits:完全感觉不到

如果线性编码,暗部精度不够(色带),亮部浪费编码空间。传输函数把更多编码值分配给人眼敏感的暗部。

三种主要曲线

Gamma (SDR)
复制代码
编码 (OETF): V = L^(1/2.2)
解码 (EOTF): L = V^2.2

V=0.5 → L = 0.5^2.2 = 0.217 → 21.7 nits (参考白100nits时)
量程: 0~100 nits
PQ (HDR10, SMPTE ST 2084)
复制代码
解码 (EOTF): L = 10000 × [max(V^(1/m2) - c1, 0) / (c2 - c3·V^(1/m2))]^(1/m1)

V=0.508 → L ≈ 100 nits
V=0.752 → L ≈ 1000 nits
V=1.0   → L = 10000 nits
量程: 0~10000 nits (绝对值)
  • 由 Dolby 提出,基于人眼视觉实验(Barten 模型)
  • 每个编码步长 = 人眼刚好能分辨的最小亮度差异
  • 10bit PQ 理论上全亮度范围无可见色带
HLG (广播)
复制代码
下半段: Gamma(兼容 SDR)
上半段: 对数(扩展高光)
量程: 相对值(自适应屏幕峰值亮度)
  • BBC/NHK 提出,为广播设计
  • SDR 电视直接显示也不会太离谱

PQ vs HLG 对比

PQ HLG
亮度定义 绝对值 (nits) 相对值
SDR 兼容 差(发灰)
适用场景 流媒体、电影 广播、直播
提出者 Dolby BBC + NHK

SDR vs HDR 的根本区别

不是 bit 数的区别,是"量程"的区别:

复制代码
SDR: 8bit (256格) 覆盖 0~100 nits    → 每格 ≈ 0.4 nits
HDR: 10bit (1024格) 覆盖 0~10000 nits → 非线性分配,暗部密集

如果把 HDR 数据当 SDR 显示(用 Gamma 解读 PQ)→ 画面发灰(最常见的 HDR 问题)。


五、HDR 元数据

为什么需要元数据

RGB + 传输函数已经能算出每个像素的亮度。但元数据解决的是 Tone Mapping 的决策问题------告诉屏幕"这个内容整体有多亮",让压缩更精准。

静态元数据 (HDR10)

整部片子一组数据,写在文件头:

复制代码
MaxCLL  = 1500 nits  (最亮像素)
MaxFALL = 800 nits   (最亮帧平均亮度)
Mastering Display:
  - 主显示器色度坐标 (RGB + 白点)
  - 最大/最小亮度

动态元数据 (Dolby Vision / HDR10+)

每帧/每场景独立的亮度信息:

复制代码
场景A (夜晚): 最亮 200 nits → 屏幕不压缩,细节全保留
场景B (爆炸): 最亮 4000 nits → 屏幕按需压缩

HDR 标准对比

标准 元数据 位深 授权 效果
HDR10 静态 (MaxCLL/MaxFALL) 10bit 免费开放 基础
HDR10+ 动态 (逐场景) 10bit 免费 (三星) 较好
Dolby Vision 动态 (逐帧 RPU) 12bit 需授权 最好
HLG 无需额外元数据 10bit 免费 广播用

六、Tone Mapping

是什么

把屏幕显示不了的亮度范围,压缩到屏幕能显示的范围内。

复制代码
视频内容: 0~4000 nits
屏幕能力: 0~1000 nits
Tone Mapping: 非线性压缩 4000→1000

为什么不能等比缩小

复制代码
等比 ÷4: 100nits人脸→25nits(太暗看不清)
非线性:  暗部基本不动,高光大幅压缩

谁来做

场景 执行者
屏幕支持 HDR 不需要 Tone Mapping,直通
屏幕不支持,系统处理 SurfaceFlinger / HWC
播放器自己做 OpenGL shader 中实现

完整流程

复制代码
HDR RGB (PQ, BT.2020)
  ↓ Step1: PQ EOTF → 线性光 (nits)
  ↓ Step2: 色域转换 BT.2020 → BT.709
  ↓ Step3: 压缩亮度 (用 MaxCLL 作为参考)
  ↓ Step4: Gamma OETF 编码
SDR RGB (Gamma, BT.709) → 屏幕正常显示

七、YUV 与色彩矩阵

YUV→RGB 转换矩阵

不同标准定义不同系数:

复制代码
BT.601:  R = Y + 1.402×V,  G = Y - 0.344×U - 0.714×V,  B = Y + 1.772×U
BT.709:  R = Y + 1.5748×V, G = Y - 0.1873×U - 0.4681×V, B = Y + 1.8556×U
BT.2020: R = Y + 1.4746×V, G = Y - 0.1646×U - 0.5714×V, B = Y + 1.8814×U

用错矩阵 → 颜色严重偏色。

Color Range

类型 Y 范围 UV 范围 场景
Limited (TV) 16~235 16~240 视频(默认)
Full (PC) 0~255 0~255 JPEG、PC 截图

搞错 → 黑不够黑、白不够白。


八、完整播放链路(从文件到屏幕)

复制代码
视频文件
  ↓ 解封装 (Demux)
  │ 提取: color_primaries / color_trc / colorspace / color_range / HDR metadata / DV info
  ↓
解码
  │ 输出: 10bit YUV + 色彩参数
  ↓
色彩处理
  │ 屏幕支持HDR → 直通 (设置 DataSpace = BT2020_PQ)
  │ 屏幕不支持  → Tone Mapping + 色域转换
  ↓
渲染 (Surface / OpenGL)
  │ 设置 DataSpace 告诉系统这是 HDR 还是 SDR
  ↓
SurfaceFlinger
  │ 识别 HDR layer → 通知 HWC 切换 HDR 模式
  ↓
屏幕
  │ PQ 模式: 用 PQ 曲线驱动子像素,峰值 1000~2000+ nits
  │ SDR 模式: 用 Gamma 曲线驱动,峰值 ~300 nits

九、Android 硬解 HDR 实现

关键参数设置

java 复制代码
MediaFormat format = MediaFormat.createVideoFormat("video/hevc", w, h);
format.setInteger(KEY_COLOR_TRANSFER, COLOR_TRANSFER_ST2084);   // PQ
format.setInteger(KEY_COLOR_STANDARD, COLOR_STANDARD_BT2020);   // 色域
format.setInteger(KEY_COLOR_RANGE, COLOR_RANGE_LIMITED);         // range
format.setByteBuffer(KEY_HDR_STATIC_INFO, hdrStaticInfoBuffer); // MaxCLL等

硬解 + Surface 直通

复制代码
MediaCodec.configure(format, surface, null, 0)
  ↓ 解码
releaseOutputBuffer(index, true)  // render=true
  ↓ MediaCodec 自动设置 buffer DataSpace = BT2020_PQ
SurfaceFlinger 识别 HDR layer → 直通到屏幕

必须用 SurfaceView(不是 TextureView),否则 HDR 信息可能丢失。

Dolby Vision 硬解

复制代码
mime = "video/dolby-vision"
查找 DV 解码器 (名字含 "dolby")
配置 dv_profile / dv_level
  ↓ DV 解码器内部处理 RPU 动态元数据
  ↓ 输出带 DV DataSpace 的 buffer
屏幕启用 DV 显示管线

DV 的动态元数据在码流 RPU 中,不需要外部传递。


十、Android OpenGL HDR 渲染

必须做的事

c 复制代码
// 1. EGL Surface 声明 HDR
EGLint surfaceAttribs[] = {
    EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_BT2020_PQ_EXT,
    EGL_NONE
};

// 2. 设置 DataSpace
ANativeWindow_setBuffersDataSpace(window, ADATASPACE_BT2020_PQ);

// 3. Framebuffer 精度 10bit 或 FP16
EGL_RED_SIZE=10, EGL_GREEN_SIZE=10, EGL_BLUE_SIZE=10

Shader 中的处理

glsl 复制代码
// 如果输出 HDR (屏幕支持):
// YUV→RGB (BT.2020矩阵) → 保持 PQ 编码 → 直接输出

// 如果输出 SDR (需要 Tone Mapping):
// YUV→RGB → PQ EOTF → 线性光 → Tone Mapping → 色域转换 → Gamma 编码

十一、参数丢失后果速查

丢失参数 后果 严重度
color_trc (传输函数) 画面发灰 (PQ 当 Gamma 解读) ⭐⭐⭐⭐⭐
color_primaries (色域) 颜色偏色/过饱和 ⭐⭐⭐⭐
colorspace (YUV矩阵) 颜色严重偏色 ⭐⭐⭐⭐
color_range 对比度异常 ⭐⭐⭐
bit_depth 截断 暗部色带 ⭐⭐⭐
MaxCLL/MaxFALL Tone Mapping 效果差 ⭐⭐
DataSpace 未设置 画面发灰 ⭐⭐⭐⭐⭐

最常见的 HDR 问题就是"发灰"------传输函数信息在某个环节丢失。


十二、关键名词速查

缩写 全称 作用
EOTF Electro-Optical Transfer Function 电信号→光(显示器侧解码)
OETF Opto-Electronic Transfer Function 光→电信号(摄像机侧编码)
PQ Perceptual Quantizer (ST 2084) HDR 绝对亮度 EOTF
HLG Hybrid Log-Gamma 广播兼容 HDR EOTF
MaxCLL Maximum Content Light Level 整片最亮像素
MaxFALL Maximum Frame-Average Light Level 最亮帧平均亮度
RPU Reference Processing Unit DV 逐帧动态元数据
DataSpace Android 色彩空间标记 告诉系统 buffer 的色彩属性
HWC Hardware Composer Android 硬件合成
SF SurfaceFlinger Android 显示合成进程
CIE XYZ 设备无关绝对色彩空间 色域转换的中间桥梁
EDID Extended Display Identification Data 显示器能力自报

十三、一句话串联

拍摄时:真实亮度通过 OETF 压缩为 RGB 数值,标注色域+传输函数+元数据存入文件。

播放时:解码出 RGB 数值 → 用传输函数还原亮度 → 用色域确定颜色 → 如果屏幕能力不够就 Tone Mapping → 驱动子像素发光。

整条链路的核心就是:确保每个环节都知道"这些数字代表什么"。丢了任何一个标注,显示就会出错。

相关推荐
Bruce_kaizy11 分钟前
c++ linux环境编程——文件io介绍以及open 、write 、read 三剑客深度详解
linux·服务器·c++·ubuntu·操作系统·文件io
亦良Cool26 分钟前
VMware虚拟机ubuntu瘦身,解决虚拟机越用越大
linux·运维·ubuntu
星辰&与海2 小时前
KVM + QEMU虚拟化方案
linux·运维
宋浮檀s2 小时前
应急响应——恶意流量&攻击行为识别
linux·运维·网络·网络安全·应急响应
REDcker2 小时前
Linux OverlayFS详解
java·linux·运维
OpenApi.cc3 小时前
2026年最新openapi:免费图片人脸识别和视频人脸识别工具
音视频
lwx9148523 小时前
Linux系统中用户锁定后如何解锁
linux·运维·服务器
zhangrelay4 小时前
ROS 2 Lyrical Luth启程-Ubuntu26.04-
linux·笔记·学习·ubuntu
WoY20204 小时前
使用iostat看磁盘IO
linux
kebidaixu4 小时前
VS Code Remote-SSH 远程开发:解决无法安装扩展、市场加载失败的问题
linux