@qidiandasheng
2020-07-01T11:12:42.000000Z
字数 4121
阅读 2862
音视频
视频是由一帧一帧的数据连接而成,而一帧视频数据其实就是一张图片。
yuv是一种图片储存格式,跟RGB格式类似。
RGB格式的图片很好理解,计算机中的大多数图片,都是以RGB格式存储的。
yuv中,y表示亮度,单独只有y数据就可以形成一张图片,只不过这张图片是灰色的。u和v表示色差(u和v也被称为:Cb-蓝色差,Cr-红色差),
YUV格式有两大类:planar和packed。
平面格式(planar formats) :对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V,如 YYYY YYYY UU VV。
紧缩格式(packed formats):对于packed的YUV格式,每个像素点的Y,U,V是连续交替存储的,如YUV YUV YUV YUV,这种排列方式跟 RGB 很类似。
有一定历史原因,最早的电视信号,为了兼容黑白电视,采用的就是yuv格式。
一张yuv的图像,去掉uv,只保留y,这张图片就是黑白的。
而且yuv可以通过抛弃色差来进行带宽优化。
比如YUV420格式图像相比RGB来说,要节省一半的字节大小,抛弃相邻的色差对于人眼来说,差别不大。
从下图可以看到一个U分量为Y分量的四分之一,V分量为Y分量的四分之一。也就是说4个像素yuv420格式为6个字节,而RGB为12个字节。
将一张图片的Y、U、V数据单独显示就会如下图所示:
YUV的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0。
以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量
4:4:4表示完全取样,每个像素点都采样单独的 YUV 分量信息,包含了最全面的 YUV 信息。占用字节数跟RGB一样。
在 YUV444 编码的基础上采用 2 * 1 的矩阵进行二次取样,也就是在水平方向上隔一列采样一次 UV 信息,在垂直方向上进行完全取样,每两个Y共用一组UV分量。
占用字节数为(width * height + (width * height) / 2 + (width * height) / 2) = (width * height) * 2
,是RGB的三分之二。
YUV422根据数据排列分为以下几种格式:
YUYV格式
字节排列 : YUYV YUYV YUYV YUYV
UYVY 格式
字节排列 : UYVY UYVY UYVY UYVY
YUV422P格式
字节排列 :YYYY YYYY UUUU VVVV
YUV420分为两种:YUV420p
和YUV420sp
:
YUV420p
:又叫planer平面模式,Y ,U,V分别再不同平面,也就是有三个平面。根据排列方式不同分为以下两种格式。
- I420格式
y,u,v 3个部分分别存储:YYYY YYYY | UU | VV- YV12格式
y,u,v 3个部分分别存储:YYYY YYYY | VV | UU
YUV420sp
:又叫bi-planer或two-planer双平面,Y一个平面,UV在同一个平面交叉存储。根据排列方式不同分为以下两种格式。
- NV12格式(iOS的格式)
y和uv 2个部分分别存储:YYYY YYYY | VU VU- NV21格式(Android的格式)
y和uv 2个部分分别存储:YYYY YYYY | UV UV
搜索PixelFormatType
的类型里420能发现以下几种格式中:
kCVPixelFormatType_420YpCbCr8Planar
kCVPixelFormatType_420YpCbCr8PlanarFullRange
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
根据表面意思,可以看出,可以分为两类:planar(平面420p)和 BiPlanar(双平面420sp)。iOS只支持NV12
,也就是双平面kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
和kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
。所以在iOS里我们就需要进行一个格式的转换把三平面转换成双平面来渲染显示。
主要是三个planar(平面)转为两个planar(平面)
使用iOS三方库libyuv
+ (CVPixelBufferRef)convertVideoSmapleBufferToNV12:(CVPixelBufferRef)pixelBuffer{
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
//图像宽度(像素)
size_t pixelWidth = CVPixelBufferGetWidth(pixelBuffer);
//图像高度(像素)
size_t pixelHeight = CVPixelBufferGetHeight(pixelBuffer);
//获取CVImageBufferRef中的y数据
uint8_t *y_frame = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
//获取CMVImageBufferRef中的u数据
uint8_t *u_frame =(unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
//获取CMVImageBufferRef中的v数据
uint8_t *v_frame =(unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 2);
//y stride
size_t plane1_stride = CVPixelBufferGetBytesPerRowOfPlane (pixelBuffer, 0);
//u stride
size_t plane2_stride = CVPixelBufferGetBytesPerRowOfPlane (pixelBuffer, 1);
//v stride
size_t plane3_stride = CVPixelBufferGetBytesPerRowOfPlane (pixelBuffer, 2);
size_t plane1_size = plane1_stride * CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
size_t plane2_size = plane2_stride * CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
size_t plane3_size = plane3_stride * CVPixelBufferGetHeightOfPlane(pixelBuffer, 2);
size_t frame_size = plane1_size + plane2_size + plane3_size;
uint8* buffer = (unsigned char *)malloc(frame_size);
uint8* dst_uv = buffer + plane1_size;
libyuv::I420ToNV12(y_frame, plane1_stride,
u_frame, plane2_stride,
v_frame, plane3_stride,
buffer, plane1_stride,
dst_uv, plane2_stride+plane3_stride,
pixelWidth, pixelHeight);
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
//转化
NSDictionary *pixelAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{}};
CVPixelBufferRef pixelBuffer1 = NULL;
CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,
pixelWidth,pixelHeight,kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
(__bridge CFDictionaryRef)pixelAttributes,&pixelBuffer1);
CVPixelBufferLockBaseAddress(pixelBuffer1, 0);
uint8_t *yDestPlane = (uint8*)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer1, 0);
memcpy(yDestPlane, buffer, pixelWidth * pixelHeight);
uint8_t *uvDestPlane = (uint8*)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer1, 1);
memcpy(uvDestPlane, dst_uv, pixelWidth * pixelHeight/2);
if (result != kCVReturnSuccess) {
NSLog(@"Unable to create cvpixelbuffer %d", result);
}
CVPixelBufferUnlockBaseAddress(pixelBuffer1, 0);
free(buffer);
return pixelBuffer1;
}