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