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

Swift中CMBlockBuffer内存所有权管理及相关崩溃问题如何解决?

Swift中CMBlockBuffer内存所有权正确处理方案

崩溃根因

你遇到的崩溃本质是CMBlockBuffer和Swift Data的内存所有权边界没有处理清楚:

  1. 你通过(dataCopy as NSData).bytes拿到的指针生命周期完全绑定到dataCopy变量,当变量出作用域被Swift自动释放后,CMBlockBuffer引用的就是野指针。
  2. 你给blockAllocator传kCFAllocatorDefault时,CMBlockBuffer销毁时会尝试调用free释放传入的内存块,但这块内存是Swift Data托管的,不是单独malloc出来的内存块,就会触发double free或指针未分配报错。
  3. 用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 04:06:02