YUV420数据格式学习(420P和420SP)

在之前的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 <MTLTexture>)textureWithPixelBuffer:(CVMetalTextureRef)pixelBuffer pixelFormat:(MTLPixelFormat)pixelFormat planeIndex:(NSInteger)planeIndex {
    id <MTLTexture> 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 <float> textureY,
                  texture2d <float> 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 <float> textureY [[ texture(0) ]],
                               texture2d <float> 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 <float> textureY [[ texture(0) ]],
                                      texture2d <float> textureU [[ texture(1) ]],
                                      texture2d <float> 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格式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,198评论 6 514
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,334评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,643评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,495评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,502评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,156评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,743评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,659评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,200评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,282评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,424评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,107评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,789评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,264评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,390评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,798评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,435评论 2 359

推荐阅读更多精彩内容