关于CVPixelBufferCreate忽略平面格式及'420f'图像旋转的技术问题
解决CoreVideo '420f'(NV12)图像旋转时的行字节数与vImage处理问题
我刚好处理过类似的YUV半平面旋转场景,咱们一步步拆解问题来解决:
1. 先搞懂输入buffer的bytesPerRow = width + 32是什么情况
你遇到的Y平面bytesPerRow = width + 32,本质是因为输入的CMSampleBuffer对应的CVPixelBuffer带有**CVPlanarComponentInfo元数据**——这部分数据占32字节,系统在计算行字节数时把这部分额外算进去了。而你直接调用CVPixelBufferCreate()时,默认不会自动添加这部分元数据,所以才会出现行字节数不匹配的问题。
解决方案:手动指定目标buffer的行字节数
创建目标CVPixelBuffer时,不能依赖系统自动计算bytesPerRow,要手动匹配输入的参数:
// 先获取输入buffer的参数 CVPixelBufferRef inputBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); size_t yWidth = CVPixelBufferGetWidthOfPlane(inputBuffer, 0); size_t yHeight = CVPixelBufferGetHeightOfPlane(inputBuffer, 0); size_t yBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(inputBuffer, 0); // 这里是width +32 size_t uvWidth = CVPixelBufferGetWidthOfPlane(inputBuffer, 1); size_t uvHeight = CVPixelBufferGetHeightOfPlane(inputBuffer, 1); size_t uvBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(inputBuffer, 1); // 创建目标buffer时,指定每个平面的bytesPerRow NSDictionary *bufferAttributes = @{ (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange), (id)kCVPixelBufferWidthKey: @(yWidth), (id)kCVPixelBufferHeightKey: @(yHeight), (id)kCVPixelBufferBytesPerRowCallbackKey: ^size_t(size_t planeIndex) { if (planeIndex == 0) { return yBytesPerRow; // 匹配输入的Y行字节数(width+32) } else { return uvBytesPerRow; // 匹配输入的UV行字节数 } } }; CVPixelBufferRef outputBuffer = NULL; OSStatus status = CVPixelBufferCreate(kCFAllocatorDefault, yWidth, yHeight, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, (__bridge CFDictionaryRef)bufferAttributes, &outputBuffer);
2. 用vImage处理'420f'图像的旋转(无需转RGBA)
vImage_Buffer确实没有bitsPerPixel参数,但我们可以针对Y和UV平面的不同格式分别处理:
- Y平面是单通道8bit数据,用
vImageRotate_Planar8函数 - UV平面是NV12格式(U/V interleaved,每个像素16bit),用
vImageRotate_Planar16函数
具体步骤代码示例(以旋转90度为例):
// 锁定输入输出buffer的内存 CVPixelBufferLockBaseAddress(inputBuffer, 0); CVPixelBufferLockBaseAddress(outputBuffer, 0); // 初始化输入vImage_Buffer void *inputYData = CVPixelBufferGetBaseAddressOfPlane(inputBuffer, 0); vImage_Buffer inputYBuffer = { .data = inputYData, .height = yHeight, .width = yWidth, .rowBytes = yBytesPerRow }; void *inputUVData = CVPixelBufferGetBaseAddressOfPlane(inputBuffer, 1); vImage_Buffer inputUVBuffer = { .data = inputUVData, .height = uvHeight, .width = uvWidth, .rowBytes = uvBytesPerRow }; // 初始化输出vImage_Buffer(旋转90度后宽高互换) void *outputYData = CVPixelBufferGetBaseAddressOfPlane(outputBuffer, 0); vImage_Buffer outputYBuffer = { .data = outputYData, .height = yWidth, .width = yHeight, .rowBytes = CVPixelBufferGetBytesPerRowOfPlane(outputBuffer, 0) }; void *outputUVData = CVPixelBufferGetBaseAddressOfPlane(outputBuffer, 1); vImage_Buffer outputUVBuffer = { .data = outputUVData, .height = uvWidth, .width = uvHeight, .rowBytes = CVPixelBufferGetBytesPerRowOfPlane(outputBuffer, 1) }; // 设置旋转参数 vImage_Flags flags = kvImageBackgroundColorFill; Pixel_8 backgroundColorY = 0; // Y通道背景色(黑色) Pixel_16 backgroundColorUV = 0; // UV通道背景色(黑色) // 执行旋转 vImage_Error yError = vImageRotate_Planar8(&inputYBuffer, &outputYBuffer, NULL, M_PI_2, backgroundColorY, flags); vImage_Error uvError = vImageRotate_Planar16(&inputUVBuffer, &outputUVBuffer, NULL, M_PI_2, backgroundColorUV, flags); // 解锁内存 CVPixelBufferUnlockBaseAddress(inputBuffer, 0); CVPixelBufferUnlockBaseAddress(outputBuffer, 0); // 检查错误 if (yError != kvImageNoError || uvError != kvImageNoError) { NSLog(@"旋转失败:Y错误码%ld,UV错误码%ld", (long)yError, (long)uvError); // 错误处理逻辑 }
3. 关于CVPlanarComponentInfo元数据的注意事项
如果输入Y平面的额外32字节确实是CVPlanarComponentInfo,旋转后你可以选择:
- 保留原元数据:把输入每行末尾的32字节元数据,拷贝到输出Y平面的每行末尾
- 忽略/重新生成:如果不需要保留原元数据,直接留空或调用
CVPixelBufferSetPlanarComponentInfoArray()重新设置
多数场景下,只要保证输出buffer的bytesPerRow和输入一致,即使元数据为空,后续的CoreVideo/CoreMedia流程也能正常处理。
内容的提问来源于stack exchange,提问作者user1418067
相关产品推荐
相关产品推荐

