什么是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的缩写。
下图所示是电视机上YCbCr的接口,只接Y显示黑白。
UV是蓝赤色度,那么绿色去哪里了?绿色在Y通道里,如下图所示Y通道便是一定份额的红绿蓝混合而成。为什么一定份额的红绿蓝混合变成了黑白色,由于红绿蓝光的份额尽管不同,可是三个感光细胞的电脉冲信号强度相同,电脉冲信号强度相同看起来便是黑白色。不是说RGB三光混合变成了白光而是眼睛看起来像白光,这也是为什么Y亮度更适合叫做Y明度的原因,明度比亮度更适合表明生理感触(为了便利,后续Y仍是叫做亮度)。
MediaCodec解码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补在后面导致数据左移需求右移回来。
如上图所示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
第一步: 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
留意:
第一步: 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转化
这种方式是略繁琐的需求处理许多逻辑,不过也由于代码是自己写的,作用可控兼容性最高。
过程1:解码16位YUV420ByteBuffer
补白:
- 解码到Buffer时configure第二个参数要传null,解码到Surface时传Surface
- 装备KEY_COLOR_FORMAT标识视频是什么色彩格局,仅仅一个标识不是说装备什么格局就一定输出什么格局,大部分情况视频是什么格局就会输出什么格局,不传也不行部分手机会溃散,android13以后COLOR_FormatYUVP010代表10位YUV420,13以前用COLOR_FormatYUV420Flexible代表了4种8位YUV420格局。
- 解码出来的数据纷歧定是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纹路
无法运用OpenGL扩展的情况下,调用glTexImage2D上传YUV420Buffer到GL_R16UI格局纹路上,GL_R16UI格局纹路与YUV420中的位置一一对应。惯例做法是把YUV420Buffer拆成y、u、v三个buffer分别上传到三个纹路,为了便利加快处理直接把buffer上传,惯例做法之所以拆成多个是由于YUV420的YUV数据混在一起插值会导致数据过错。
- texImage2D办法表明把某格局的Buffer数据上传到某格局的纹路上
- GL_R16UI表明纹路格局,R表明RGB三位都是R值,16表明像素16位,U表明unsigned无符号,I表明Integer整形不归一化,也便是16位无符号量化纹路
- GL_RED_INTEGER、GL_UNSIGNED_SHORT表明Buffer的像素格局,GL_RED_INTEGER表明每个像素都是R值,GL_UNSIGNED_SHORT表明无符号Short也便是2字节一像素。
- 纹路的宽设为strideWidth/2,由于MediaCodec的strideWidth表明字节宽度而不是像素宽度,要从strideWidth得到纹路的宽就需求除以字节巨细,16位YUV是2字节一像素就除以2
- 纹路的高设为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 |
过程4:16位YUV纹路转归一化RGB纹路
先右移6位把16位YUV转成10位YUV,然后用YUV转RGB矩阵把10位YUV转化成10位RGB,10位RGB再归一化就可以
留意:
- YUV转RGB矩阵要留意判别YUV的规模和色域不能直接从网络上仿制,不同矩阵针对不同色域用错会呈现色差
- 10位RGB归一化除以1023而不是1024,由于色彩是从0-1023而不是0-1024
问题思考
下面6个问题留给大家思考
-
怎样处理部分手机MediaCodec不支撑解码16位YUV420Buffer?
-
怎样打印OpenGL Shader内纹路数据验证samplerExternalOESYUV转RGB的色域支撑情况?
-
YUV和YCbCr有什么区别?
-
怎样完成加快4种YUV420格局转YUV速度?
-
不同规模和色域下YUV转RGB矩阵不相同,怎样推导或许找到正确的公式?
-
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逆色彩映射探究