HDR转SDR实践之旅(一)流程总结

9,676 阅读10分钟

什么是HDR视频

HDR视频是高动态范围视频(High Dynamic Range的缩写),SDR视频是标准动态范围视频(Standard  Dynamic Range的缩写),动态范围指的是亮度最大值和最小值的比值。

如下图所示SDR和HDR对比发现动态范围越高,颜色更鲜艳,亮度暗部细节越多。

HDR和SDR对比

先从遇到的问题开始讲起,之所以要处理HDR视频是因为线上反馈HDR视频又暗又灰,HDR视频正确播放需要特殊处理才行。

21403fb9f1fd4ecabfd78b33a21ab41a.jpg

本系列文章主要讲Android中如何正确处理HDR视频,从开发遇到的问题作为切入点浅显易懂讲解HDR理论,你会从中学到以下10点。

  1. 如何用MediaCodec实现HDR解码渲染
  2. HDR视频转换SDR流程
  3. OpenGL纹理如何支持10位YUV数据
  4. SurfaceTexture如何支持10位BT2020YUV
  5. 10位YUV420(i420、YV12、NV12、NV21)如何用Shader转换成RGB
  6. YUV转RGB矩阵、BT2020转BT709色域矩阵是如何计算出来的
  7. 传递函数(Gamma矫正、光电转换、电光转换)的正确认知和参数详解
  8. 各种ToneMapping色调映射技术汇总
  9. 如何利用HDR视频和屏幕亮度信息动态调整效果
  10. 全网最全HDR开发资源汇总(测试视频、LUT、经验分享、标准文档)

如果你觉得有所收获,来给HDR转SDR开源代码点个赞吧,你的鼓励是我前进最大的动力。

HDR带来的问题

HDR效果虽好也给拍摄、存储、显示阶段带来了各种问题

拍摄问题

摄像头的图像传感器能捕捉的动态范围有限,怎么收集更多亮度信息呢?用不同的曝光得到多张图融合成一张,其中低曝光拿到亮处信息,高曝光拿到暗处信息,下图根据多张曝光度不一样的照片(a)融合生成亮处暗处都清晰的照片(c)。 拍摄HDR

存储问题

下图HDR图像用8位数据存储会出现色晕、暗部和亮部看不清等情况,如何用更小的数据存储更多更亮的颜色?用更多位数、更大色域、压缩率更高的格式存储数据(10位_BT2020色域_PQ_H265视频)。

8bit_vs_10bit_barevna_hloubka.jpg

显示问题

如何让屏幕支持更高亮度、更多颜色?更换屏幕材料(量子点、OLED)是种做法,下图OLED屏幕比起LCD屏幕亮的更亮、黑的更黑,也有厂商为了减小成本用抖色方案支持更高位数的颜色(假HDR)。

5930f8330ab94d448bc0cd7add987d96.jpeg

如何让屏幕中的颜色更真实?根据屏幕和图像的亮度信息重新调整效果,下图把左边的颜色通过色调映射到右边三种屏幕上。

image.png

本篇文章讲的就是HDR显示问题,HDR视频如何在SDR屏幕正确播放的开发流程。

SDR视频播放流程

HDR视频和SDR视频相比色域更大、亮度更高、细节更多,理论上播放起来也就不够鲜艳而已,怎么播放起来又暗又灰呢?变暗还好理解,HDR视频比SDR视频亮,把HDR视频当SDR视频处理当然会变暗了,那怎么还变灰了呢?宽色域用窄色域的屏幕会变灰,如下图所示左右两个颜色都是(0.5,0.0,0.0),但是不同屏幕下不一样。

image.png

那怎么正确播放HDR视频,也不要求视频效果很好至少也不要变灰呀,让我们先来梳理一下原先的SDR视频播放流程。

SDR播放流程

如上图SDR视频播放流程分3步

第1步: Mp4解封装的数据送入MediaCodec

第2步: MediaCodec解码到SurfaceTexture

第3步: SurfaceTexture绑定的纹理经过处理后渲染到SurfaceView或TextureView上屏

刚开始认为问题出在第2步,SurfaceTexture不支持10位只取HDR视频的低8位数据忽略高2位,测试发现SurfaceTexture支持10位,绑定的RGB纹理和视频原始YUV换算后基本上一样,其实问题出在第三步,纹理用OpenGL渲染时屏幕不知道要开启HDR,这需要对屏幕Surface开启HDR标识。针对这个问题有两个思路,一个是让屏幕开启HDR来适应视频,另外一个是让视频变成SDR来适应屏幕,根据这两个思路有4个解决方案。

未命名文件 (14).jpg

方案优点缺点
直接解码并渲染到SurfaceView(不需OpenGL的HDR标识)真HDR1.不支持编辑
2.不支持TextureView
3.屏幕需支持HDR
解码到SurfaceTexture再渲染SurfaceView(HDR标识)1.真HDR
2.支持纯HDR编辑
1.不支持HDR和SDR混合编辑
2.不支持TextureView
3.屏幕需支持HDR
4.不支持HLG视频
5.存在兼容性问题(小米手机上发现10位HDR标识导致HDR效果延时,用16位HDR标识正常)
解码到SurfaceTexture后转成8位BT709再渲染1.支持编辑
2.支持TextureView和SurfaceView
3.屏幕不需支持HDR
4.支持HLG、PQ视频
1. HDR转SDR效果相对而言差一点
2. 部分手机可能存在SurfaceTexture不支持10位BT2020,暂时未发现
解码到Buffer后把10位YUV数据转成8位BT709RGB纹理再渲染1.支持编辑
2.支持TextureView和SurfaceView
3.屏幕不需支持HDR
4.支持HLG、PQ视频
1. HDR转SDR效果相对而言差一点
2. 部分手机对HDR视频不支持Buffer解码(如华为的麒麟芯片手机)

