在之前的Metal与图形渲染三:透明通道视频已经对YUV有所简介,但当时也是大概了解了YUV的格式,直接套用了YUV转RGB的公式,其实对YUV的数据格式还不是太了解,这次来深入一下对YUV420格式的学习。
一. YUV420简介
YUV使用亮度(Y)和色度(UV)来指定一个像素的颜色,由于人眼对色度不敏感,因此一个Y可以对应多个UV,以减少数据的带宽占用。如果单独只有Y,那么图像是能正常显示的,但只有黑白的颜色。下图分别代表正常图像、Y值、U值和V值。
YUV420是指YUV以4:2:0的方式进行采样,在每一行扫描时,只扫描一种色度分量(U 或者 V),和 Y 分量按照 2 : 1 的方式采样。参考文章这个例子非常清晰易懂
举个例子 :
假设图像像素为:
[Y0 U0 V0]、[Y1 U1 V1]、 [Y2 U2 V2]、 [Y3 U3 V3]
[Y5 U5 V5]、[Y6 U6 V6]、 [Y7 U7 V7] 、[Y8 U8 V8]
那么采样的码流为:Y0 U0 Y1 Y2 U2 Y3 Y5 V5 Y6 Y7 V7 Y8
其中,每采样过一个像素点,都会采样其 Y 分量,而 U、V 分量就会间隔一行按照 2 : 1 进行采样。
最后映射出的像素点为:
[Y0 U0 V5]、[Y1 U0 V5]、[Y2 U2 V7]、[Y3 U2 V7]
[Y5 U0 V5]、[Y6 U0 V5]、[Y7 U2 V7]、[Y8 U2 V7]
下图叉叉代表Y分量,圆圈代表UV分量,可以看到每4个Y分量共用一组UV分量,也就是420格式了。
二. YUV420采样格式
YUV的存储格式包括Planar和Packed,Planar先连续存储所有像素点的 Y 分量,然后存储 U 分量,最后是 V 分量。Packed连续交替存储每个像素点的YUV分量。Planar和Packed在代码上的区别就是,Planar会将YUV数据分别存于多个数组,而Packed会将数据存在一个数组里面。
YUV420采样格式包括YUV420P和YUV420SP,格式都是Planar的,区别在于 YUV420P (420 Planar)类型就会先存储所有的 U 分量或者 V 分量,而 YUV420SP (YUV420 Semi Planar)则是按照 UV 或者 VU 的交替顺序进行存储。
下图是YUV420P格式:
下图是YUV420SP格式:
可以看出,YUV420P和YUV420SP的区别在于:
YUV420SP的UV分量存放在一个数组里面,该数组的宽度和Y分量的宽度相等,而高度为Y分量的一半,总共用了两个数组存放YUV数据,即两平面。在iOS开发中,形成CVPixelBuffer的数据结构。我们之前根据CVPixelBuffer渲染视频RGB就是这个格式。
- (id )textureWithPixelBuffer:(CVMetalTextureRef)pixelBuffer pixelFormat:(MTLPixelFormat)pixelFormat planeIndex:(NSInteger)planeIndex {
id texture = nil;
// planeIndex为0时是Y分量数据,planeIndex为1时是UV分量数据
size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex);
size_t height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex);
CVMetalTextureRef textureRef = NULL;
CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, _textureCache, pixelBuffer, NULL, pixelFormat, width, height, planeIndex, &textureRef);
if (status == kCVReturnSuccess) {
texture = CVMetalTextureGetTexture(textureRef);
CFRelease(textureRef);
} else {
texture = nil;
}
return texture;
}
渲染时也会将UV作为一个纹理进行转换。
float3 rgbFromYuv(float2 textureCoor,
texture2d textureY,
texture2d textureUV,
constant CCAlphaVideoMetalConvertMatrix *convertMatrix) {
constexpr sampler textureSampler (mag_filter::linear,
min_filter::linear);
float3 yuv = float3(textureY.sample(textureSampler, textureCoor).r,
textureUV.sample(textureSampler, textureCoor).rg);
return convertMatrix->matrix * (yuv + convertMatrix->offset);
}
fragment float4 movieFragment(SingleInputVertexIO input [[ stage_in ]],
texture2d textureY [[ texture(0) ]],
texture2d textureUV [[ texture(1) ]],
constant CCAlphaVideoMetalConvertMatrix *convertMatrix [[ buffer(0) ]]) {
float3 rgb = rgbFromYuv(input.textureCoordinate, textureY, textureUV, convertMatrix);
return float4(rgb, 1.0);
}
YUV420P的UV分量分别存放于两个数组,U分量数组、V分量数组的宽度为Y分量的一半,高度也为Y分量的一半,总共用了三个数组存放YUV数据,即三平面。渲染时需要将YUV分别作为三个纹理进行输入。
fragment float4 movieByPixelsFragment(SingleInputVertexIO input [[ stage_in ]],
texture2d textureY [[ texture(0) ]],
texture2d textureU [[ texture(1) ]],
texture2d textureV [[ texture(2) ]],
constant CCAlphaVideoMetalConvertMatrix *convertMatrix [[ buffer(0) ]]) {
float2 textureCoor = input.textureCoordinate;
constexpr sampler textureSampler (mag_filter::linear,
min_filter::linear);
float y = textureY.sample(textureSampler, textureCoor).r;
float u = textureU.sample(textureSampler, textureCoor).r;
float v = textureV.sample(textureSampler, textureCoor).r;
float3 yuv = float3(y, u, v);
float3 rgb = convertMatrix->matrix * (yuv + convertMatrix->offset);
return float4(rgb, 1.0);
}
三. YUV420的具体格式内容
YUV420P包括YU12(也称I420P)和YV12,其区别在于,YU12按Y、U、V的顺序存储,而UV12按Y、V、U的顺序存储。
YUV420SP包括NV12和NV21,同理,其区别在于,NV12按Y、UV分量的格式存储,而NV21按Y、VU分量的格式存储。值得一提的是,iOS采样格式为NV12,Android采样格式为NV21。
用参考文章的图总结下格式:
四. YUV420的数据内容
一个YUV数据并不一定全是图像数据,也有可能是一些填充的内容,需要确保是按16位对齐的,即为16的倍数。
Padding用于16位对齐的填充,Pitch(也称为Stride)为YUV数据的行字节数(BytesPerRow),因此,Width不一定等于Pitch,Pitch为16的倍数,但Width不一定为16的倍数,此外,Height维度是不会有填充的,这点在YUV数据渲染或复制时需要注意。
下面四个方法返回的内容各不相同,需要注意区分,特别是Pitch和Width的区别:
// Width
CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex)
// Pitch
CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, planeIndex)
// Height
CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex)
// Base Address
CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, planeIndex)
五. 总结
YUV420采样时,四个Y分量共用1对UV分量。
YUV420P和YUV420SP的区别在于前者有三个平面(U和V各为一个平面,宽度和高度均为Y平面的1/2),后者有两个平面(U和V处于一个平面,宽度和Y平面相等,高度为Y平面的1/2)。
YU12和YV12、NV12和NV21的区别在于取样先U后V还是先V后U。
YUV420可能会有填充数据,需要注意Pitch和Width的区别。
六. 参考
一文读懂 YUV 的采样与格式
YUV数据采集存储及部分转换原理
YUV格式