iOS Accelerate:将亮度/色度缓冲区存入CVPixelBuffer时数据丢失问题
问题分析与解决方案
核心问题:内存提前释放
你的代码里defer块会在函数返回前调用lumaDestination.free()和chromaDestination.free(),直接释放vImage缓冲区的内存。而CVPixelBufferCreateWithPlanarBytes默认不会拷贝传入的外部内存,仅持有指针引用。当函数返回时,vImage的内存已经被释放,返回的CVPixelBuffer里的平面地址自然为空或无效。
其他关键错误
色度缓冲区宽度错误
420YpCbCr格式的色度(CbCr)平面宽度应为亮度(Y)平面的一半,你代码里chromaWidth = source.width是错误的,正确值应为source.width / 2。CVPixelBuffer平面高度配置错误
对于kCVPixelFormatType_420YpCbCr8BiPlanarFullRange格式:- 第一个平面(Y)的高度等于原图像高度(
lumaHeight) - 第二个平面(CbCr)的高度才是原图像高度的一半(
chromaHeight)
你代码里planeHeights = [Int(chromaHeight), Int(chromaHeight)]把Y平面高度也设成了色度高度,完全不符合格式要求。
- 第一个平面(Y)的高度等于原图像高度(
字节行宽配置问题
色度平面的字节行宽应为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 }
修复说明
- 内存管理修正:移除原
defer自动释放,改用CVPixelBufferReleaseBytesCallback回调,让CVPixelBuffer在自身销毁时自动释放vImage内存,确保内存生命周期与CVPixelBuffer一致。 - 格式参数修正:修复了色度宽度、平面高度、字节行宽的配置,完全符合目标格式规范。
- 错误处理优化:在转换或CVPixelBuffer创建失败时,手动释放vImage内存,避免泄漏。
内容的提问来源于stack exchange,提问作者Grzegorz Baczek
相关产品推荐
相关产品推荐

