赞
踩
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的缩写。

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

UV是蓝红色度,那么绿色去哪里了?绿色在Y通道里,如下图所示Y通道就是一定比例的红绿蓝混合而成。为什么一定比例的红绿蓝混合变成了黑白色,因为红绿蓝光的比例虽然不同,但是三个感光细胞的电脉冲信号强度一样,电脉冲信号强度一样看起来就是黑白色。不是说RGB三光混合变成了白光而是眼睛看起来像白光,这也是为什么Y亮度更适合叫做Y明度的原因,明度比亮度更适合表示生理感受(为了方便,后续Y还是叫做亮度)。


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位归一化后最多只差 3 1024 \frac{3}{1024} 10243也可视为支持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的Buffer数据是用16位存储的,还要实现16位YUV转10位YUV,因为字节对齐后处理方便还能加快读取速度,10位也就是1.25字节,对齐后就是2字节(16位)。10位变16位多出来的位数补0就可以了,大端情况下0补在前面数据不会发生变化,小端情况下0补在后面导致数据左移需要右移回来。

如上图所示YUV16位十进制479大端情况下还是479,小端右边补6位变成30656(479*2^6),16位YUV变成10位YUV需要右移6位去除0。上图中看到正常的小端数据和YUV的小端数据是不一样的,那么为什么YUV小端不用正常小端存储,这样不是更简单吗,我的猜测是为了保证16位数据被当成8位归一化和16位直接归一化的数据差距最小(YUV小端右边8位保留着原数据的高8位丢弃低位归一化后和16位归一化最多就差
3
1024
\frac{3}{1024}
10243)。
OpenGL本身不支持YUV纹理只支持RGB纹理需要特殊扩展或者自己处理,下面3个方案为了方便用伪代码讲解。

第一步: 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。

注意:
第一步: 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。
这种方式是略繁琐的需要处理很多逻辑,不过也因为代码是自己写的,效果可控兼容性最高。


备注:

无法使用OpenGL扩展的情况下,调用glTexImage2D上传YUV420Buffer到GL_R16UI格式纹理上,GL_R16UI格式纹理与YUV420中的位置一一对应。常规做法是把YUV420Buffer拆成y、u、v三个buffer分别上传到三个纹理,为了方便加速处理直接把buffer上传,常规做法之所以拆成多个是因为YUV420的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 |


先右移6位把16位YUV转成10位YUV,然后用YUV转RGB矩阵把10位YUV转换成10位RGB,10位RGB再归一化就可以
注意:
下面6个问题留给大家思考
怎么解决部分手机MediaCodec不支持解码16位YUV420Buffer?
如何打印OpenGL Shader内纹理数据验证samplerExternalOESYUV转RGB的色域支持情况?
YUV和YCbCr有什么区别?
如何实现加快4种YUV420格式转YUV速度?
不同范围和色域下YUV转RGB矩阵不一样,怎么推导或者找到正确的公式?
samplerExternalOES内部是如何实现YUV转RGB、位数和色域支持情况如何?
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。