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

缩放CMSampleBuffer尺寸避免Broadcast Extension内存超限

崩溃原因排查

  • 内存管理缺失:CVPixelBuffer属于CoreFoundation类型,ARC不会自动管理其生命周期,你当前代码中从缓冲池申请的pixelBuffer、旧的缓冲池实例没有手动释放,会持续堆积内存;同时输入的原始CMSampleBuffer如果没有及时调用CMSampleBufferInvalidate释放,也会占用大量内存,而Broadcast Upload Extension的内存上限仅为50MB,很容易触发阈值崩溃。
  • CIContext配置不合理:默认CIContext会开启中间缓存,且如果没有指定Metal硬件加速,会 fallback 到CPU渲染,产生大量临时内存副本,进一步拉高内存占用。
  • 缩放方案开销过高:CILanczosScaleTransform属于高质量缩放滤镜,计算和内存开销都很大,不适合低内存限制的扩展环境;同时你全程使用CIImage做中转,会额外产生多份内存副本。
  • 缓冲池配置错误:你创建缓冲池时没有限制最大缓冲数量,系统会按需创建过多pixelBuffer,直接超过内存上限。
  • 复用逻辑存在隐患:你使用全局变量持有pixelBuffer复用,若OpenTok的consumeImageBuffer是异步处理逻辑,会出现你正在写入新帧、OpenTok还在读取旧帧的内存冲突,同时旧的pixelBuffer没有及时释放也会造成泄漏。

优化方案

1. 补全内存管理逻辑

首先实现完整的资源销毁方法,确保旧的缓冲池和pixelBuffer都被正确释放:

private func destroyPixelBuffers() {
    if let pixelBuffer = self.pixelBuffer {
        CVPixelBufferRelease(pixelBuffer)
        self.pixelBuffer = nil
    }
    if let pool = self.pixelBufferPool {
        CVPixelBufferPoolRelease(pool)
        self.pixelBufferPool = nil
    }
}

在processSampleBuffer中处理完CMSampleBuffer后立刻调用CMSampleBufferInvalidate(sampleBuffer)释放原始帧内存,不要长时间持有。

2. 优化CIContext配置

关闭中间缓存,优先使用Metal硬件加速:

lazy var context: CIContext = {
    guard let metalDevice = MTLCreateSystemDefaultDevice() else {
        return CIContext(options: [.useSoftwareRenderer: false, .cacheIntermediates: false])
    }
    return CIContext(mtlDevice: metalDevice, options: [.cacheIntermediates: false])
}()

3. 修正缓冲池配置,限制缓冲数量

创建缓冲池时添加缓冲数量限制,避免创建过多pixelBuffer:

private func updateBufferPool(newWidth: Int, newHeight: Int) {
    // 先销毁旧缓冲池
    destroyPixelBuffers()
    
    let pixelBufferAttributes: [String: Any] = [
        kCVPixelBufferPixelFormatTypeKey as String: UInt(self.videoFormat),
        kCVPixelBufferWidthKey as String: newWidth,
        kCVPixelBufferHeightKey as String: newHeight,
        kCVPixelBufferIOSurfacePropertiesKey as String: [:]
    ]
    // 限制缓冲池最多保留3个缓冲,满足双/三缓冲需求即可
    let poolAttributes: [String: Any] = [
        kCVPixelBufferPoolMaximumBufferCountKey as String: 3
    ]
    
    CVPixelBufferPoolCreate(nil, poolAttributes as NSDictionary, pixelBufferAttributes as NSDictionary, &pixelBufferPool)
}

4. 重构帧处理逻辑,添加自动释放池

每帧处理添加自动释放池,及时销毁临时对象,不要用全局pixelBuffer复用,避免内存冲突:

func processPixelBuffer(pixelBuffer:CVPixelBuffer, timeStamp ts:CMTime) {
    // 每帧加自动释放池,临时对象处理完立刻释放
    autoreleasepool {
        guard  let ciImage = self.scaleFilterImage(inputImage: pixelBuffer.cmIImage, withAspectRatio: 1.0, scale: CGFloat(kVideoFrameScaleFactor)) else {return}
        let targetWidth = Int(ciImage.extent.size.width)
        let targetHeight = Int(ciImage.extent.size.height)
        
        if self.pixelBufferPool == nil {
            self.updateBufferPool(newWidth: targetWidth, newHeight: targetHeight)
        }
        
        var outputPixelBuffer: CVPixelBuffer?
        guard CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, self.pixelBufferPool, &outputPixelBuffer) == kCVReturnSuccess, let outputBuffer = outputPixelBuffer else {return}
        
        // 渲染前后加锁,避免内存访问冲突
        CVPixelBufferLockBaseAddress(outputBuffer, [])
        context?.render(ciImage, to: outputBuffer)
        CVPixelBufferUnlockBaseAddress(outputBuffer, [])
        
        self.videoCaptureConsumer?.consumeImageBuffer(outputBuffer,
                                                      orientation:.up,
                                                      timestamp:ts,
                                                      metadata:nil)
        // 若consumeImageBuffer为异步持有buffer,需等OpenTok回调通知帧处理完成后再释放
        CVPixelBufferRelease(outputBuffer)
    }
}

5. 可选:替换为更轻量的缩放方案

如果调整后内存依然过高,可弃用CIFilter缩放,改用Accelerate框架的vImage接口做缩放,内存开销和计算速度都比CIFilter好很多,非常适合广播扩展场景。


内容的提问来源于stack exchange,提问作者Mehmet Baykar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 01:15:04