什么是YUV

HDR视频是10位或12位YUV,OpenGL不支撑10位YUV纹路需求特别处理,本篇文章讲的便是HDR转SDR第一步解码10位YUV纹路的办法(12位同理)。如果你觉得有所收成,来给HDR转SDR开源代码点个赞吧,你的鼓舞是我行进最大的动力。

YUV是为了处理彩色电视和黑白电视的兼容问题创造的。下图中右边的RGB图画和左面的YUV三通道图画等价,黑白电视机只需求Y亮度通道,UV是色度(U是蓝色与亮度的差量,V是赤色与亮度的差量),这也是为什么YUV又被叫做YCbCr的原因,Cb是ColorBlue的缩写,Cr是ColorRed的缩写。

HDR转SDR实践之旅(二)解码10位YUV纹理

下图所示是电视机上YCbCr的接口,只接Y显示黑白。

HDR转SDR实践之旅(二)解码10位YUV纹理

UV是蓝赤色度,那么绿色去哪里了?绿色在Y通道里,如下图所示Y通道便是一定份额的红绿蓝混合而成。为什么一定份额的红绿蓝混合变成了黑白色,由于红绿蓝光的份额尽管不同,可是三个感光细胞的电脉冲信号强度相同,电脉冲信号强度相同看起来便是黑白色。不是说RGB三光混合变成了白光而是眼睛看起来像白光,这也是为什么Y亮度更适合叫做Y明度的原因,明度比亮度更适合表明生理感触(为了便利,后续Y仍是叫做亮度)。

HDR转SDR实践之旅(二)解码10位YUV纹理

MediaCodec解码10位YUV流程

HDR转SDR实践之旅(二)解码10位YUV纹理

MediaCodec解码10位YUV有3种计划,最终都是为了得到归一化的RGB纹路。

计划1: 解码到SurfaceTexture,SurfaceTexture与samplerExternalOES纹路采样器绑定得到归一化非线性的RGB数据

计划2: 解码到SurfaceTexture,SurfaceTexture与samplerExternal2DY2YEXT纹路采样器绑定得到归一化非线性的YUV数据,再用BT2020YUV转RGB矩阵转化成归一化非线性的RGB纹路,需GL_EXT_YUV_target扩展支撑

计划3: 解码出16位YUV420Buffer(10位YUV是16位存储的),上传到纹路后经Shader处理得到归一化的RGB纹路(先把YUV420Buffer上传到16位纹路中,再用Shader从YUV420转化成YUV,然后右移6位得到10位YUV,再进行YUV转RGB转化得到10位RGB归一化纹路)。

计划 长处 缺陷
计划1:解码到samplerExternalOES绑定的SurfaceTexture 代码简略 尽管samplerExternalOES只支撑8位,可是高8位归一化后最多只差31024\frac{3}{1024}也可视为支撑10位
计划2:解码到samplerExternal2DY2YEXT绑定的SurfaceTexture 代码简略 GL_EXT_YUV_target扩展支撑
计划3:解码出16位YUV420Buffer再用Shader转化 自己处理流程可控 1. 纷歧定支撑解码出16位Buffer(测试发现解出16位的手机大都是晓龙中高端机,华为的麒麟芯片不支撑)
2. 代码略繁琐

3个计划各有利弊互补处理兼容性问题,从代码便利程度上计划1>计划2>计划3,从兼容性程度上计划3>计划1>计划2。

10位YUV存储

完成10位YUV纹路之前还要处理一个问题,10位YUV的Buffer数据是用16位存储的,还要完成16位YUV转10位YUV,由于字节对齐后处理便利还能加快读取速度,10位也便是1.25字节,对齐后便是2字节(16位)。10位变16位多出来的位数补0就可以了,大端情况下0补在前面数据不会发生变化,小端情况下0补在后面导致数据左移需求右移回来。

