Swift中CMBlockBuffer内存所有权管理及相关崩溃问题如何解决?
Swift中CMBlockBuffer内存所有权正确处理方案
崩溃根因
你遇到的崩溃本质是CMBlockBuffer和Swift Data的内存所有权边界没有处理清楚:
- 你通过
(dataCopy as NSData).bytes拿到的指针生命周期完全绑定到dataCopy变量,当变量出作用域被Swift自动释放后,CMBlockBuffer引用的就是野指针。 - 你给
blockAllocator传kCFAllocatorDefault时,CMBlockBuffer销毁时会尝试调用free释放传入的内存块,但这块内存是SwiftData托管的,不是单独malloc出来的内存块,就会触发double free或指针未分配报错。 - 用
kCFAllocatorNull虽然可以避免系统自动释放内存,但你需要手动持有Data直到CMBlockBuffer完全销毁,手动维护全局存储很难判断释放时机,很容易出问题。
推荐解决方案
方案1:让CMBlockBuffer完全托管内存(最简单,无手动内存管理)
让系统自动分配CMBlockBuffer的内部内存,你只需要把Data的内容拷贝进去即可,后续内存完全由CMBlockBuffer自己管理,不需要外部持有任何数据:
let dataCopy = dataFromStream let count = dataCopy.count var blockBuffer: CMBlockBuffer? // 传nil给memoryBlock让系统自动分配对应大小的内存 let createStatus = CMBlockBufferCreateWithMemoryBlock( allocator: kCFAllocatorDefault, memoryBlock: nil, blockLength: count, blockAllocator: kCFAllocatorDefault, customBlockSource: nil, offsetToData: 0, dataLength: count, flags: 0, blockBufferOut: &blockBuffer ) guard createStatus == noErr, let blockBuffer = blockBuffer else { // 处理创建失败逻辑 return } // 将Data内容拷贝到CMBlockBuffer的内部内存中 dataCopy.withUnsafeBytes { bufPtr in CMBlockBufferReplaceDataBytes( with: bufPtr.baseAddress!, blockBuffer: blockBuffer, offsetIntoDestination: 0, dataLength: count ) } // 后续正常创建CMSampleBuffer、送入AVSampleBufferDisplayLayer即可 // 不需要再持有dataCopy,当CMSampleBuffer被消费释放时,CMBlockBuffer会自动释放自己的内存 var sampleBuffer: CMSampleBuffer? var sampleSize = count let sampleStatus = CMSampleBufferCreate( allocator: kCFAllocatorDefault, dataBuffer: blockBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: myFormatDescription, sampleCount: 1, sampleTimingEntryCount: 0, sampleTimingArray: nil, sampleSizeEntryCount: 1, sampleSizeArray: &sampleSize, sampleBufferOut: &sampleBuffer ) guard sampleStatus == noErr, let sampleBuffer = sampleBuffer else { return } myVideoLayer.enqueue(sampleBuffer)
这个方案实现简单,出错概率极低,适合绝大多数普通场景,唯一的开销是一次内存拷贝,对性能影响可以忽略。
方案2:零拷贝绑定Data生命周期(高性能场景)
如果要避免内存拷贝,追求极致性能,可以通过CMBlockBuffer的自定义释放回调,把Data的生命周期和CMBlockBuffer绑定,CMBlockBuffer销毁时自动释放对应的Data:
let data = dataFromStream let count = data.count // 将Data持有为Unmanaged指针,作为上下文传递给释放回调 let unmanagedData = Unmanaged.passRetained(data as NSData) var customBlockSource = CMBlockBufferCustomBlockSource() customBlockSource.version = kCMBlockBufferCustomBlockSourceVersionCurrent customBlockSource.refCon = UnsafeMutableRawPointer(unmanagedData.toOpaque()) // 自定义释放回调,CMBlockBuffer销毁时自动调用 customBlockSource.FreeBlock = { refCon, _, _ in guard let refCon = refCon else { return } Unmanaged<NSData>.fromOpaque(refCon).release() } var blockBuffer: CMBlockBuffer? let createStatus = CMBlockBufferCreateWithMemoryBlock( allocator: kCFAllocatorDefault, memoryBlock: UnsafeMutableRawPointer(mutating: (data as NSData).bytes), blockLength: count, blockAllocator: kCFAllocatorNull, // 禁止系统自动释放内存,走自定义回调 customBlockSource: &customBlockSource, offsetToData: 0, dataLength: count, flags: 0, blockBufferOut: &blockBuffer ) guard createStatus == noErr, let blockBuffer = blockBuffer else { // 创建失败要手动释放之前retain的Data,避免内存泄漏 unmanagedData.release() return } // 后续创建CMSampleBuffer、送入渲染层的逻辑和方案1一致 // 不需要外部持有data,CMBlockBuffer销毁时会自动触发回调释放Data
这个方案没有内存拷贝开销,适合高帧率、大分辨率的实时音视频场景,也不需要手动维护全局存储。
原代码问题说明
你之前用全局数组存储Data的方案,本质是没有把Data的生命周期和CMBlockBuffer绑定,无法准确判断AVSampleBufferDisplayLayer什么时候会消费完CMSampleBuffer,所以很难确定Data的释放时机,很容易出现提前释放或者内存泄漏的问题,不推荐使用。
内容的提问来源于stack exchange,提问作者Hundley
相关产品推荐
相关产品推荐

