CVPixelBufferRef YUV NV12 pixelFormatType 創建 數據填充 數據讀取 CGImageRef to CVPixelBufferRef ...
CVPixelBuffer的創建數據填充以及數據讀取
CVPixelBuffer 在音視頻編解碼以及圖像處理過程中應用廣泛,有時需要讀取內部數據,很少的時候需要自行創建並填充數據,下麵簡單敘述。
創建
創建時調用的方法主要是這個:
CVReturn CVPixelBufferCreate(CFAllocatorRef allocator,
size_t width,
size_t height,
OSType pixelFormatType,
CFDictionaryRef pixelBufferAttributes,
CVPixelBufferRef _Nullable *pixelBufferOut);
提供必須的參數即可,
XX pixelFormatType 常用的這幾個:
/* 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的話,直接指定數據地址也可以。
XX pixelBufferAttributes
這個參數是optinal的,提供所有額外的信息。Core Video根據提供的參數來創建合適的數據,我看到網上的代碼往往是這樣提供的:
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等信息,然後按行讀取數據即可。切記,仍然需要按行讀取數據。
補充:從 Image 創建 PixelBuffer
直接附上可運行的代碼:
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
How to convert from YUV to CIImage for iOS
How do I export UIImage array as a movie?