结论: 只播放不需要编辑用方案1,纯HDR编辑用方案2,HDR和SDR混合编辑的话综合方案3、4解决兼容性问题。

注意点:

  1. 方案1、2只能渲染到SurfaceView的Surface上,TextureView的SurfaceTexture构造出来的Surface没有HDR效果,这也是为什么明明ExoPlayer实现了HDR却在Flutter和TextureView上失效的原因。
  2. 方案3、4把HDR视频转成了SDR是为了能和原先的SDR素材一起编辑,不把SDR素材转成HDR是因为SDR转HDRHDR转SDR的难度更大很容易出现色差(采用反色调映射时损失的亮度和色度无法恢复导致出现色彩失真和条带现象)。方案3比方案4代码写起来简单一点。
  3. OpenGL对Surface标识HDR只需要对WindowSurface的Config设为RGB10位(不需要GLContext的Config也设置10位)和配置EGL_GL_COLORSPACE_BT2020_PQ_EXT属性,但是该标识只对PQ视频有效,作用在HLG视频会导致视频过爆,暂时未发现OpenGL支持HLG。
  4. SurfaceTexture可以关联samplerExternalOESsamplerExternal2DY2YEXT纹理采样器处理得到归一化线性RGB。

HDR视频转SDR流程

HDR视频播放只需要用SurfaceView就行,编辑需要HDR转SDR,下图说明HDR视频相对于SDR视频是宽色域、高亮度、高位深的,只要改变这三要素就能把HDR视频转成SDR视频。

未命名文件 (15).jpg

如下图所示HDR转换流程就是用色域转换矩阵把宽色域变成窄色域,用色调映射保证亮度变化时颜色不失真,归一化后量化改变位深,而所有的前提要在线性空间操作才行(拍摄视频时会把自然场景的线性场景光变成非线性的电信号,编辑视频时当然要转换回来啦)。所谓线性空间指的是颜色连续均匀(颜色之间可以插值),其中线性变非线性叫光电转换,非线性转线性叫电光转换,很好记,光是自然生成的,自然界的东西都是线性的,光变电就是线性变非线性,这两种转换都被叫做传递函数。

未命名文件 (1).jpg

名词解释:

归一化: 将数值压缩到[0,1] 范围

电光转换: 视频用非线性电信号存储,处理的时候需要转成线性光数据,实际操作是代入特定的数学公式

色调映射: 目的是为了高亮度转成低亮度保证色彩不失真,实际操作是一段曲线映射

色域转换: 宽色域视频直接用窄色域进行显示会变灰,需要用宽色域转换成窄色域,实际操作是矩阵转换

光电转换: 屏幕只能显示非线性电信号,线性光数据需要转成非线性电信号,用线性光数据显示会导致变亮,实际操作是代入特定的数学公式,和电光转换的公式反了反

量化: 将数值还原到[0,2^n-1] 范围内,n=8就是变成0-255,n=10就是变成0-1023

以下就是在Android中HDR视频转SDR的具体步骤:

进度工作 (3).jpg

HDR视频转SDR视频在实际操作中其实就是把10位BT2020YUV转成8位BT709RGB,前者动态范围高,后者动态范围低。

第一步: 解码处理得到归一化线形RGB有3种方式,其中方式1代码写起来最简单。

  1. MediaCodec解码到SurfaceTexture,SurfaceTexture与samplerExternalOES纹理采样器绑定得到归一化非线性的RGB数据,samplerExternalOES测试发现支持10位BT2020RGB(兼容性未知)
  2. MediaCodec解码到SurfaceTexture,SurfaceTexture与samplerExternal2DY2YEXT纹理采样器绑定得到归一化非线性的YUV数据,再用BT2020YUV转RGB矩阵转换成归一化非线性的RGB数据,需GL_EXT_YUV_target扩展支持
  3. MediaCodec解码得到YUV420 ByteBuffer转换成OpenGL16位非线性YUV纹理,位移得到10位YUV再通过Shader处理转成归一化非线性的RGB数据

第二步: 电光转换把归一化非线性的RGB数据转换成线性

第三步: 根据视频元数据里面的亮度信息和屏幕亮度数据用色调映射调整RGB颜色

第四步: 色域转换矩阵把BT2020转换BT709,BT2020是宽色域BT709是窄色域

第五步: 光电转换把线性数据还原成非线性,量化得到8位BT709RGB

问题思考

下面3个问题留给大家思考

  1. 颜色处理要在线性空间,YUV转RGB为什么要在电光转换之前(非线性)处理?
  2. 为什么SurfaceView支持HDR,TextureView不支持HDR?
  3. OpenGL创建的WindowSurface只支持PQ不支持HLG,有什么好的解决方案吗?

系列文章