先說結論:
? ? ?CVPixelBufferCreate 創建的 CVPixelBufferRef 可能由以下的原因導致的:
1.pixelFormatType 格式錯誤,換一下格式嘗試
2.width和height 非 32 的整數倍
3.視頻幀的寬高比非標準比例(4:3,16:9,1:1)
另外說明,我沒找到比較有權威的對應文檔和教程,上面是我通過測試得出的結論,如果有錯誤,還請批評指正
一、pixelFormatType 格式錯誤?
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 是nv12 的格式 yyyyuvuv
kCVPixelFormatType_420YpCbCr8Planar 是i420的格式 yyyyuuvv
需要注意的是,并不是每一個枚舉值在iOS上都是支持的,可能存在不支持的情況
二、width和height 非 32 的整數倍
CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,1920,1080,kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, // NV12(__bridge CFDictionaryRef)(pixelAttributes),&pixelBuffer);
像素的存儲格式通常是一行一行存儲的,當寬度為了實現32的整數倍做出調整時,從幀數據復制的時候,就要在多出的那部分做對應的填充。縮小也是一樣的,要去放棄多余的數據
比如原視頻是 寬4 高4的視頻,你調成寬8高8的時候
yyyy? ? ? ? ? ? ? ? ? ? ? ? yyyyyyyy (后面四個y是自己填充的)
yyyy? ? ? ? ? ? ? ? ? ? ? ? yyyyyyyy (后面四個y是自己填充的)
uuuu? ? ?->>? ? ? ? ? ? ?uuuuuuuu (后面四個u是自己填充的)
vvvv? ? ? ? ? ? ? ? ? ? ? ? vvvvvvvv?(后面四個v是自己填充的)
例子可能不太恰當,但是意思比較清楚的。后面會有代碼舉例
三、視頻幀的寬高比非標準比例(4:3,16:9,1:1)
這個解決辦法和上一條是一樣的,去調整寬高,來實現比例的要求。
例子:
//例子是 I420 數據轉成 NV12 的 CVPixelBufferRef
//width 原視頻的寬
//height 原視頻的高
//buffer NSData 類型的原視頻的幀數據。格式為I420NSDictionary *pixelAttributes = @{(NSString *)kCVPixelBufferIOSurfacePropertiesKey:@{}};//調整后的widthint inner_width = 0;//調整后的heightint inner_height = 0;//視頻比例不是 16:9 , 4:3 , 1:1 ,寬高不是32的整數倍時,將寬高統一調整為1920和1080.//因為項目要求,所有視頻都不會比1920x1080大,故這里只是放大,無需縮小if (width*1.0 / height*1.0 != 16.0/9 || width*1.0 / height*1.0 != 1.0 || width*1.0 / height*1.0 != 4.0/3 || width%32 != 0 || height%32 != 0 ) {inner_width = 1920;inner_height = 1080;}else{inner_width = width;inner_height = height;}CVPixelBufferRef pixelBuffer = NULL;CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,inner_width,inner_height,kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, // NV12(__bridge CFDictionaryRef)(pixelAttributes),&pixelBuffer);if (result != kCVReturnSuccess) {//CVPixelBufferRef 初始化失敗NSLog(@"Unable to create cvpixelbuffer %d", result);}// 復制 buffer.bytes 數據uint8_t* pdata = (uint8_t*)buffer.bytes;CVPixelBufferLockBaseAddress(pixelBuffer, 0);// Y 數據填充unsigned char *yDestPlane = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);for (int i = 0, k = 0; i < inner_height; i++) {for (int j = 0; j< inner_width; j++) {if (inner_width > width && j >= width) {// 擴大的那部分 width 用該行最后一個像素填充if (i >= height) {// 高度大于視頻的實際高度,說明該行沒有幀數據,則用實際視頻幀的最后一行最后一個像素填充yDestPlane[k++] = pdata[height*width];continue;}yDestPlane[k++] = pdata[i*width + width];continue;}if (inner_height > height && i >= height) {// 高度大于視頻的實際高度,說明改行沒有幀數據,則用實際視頻幀的最后一行最后一個像素填充yDestPlane[k++] = pdata[height * width];continue;}yDestPlane[k++] = pdata[i*width + j];}}// UV 數據填充unsigned char *uvDestPlane = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);for (int i = 0, k = 0; i < inner_height / 2; i++) {for (int j = 0; j< inner_width / 2; j ++) {if (inner_width > width && j >= width/2) {if (i > height/2) {// 高度大于視頻的實際高度,說明改行沒有幀數據,則用實際視頻幀的最后一行最后一個像素填充uvDestPlane[k++] = pdata[height *width/4 + width * height];uvDestPlane[k++] = pdata[height *width/4 + width * height * 5 / 4];}else{// 高度不高于視頻的實際高度,說明改行有幀數據,則用實際視頻幀的該行最后一個像素填充uvDestPlane[k++] = pdata[i *width/2 + width/2 + width * height];uvDestPlane[k++] = pdata[i *width/2 + width/2 + width * height * 5 / 4];}continue;}if (inner_height > height && i >= height/2) {// 高度大于視頻的實際高度,說明改行沒有幀數據,則用實際視頻幀的最后一行最后一個像素填充uvDestPlane[k++] = pdata[height *width/4 + width * height];uvDestPlane[k++] = pdata[height *width/4 + width * height * 5 / 4];continue;}// 高度不高于視頻的實際高度,說明改行有幀數據,則用實際視頻幀像素填充uvDestPlane[k++] = pdata[i *width/2 + j + width * height];uvDestPlane[k++] = pdata[i *width/2 + j + width * height * 5 / 4];}}CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
//pixelBuffer 構造完成,用于后續使用