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

iOS Accelerate:将亮度/色度缓冲区存入CVPixelBuffer时数据丢失问题

问题分析与解决方案

核心问题:内存提前释放

你的代码里defer块会在函数返回前调用lumaDestination.free()和chromaDestination.free(),直接释放vImage缓冲区的内存。而CVPixelBufferCreateWithPlanarBytes默认不会拷贝传入的外部内存,仅持有指针引用。当函数返回时,vImage的内存已经被释放,返回的CVPixelBuffer里的平面地址自然为空或无效。

其他关键错误

  1. 色度缓冲区宽度错误
    420YpCbCr格式的色度(CbCr)平面宽度应为亮度(Y)平面的一半,你代码里chromaWidth = source.width是错误的,正确值应为source.width / 2。

  2. CVPixelBuffer平面高度配置错误
    对于kCVPixelFormatType_420YpCbCr8BiPlanarFullRange格式:

    • 第一个平面(Y)的高度等于原图像高度(lumaHeight)
    • 第二个平面(CbCr)的高度才是原图像高度的一半(chromaHeight)
      你代码里planeHeights = [Int(chromaHeight), Int(chromaHeight)]把Y平面高度也设成了色度高度,完全不符合格式要求。
  3. 字节行宽配置问题
    色度平面的字节行宽应为chromaWidth * 2(每个CbCr像素占2字节),但由于之前的chromaWidth计算错误,这个值也跟着出错了。

修复后的代码

func convertTo420Yp8(source: inout vImage_Buffer) -> CVPixelBuffer? {
    let lumaWidth = source.width
    let lumaHeight = source.height
    
    // 修正:色度宽度为亮度宽度的一半
    let chromaWidth = source.width / 2
    let chromaHeight = source.height / 2
    
    guard var lumaDestination = try? vImage_Buffer(
        width: Int(lumaWidth),
        height: Int(lumaHeight),
        bitsPerPixel: 8
    ) else {
        return nil
    }
    
    guard var chromaDestination = try? vImage_Buffer(
        width: Int(chromaWidth),
        height: Int(chromaHeight),
        bitsPerPixel: 8
    ) else {
        return nil
    }
    
    var error = kvImageNoError
    error = vImageConvert_ARGB8888To420Yp8_CbCr8(
        &source,
        &lumaDestination,
        &chromaDestination,
        &infoARGBtoYpCbCr,
        nil,
        vImage_Flags(kvImagePrintDiagnosticsToConsole)
    )
    
    guard error == kvImageNoError else {
        // 出错时手动释放内存
        lumaDestination.free()
        chromaDestination.free()
        return nil
    }
    
    let pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
    // 修正:平面高度配置
    let planeWidths = [Int(lumaWidth), Int(chromaWidth)]
    let planeHeights = [Int(lumaHeight), Int(chromaHeight)]
    // 修正:字节行宽
    let bytesPerRows = [Int(lumaWidth), Int(chromaWidth * 2)]
    let baseAddresses: [UnsafeMutableRawPointer?] = [lumaDestination.data, chromaDestination.data]
    var outputPixelBuffer: CVPixelBuffer?
    
    // 定义释放回调,让CVPixelBuffer销毁时自动释放vImage内存
    let releaseCallback: CVPixelBufferReleaseBytesCallback = { _, data, _ in
        guard let data = data else { return }
        let lumaBuffer = vImage_Buffer(data: data, width: lumaWidth, height: lumaHeight, bitsPerPixel: 8)
        let chromaBuffer = vImage_Buffer(data: data.advanced(by: lumaWidth * lumaHeight), width: chromaWidth, height: chromaHeight, bitsPerPixel: 8)
        lumaBuffer.free()
        chromaBuffer.free()
    }
    
    let totalSize = lumaWidth * lumaHeight + chromaWidth * chromaHeight * 2
    let status = CVPixelBufferCreateWithPlanarBytes(
        kCFAllocatorDefault,
        Int(lumaWidth),
        Int(lumaHeight),
        pixelFormat,
        lumaDestination.data,
        totalSize,
        2,
        baseAddresses,
        planeWidths,
        planeHeights,
        bytesPerRows,
        releaseCallback,
        nil,
        nil,
        &outputPixelBuffer
    )
    
    if status != noErr {
        // 创建失败时手动释放内存
        lumaDestination.free()
        chromaDestination.free()
    }
    
    return outputPixelBuffer
}

修复说明

  1. 内存管理修正:移除原defer自动释放,改用CVPixelBufferReleaseBytesCallback回调,让CVPixelBuffer在自身销毁时自动释放vImage内存,确保内存生命周期与CVPixelBuffer一致。
  2. 格式参数修正:修复了色度宽度、平面高度、字节行宽的配置,完全符合目标格式规范。
  3. 错误处理优化:在转换或CVPixelBuffer创建失败时,手动释放vImage内存,避免泄漏。

内容的提问来源于stack exchange,提问作者Grzegorz Baczek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 04:25:30