HDR转SDR实践之旅(二)解码10位YUV纹理
如上图所示YUV16位十进制479大端情况下仍是479,小端右边补6位变成30656(479*2^6),16位YUV变成10位YUV需求右移6位去除0。上图中看到正常的小端数据和YUV的小端数据是不相同的,那么为什么YUV小端不必正常小端存储,这样不是更简略吗,我的猜测是为了确保16位数据被当成8位归一化和16位直接归一化的数据距离最小(YUV小端右边8位保留着原数据的高8位丢弃低位归一化后和16位归一化最多就差31024\frac{3}{1024})。

10位YUV纹路计划

OpenGL自身不支撑YUV纹路只支撑RGB纹路需求特别扩展或许自己处理,下面3个计划为了便利用伪代码解说。

计划一解码到samplerExternalOES绑定的SurfaceTexture

HDR转SDR实践之旅(二)解码10位YUV纹理

第一步: Mediacodec用Configure办法装备Surfacetexture,解码的数据传递到Surfacetexture

第二步: SurfaceTexture在OpenGL环境中调用attachToGLContext绑定纹路,updateTexImage办法把SurfaceTexture的内容更新到纹路。

第三步: 纹路采样器samplerExternalOES和SurfaceTexture的纹路绑定,samplerExternalOES是OpenGL的扩展支撑把YUV转成RGB

samplerExternalOES支撑YUV转RGB是毫无疑问的,那么samplerExternalOES支撑10位BT2020YUV转RGB吗?经过测试发现把RGB打印出来非常接近正常流程转出来的RGB,差错可以忽略不计,所以扫除机型兼容和精度差错情况下,samplerExternalOES可以视为支撑BT2020YUV转RGB。

计划二解码到samplerExternal2DY2YEXT绑定的SurfaceTexture

HDR转SDR实践之旅(二)解码10位YUV纹理
留意:

第一步: Mediacodec运用Configure办法装备Surfacetexture,解码的数据会传递到Surfacetexture

第二步: SurfaceTexture在OpenGL环境中调用attachToGLContext绑定纹路,updateTexImage办法把SurfaceTexture的内容更新到纹路。

第三步: 纹路采样器samplerExternal2DY2YEXT yuvtexture和SurfaceTexture的纹路绑定,samplerExternal2DY2YEXT是OpenGLGL_EXT_YUV_target扩展的YUV采样器(支撑直接YUV插值),留意运用要判别手机是否支撑GL_EXT_YUV_target扩展。

第四步: GL_EXT_YUV_target扩展只支撑BT709YUV转RGB,需求经过BT2020YUV转RGB矩阵转化成归一化RGB。

计划三解码出16位YUV420Buffer再用Shader转化

这种方式是略繁琐的需求处理许多逻辑,不过也由于代码是自己写的,作用可控兼容性最高。

HDR转SDR实践之旅(二)解码10位YUV纹理

过程1:解码16位YUV420ByteBuffer

HDR转SDR实践之旅(二)解码10位YUV纹理

补白:

  1. 解码到Buffer时configure第二个参数要传null,解码到Surface时传Surface
  2. 装备KEY_COLOR_FORMAT标识视频是什么色彩格局,仅仅一个标识不是说装备什么格局就一定输出什么格局,大部分情况视频是什么格局就会输出什么格局,不传也不行部分手机会溃散,android13以后COLOR_FormatYUVP010代表10位YUV420,13以前用COLOR_FormatYUV420Flexible代表了4种8位YUV420格局。
  3. 解码出来的数据纷歧定是16位还有可能是8位,8位纷歧定表明硬件不支撑HDR,仅仅手机内部运用特别的ColorFormat标识成8位就输出8位Buffer,测试发现解出16位的手机大都是晓龙中高端机,华为的麒麟芯片不支撑,暂时没有办法绕过,可是就像前面说的16位YUV小端取原数据高8位归一化和16位YUV归一化相差很少。YUV的位数可以经过bufferSizewidth⋅height\frac{bufferSize}{width \cdot height}来判别,8位YUV420是1.5倍,16位YUV420是3倍。算起来很简略,YUV每个通道表明1字节(8位)的话,UV通道两个方向都会减小了一半,最终8位YUV是1+14⋅21+\frac{1}{4}\cdot 2=1.5,16位天然要乘以2等于3喽。

过程2:生成16位YUV420纹路

