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

60FPS下向CMSampleBuffer插入图片时CIContext.render崩溃求助

60FPS下CIContext渲染CMSampleBuffer崩溃的解决方案

问题概述

将图片合成到CMSampleBuffer时,代码会随机在ciContext.render(compositedImage, to: outputBuffer)行触发坏内存访问崩溃,且仅在60FPS场景下出现,30FPS时无异常。函数由同一线程的代理直接调用,已尝试Metal/CPU版本CIContext及手动锁定解锁缓冲区操作。

核心问题与修复方案

1. 移除不必要的像素缓冲区手动锁定

Metal版本的CIContext会自动处理像素缓冲区的访问同步,手动锁定反而可能引发线程冲突,高帧率下更易触发崩溃。

// 删除以下两行代码
// let flag: CVPixelBufferLockFlags = CVPixelBufferLockFlags(rawValue: 0)
// CVPixelBufferLockBaseAddress(outputBuffer, flag)
ciContext.render(compositedImage, to: outputBuffer)
// CVPixelBufferUnlockBaseAddress(outputBuffer, flag)

2. 优化像素缓冲区生命周期管理

创建的像素缓冲区可能在CIContext渲染完成前被提前释放,60FPS下频繁的内存回收放大了这个问题。确保CMSampleBuffer持有缓冲区的强引用,或手动保留缓冲区:

guard let outputBuffer = newPixelBuffer else { return nil }
CVPixelBufferRetain(outputBuffer) // 手动保留缓冲区

// 执行渲染和CMSampleBuffer创建操作
ciContext.render(compositedImage, to: outputBuffer)

// 创建CMSampleBuffer后无需手动Release,CMSampleBuffer会管理引用

3. 简化像素缓冲区属性配置

冗余的IOSurface属性可能导致内存管理异常,去掉非必要的属性设置:

let pixelBufferAttrs = [
    kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA),
    kCVPixelBufferWidthKey as String: CVPixelBufferGetWidth(pixelBuffer),
    kCVPixelBufferHeightKey as String: CVPixelBufferGetHeight(pixelBuffer)
] as CFDictionary

4. 优化CIContext初始化配置

使用Metal设备和专用命令队列初始化CIContext,避免高帧率下的线程调度冲突:

// 全局初始化CIContext(不要在process函数内重复创建)
let metalDevice = MTLCreateSystemDefaultDevice()
let metalQueue = metalDevice?.makeCommandQueue()
let ciContext = CIContext(mtlDevice: metalDevice, commandQueue: metalQueue, options: [
    CIContextOption.useSoftwareRenderer: false,
    CIContextOption.highQualityDownsample: false
])

修改后的完整代码

// 全局初始化CIContext
let metalDevice = MTLCreateSystemDefaultDevice()
let metalQueue = metalDevice?.makeCommandQueue()
let ciContext = CIContext(mtlDevice: metalDevice, commandQueue: metalQueue, options: [
    CIContextOption.useSoftwareRenderer: false,
    CIContextOption.highQualityDownsample: false
])

func process(sampleBuffer: CMSampleBuffer) -> CMSampleBuffer? {
    guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
        return nil
    }

    let ciImage = CIImage(cvPixelBuffer: pixelBuffer).oriented(.up).clampedToExtent()
    let compositedImage = overlayImage.composited(over: ciImage)

    var newPixelBuffer: CVPixelBuffer?
    let pixelBufferAttrs = [
        kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA),
        kCVPixelBufferWidthKey as String: CVPixelBufferGetWidth(pixelBuffer),
        kCVPixelBufferHeightKey as String: CVPixelBufferGetHeight(pixelBuffer)
    ] as CFDictionary

    CVPixelBufferCreate(kCFAllocatorDefault,
                        CVPixelBufferGetWidth(pixelBuffer),
                        CVPixelBufferGetHeight(pixelBuffer),
                        kCVPixelFormatType_32BGRA,
                        pixelBufferAttrs,
                        &newPixelBuffer)

    guard let outputBuffer = newPixelBuffer else { return nil }
    CVPixelBufferRetain(outputBuffer)

    ciContext.render(compositedImage, to: outputBuffer)
    
    var timingInfo = CMSampleTimingInfo()
    CMSampleBufferGetSampleTimingInfo(sampleBuffer, at: 0, timingInfoOut: &timingInfo)

    var formatDescription: CMFormatDescription?
    CMVideoFormatDescriptionCreateForImageBuffer(allocator: kCFAllocatorDefault,
                                                 imageBuffer: outputBuffer,
                                                 formatDescriptionOut: &formatDescription)

    guard let newFormatDesc = formatDescription else {
        CVPixelBufferRelease(outputBuffer)
        return nil
    }

    var newSampleBuffer: CMSampleBuffer?
    CMSampleBufferCreateReadyWithImageBuffer(allocator: kCFAllocatorDefault,
                                             imageBuffer: outputBuffer,
                                             formatDescription: newFormatDesc,
                                             sampleTiming: &timingInfo,
                                             sampleBufferOut: &newSampleBuffer)

    return newSampleBuffer
}

内容的提问来源于stack exchange,提问作者Peter Lapisu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 00:16:04