You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:17:26