HDR转SDR实践之旅(二)解码10位YUV纹理

无法运用OpenGL扩展的情况下,调用glTexImage2D上传YUV420Buffer到GL_R16UI格局纹路上,GL_R16UI格局纹路与YUV420中的位置一一对应。惯例做法是把YUV420Buffer拆成y、u、v三个buffer分别上传到三个纹路,为了便利加快处理直接把buffer上传,惯例做法之所以拆成多个是由于YUV420的YUV数据混在一起插值会导致数据过错。

  1. texImage2D办法表明把某格局的Buffer数据上传到某格局的纹路上
  2. GL_R16UI表明纹路格局,R表明RGB三位都是R值,16表明像素16位,U表明unsigned无符号,I表明Integer整形不归一化,也便是16位无符号量化纹路
  3. GL_RED_INTEGERGL_UNSIGNED_SHORT表明Buffer的像素格局,GL_RED_INTEGER表明每个像素都是R值,GL_UNSIGNED_SHORT表明无符号Short也便是2字节一像素。
  4. 纹路的宽设为strideWidth/2,由于MediaCodec的strideWidth表明字节宽度而不是像素宽度,要从strideWidth得到纹路的宽就需求除以字节巨细,16位YUV是2字节一像素就除以2
  5. 纹路的高设为info.size/stridewidth, 由于视频的高并不是YUV420的高,Buffer巨细是纹路的字节宽度和纹路高的乘积,要从Buffer巨细得到纹路高只要除以纹路的字节宽度就可以。比如视频宽1280高720,字节巨细是1280∗720∗31280*720*3通道,strideWidth是1280∗21280*2字节,那么纹路的宽便是strideWidth/2=1280∗2/2=1280strideWidth/2=1280*2/2=1280,纹路的高便是size/strideWidth=1280∗720∗3/(1280∗2)=720∗1.5=1080size/strideWidth = 1280*720*3/(1280*2)= 720*1.5 =1080

过程3:16位YUV420纹路转16位YUV纹路

YUV420是为了传输YUV减小巨细创造的,天然可以用YUV420转YUV公式转化。留意转化之前要根据Android视频的色彩格局判别是哪种YUV420,Android 色彩格局中整理出色彩格局和YUV420的对应联系如下所示。

Android色彩格局 YUV420
COLOR_FormatYUV420Planar 8位i420
COLOR_FormatYUV420PackedPlanar 8位 YV12
COLOR_FormatYUV420SemiPlanar 8位 NV12
COLOR_FormatYUV420PackedSemiPlanar 8位 NV21
HAL_PIXEL_FORMAT_YCbCr_420_P010
HAL_PIXEL_FORMAT_YCbCr_420_P010_UBWC
HAL_PIXEL_FORMAT_YCbCr_420_P010_VENUS
COLOR_FormatYUVP010
HAL_PIXEL_FORMAT_YCbCr_420_P010_UBWC
10位 NV12

HDR转SDR实践之旅(二)解码10位YUV纹理

过程4:16位YUV纹路转归一化RGB纹路

HDR转SDR实践之旅(二)解码10位YUV纹理
先右移6位把16位YUV转成10位YUV,然后用YUV转RGB矩阵把10位YUV转化成10位RGB,10位RGB再归一化就可以

留意:

  1. YUV转RGB矩阵要留意判别YUV的规模和色域不能直接从网络上仿制,不同矩阵针对不同色域用错会呈现色差
  2. 10位RGB归一化除以1023而不是1024,由于色彩是从0-1023而不是0-1024

问题思考

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

  1. 怎样处理部分手机MediaCodec不支撑解码16位YUV420Buffer?

  2. 怎样打印OpenGL Shader内纹路数据验证samplerExternalOESYUV转RGB的色域支撑情况?

  3. YUV和YCbCr有什么区别?

  4. 怎样完成加快4种YUV420格局转YUV速度?

  5. 不同规模和色域下YUV转RGB矩阵不相同,怎样推导或许找到正确的公式?

  6. samplerExternalOES内部是怎样完成YUV转RGB、位数和色域支撑情况怎样?

系列文章

  • 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逆色彩映射探究