草庐IT

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

信念着了火 2023-12-02 原文

什么是HDR视频

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

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

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

本系列文章主要讲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)融合生成亮处暗处都清晰的照片©。

存储问题

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

显示问题

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

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

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

SDR视频播放流程

HDR视频和SDR视频相比色域更大、亮度更高、细节更多,理论上播放起来也就不够鲜艳而已,怎么播放起来又暗又灰呢?变暗还好理解,HDR视频比SDR视频亮,把HDR视频当SDR视频处理当然会变暗了,那怎么还变灰了呢?下图中说明了R颜色都是0.5宽色域用窄色域的屏幕会变灰。

那怎么正确播放HDR视频,也不要求视频效果很好至少也不要变灰呀,让我们先来梳理一下原先的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个解决方案。

方案优点缺点
直接解码并渲染到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视频。

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

名词解释:

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

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

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

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

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

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

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

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,有什么好的解决方案吗?

系列文章

  • HDR转SDR实践之旅(一)流程总结
  • HDR转SDR实践之旅(二)解码10位YUV纹理
  • HDR转SDR实践之旅(三)YUV420转YUV超快公式
  • HDR转SDR实践之旅(四)YUV转RGB矩阵正确推导
  • HDR转SDR实践之旅(五)色域转换BT2020转BT709
  • HDR转SDR实践之旅(六)传递函数与色差矫正
  • HDR转SDR实践之旅(七)Gamma、HLG、PQ公式详解
  • HDR转SDR实践之旅(八)色调映射
  • HDR转SDR实践之旅(九)HDR开发资源汇总
  • HDR转SDR实践之旅(十)SDR转HDR逆色调映射探索

有关HDR转SDR实践之旅(一)流程总结的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  3. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

  4. ruby-on-rails - Rails 中同一个类的多个关联的最佳实践? - 2

    我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来

  5. ruby-on-rails - 向 Rails 3 添加 Ruby 扩展方法的最佳实践? - 2

    我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion

  6. Ruby 最佳实践 : working with classes - 2

    参见下面的示例,我想最好使用第二种方法,但第一种也可以。哪种方法最好,使用另一种的后果是什么?classTestdefstartp"started"endtest=Test.newtest.startendclassTest2defstartp"started"endendtest2=Test2.newtest2.start 最佳答案 我肯定会说第二种变体更有意义。第一个不会导致错误,但对象实例化完全过时且毫无意义。外部变量在类的范围内不可见:var="string"classAvar=A.newendputsvar#=>strin

  7. ruby - 存储外部 API 的密码 - 最佳实践 - 2

    如果我构建了一个应用程序来访问来自Gmail、Twitter和Facebook的一些数据,并且我希望用户只需输入一次他们的身份验证信息,并且在几天或几周后重置,那会怎样是在Ruby中动态执行此操作的最佳方法吗?我看到很多人只是拥有他们客户/用户凭证的配置文件,如下所示:gmail_account:username:myClientpassword:myClientsPassword这看起来a)非常不安全,b)如果我想为成千上万的用户存储此类信息,它就无法工作。推荐的方法是什么?我希望能够在这些服务之上构建一个界面,因此每次用户进行交易时都必须输入凭据是不可行的。

  8. 玩以太坊链上项目的必备技能(初识智能合约语言-Solidity之旅一) - 2

    前面一篇关于智能合约翻译文讲到了,是一种计算机程序,既然是程序,那就可以使用程序语言去编写智能合约了。而若想玩区块链上的项目,大部分区块链项目都是开源的,能看得懂智能合约代码,或找出其中的漏洞,那么,学习Solidity这门高级的智能合约语言是有必要的,当然,这都得在公链``````以太坊上,毕竟国内的联盟链有些是不兼容Solidity。Solidity是一种面向对象的高级语言,用于实现智能合约。智能合约是管理以太坊状态下的账户行为的程序。Solidity是运行在以太坊(Ethereum)虚拟机(EVM)上,其语法受到了c++、python、javascript影响。Solidity是静态类型

  9. Simulink方法总结和避坑指南(一)——Simulink入门与基本调试方法 - 2

    文章目录一、项目场景二、基本模块原理与调试方法分析——信源部分:三、信号处理部分和显示部分:四、基本的通信链路搭建:四、特殊模块:interpretedMATLABfunction:五、总结和坑点提醒一、项目场景  最近一个任务是使用simulink搭建一个MIMO串扰消除的链路,并用实际收到的数据进行测试,在搭建的过程中也遇到了不少的问题(当然这比vivado里面的debug好不知道多少倍)。准备趁着这个机会,先以一个很基本的通信链路对simulink基础和相关的debug方法进行总结。  在本篇中,主要记录simulink的基本原理和基本的SISO通信传输链路(QPSK方式),计划在下篇记

  10. ruby-on-rails - 使用设计身份验证的 API 访问 - 最佳实践? - 2

    我正在使用Devise在Rails应用程序中,并希望通过API公开一些模型数据,但应该像应用程序一样限制对API的访问。$curlhttp://myapp.com/api/v1/sales/7.json{"error":"Youneedtosigninorsignupbeforecontinuing."}很明显。在这种情况下是否有访问API的最佳实践?我更喜欢一步验证+获取数据,但这只是为了让客户的工作更轻松。他们将使用JQuery在客户端提取数据。感谢您提供任何信息!凡妮莎 最佳答案 我建议您按照以下帖子中的选项2:使用APIke

随机推荐