CVPixelBuffer 在音视频编解码以及图像处理过程当中应用普遍,有时须要读取内部数据,不多的时候须要自行建立并填充数据,下面简单叙述。ios
建立时调用的方法主要是这个:ide
CVReturn CVPixelBufferCreate(CFAllocatorRef allocator, size_t width, size_t height, OSType pixelFormatType, CFDictionaryRef pixelBufferAttributes, CVPixelBufferRef _Nullable *pixelBufferOut);
提供必须的参数便可,函数
XX pixelFormatType 经常使用的这几个:ui
/* NV12 */ kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange = '420v', /* Bi-Planar Component Y'CbCr 8-bit 4:2:0, video-range (luma=[16,235] chroma=[16,240]). baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct */ kCVPixelFormatType_420YpCbCr8BiPlanarFullRange = '420f', /* Bi-Planar Component Y'CbCr 8-bit 4:2:0, full-range (luma=[0,255] chroma=[1,255]). baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct */ /* YUV420P */ kCVPixelFormatType_420YpCbCr8Planar = 'y420', /* Planar Component Y'CbCr 8-bit 4:2:0. baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrPlanar struct */
由于我想要建立NV12格式的buffer,因此没有使用那个直接提供数据的建立函数,后续提供。若是数据格式爲420P的话,直接指定数据地址也能够。this
XX pixelBufferAttributescode
这个参数是optinal的,提供全部额外的信息。Core Video根据提供的参数来建立合适的数据,我看到网上的代码每每是这样提供的:orm
NSDictionary *pixelAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{}};
说明以下:视频
Provide a value for this key if you want Core Video to use the IOSurface framework to allocate the pixel buffer. (See IOSurface.) Provide an empty dictionary to use default IOSurface options.
以 NV12 格式的数据填充举例说明。对象
在访问buffer内部裸数据的地址時(读或写都同样),须要先将其锁上,用完了再放开,以下:图片
CVPixelBufferLockBaseAddress(pixelBuffer, 0); // To touch the address of pixel... CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
Y通道(Luminance)与 UV通道(Chrominance)分开填充数据,并且须要注意后者是UV交错排列的。在填充数据時还须要考虑到数据对齐的问题,当视频帧的宽高并非某个对齐基数的倍数時(好比16),内部具体如何分配内存是不肯定的,保险的作法就是逐行数据填充。这里我放上填充Chrominance通道数据的例子:
size_t bytesPerRowChrominance = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); long chrominanceWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); long chrominanceHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); // Chrominance uint8_t *uvDestPlane = (uint8_t *) CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); memset(uvDestPlane, 0x80, chrominanceHeight * bytesPerRowChrominance); for (int row = 0; row < chrominanceHeight; ++row) { memcpy(uvDestPlane + row * bytesPerRowChrominance, uvDataPtr + row * _outVideoWidth, _outVideoWidth); } free(uvDataPtr);
在逐行copy数据的时候,pixel内部地址每一个循环步进 current_row * bytesPerRowChrominance
的大小,这是pixelbuffer内部的内存排列。而后个人数据来源内存排列是紧密排列不考虑内存多少位对齐的问题的,因此每次的步进是 current_row * _outVideoWidth
也就是真正的视频帧的宽度。每次copy的大小也应该是真正的宽度。对于这个通道来讲,宽度和高度都是亮度通道的一半,每一个元素有UV两个信息,因此这个通道每一行占用空间和亮度通道应该是同样的。也就是每一行copy数据的大小是这样算出来的:_outVideoWidth / 2 * 2
.
数据读取和数据填充正好是相反的操做,操做流程类似,先获取pixelBuffer的一些具体信息,判断信息无误后继续读取数据。
unsigned long planes = CVPixelBufferGetPlaneCount(pixelRef);
若通道数目错误显然逻辑已经错误,无需继续。一样是先锁住BaseAddress,而后获取其bytesPerRowChrominance等信息,而后按行读取数据便可。切记,仍然须要按行读取数据。
直接附上可运行的代码:
size_t height; size_t width; NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey, [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, nil]; CVPixelBufferRef pxbuffer = NULL; CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options, &pxbuffer); NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); CVPixelBufferLockBaseAddress(pxbuffer, 0); void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer); NSParameterAssert(pxdata != NULL); CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(pxdata, width, height, 8, 4 * width, rgbColorSpace, kCGImageAlphaNoneSkipFirst); NSParameterAssert(context); CGContextConcatCTM(context, CGAffineTransformMakeRotation(0)); CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image); CGColorSpaceRelease(rgbColorSpace); CGContextRelease(context); CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
建立格式爲 kCVPixelFormatType_32ARGB
的 pixelBuffer,建立一个CGContextRef 对象,并将其内部地址设置爲pixelBuffer的内部地址。使用 CGContextDrawImage()
函数将原始图片的数据绘制到咱们建立的context上面,完成。
参考资料:
大神的文章,很详细:读写CVPixelBufferRef
Create CVPixelBuffer from YUV with IOSurface backed