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

CustomVideoCompositorClass中CGContext绘制CGImage触发EXC_BAD_ACCESS错误

看起来你在自定义视频合成器的时候遇到了经典的内存访问错误,我之前也踩过类似的坑,给你分析下可能的原因,再分享几个更省心的视频叠加方案:

核心问题排查:EXC_BAD_ACCESS 原因分析

1. 像素缓冲区属性不匹配

自定义合成器的两个属性requiredPixelBufferAttributesForRenderContext和sourcePixelBufferAttributes必须保证像素格式、颜色空间等参数一致,否则在绘制时会因为上下文和源图像的格式不兼容导致内存崩溃。

给你一个经过验证的配置示例,尽量使用硬件兼容的格式:

class CustomComposition: NSObject, AVVideoCompositing {
    static var requiredPixelBufferAttributesForRenderContext: [String: Any] {
        return [
            kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
            kCVPixelBufferWidthKey as String: NSNumber(value: 1920), // 替换成你的目标宽度
            kCVPixelBufferHeightKey as String: NSNumber(value: 1080), // 替换成你的目标高度
            kCVPixelBufferMetalCompatibilityKey as String: true,
            kCVPixelBufferIOSurfacePropertiesKey as String: [:]
        ]
    }

    static var sourcePixelBufferAttributes: [String: Any]? {
        return [
            kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
            kCVPixelBufferIOSurfacePropertiesKey as String: [:]
        ]
    }

    // 其他协议实现代码...
}

2. CGDataProvider 生命周期问题

你怀疑的createSourceImage方法确实是高风险点,如果手动创建CGDataProvider时没有正确管理内存,比如CVPixelBuffer的内存提前被释放,就会导致CGImage引用野指针,调用draw时崩溃。

不要手动创建CGDataProvider,改用CoreImage的方式从CVPixelBuffer生成CGImage,CoreImage会自动处理内存管理:

func createSourceImage(from pixelBuffer: CVPixelBuffer) -> CGImage? {
    let ciImage = CIImage(cvImageBuffer: pixelBuffer)
    // 尽量用共享的CIContext,避免重复创建
    let context = CIContext(options: [.useSoftwareRenderer: false, .cacheIntermediates: true])
    return context.createCGImage(ciImage, from: ciImage.extent)
}

3. CGContext 状态未正确初始化

调用draw前,确保CGContext的状态是正常的,比如设置正确的颜色空间、混合模式,避免上下文处于异常状态:

func renderFrame(context: AVVideoCompositionRenderContext) {
    guard let gc = context.renderContext else { return }
    let outputRect = context.renderContextBounds
    
    // 先清空上下文
    gc.setFillColor(UIColor.clear.cgColor)
    gc.fill(outputRect)
    gc.setBlendMode(.normal)
    
    // 设置和CGImage匹配的颜色空间
    if let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) {
        gc.setColorSpace(colorSpace)
    }
    
    // 再绘制前后图像
    if let backImage = createSourceImage(from: backPixelBuffer) {
        gc.draw(backImage, in: outputRect)
    }
    if let frontImage = createSourceImage(from: frontPixelBuffer) {
        gc.draw(frontImage, in: CGRect(x: 100, y: 100, width: 300, height: 500))
    }
}
更简单的视频叠加替代方案

自定义AVVideoCompositing虽然灵活,但对内存管理要求极高,推荐优先使用以下上层API:

1. 用AVVideoCompositionLayerInstruction直接叠加(适合导出视频)

这是苹果官方推荐的视频叠加方式,不需要手动绘制,直接给轨道设置变换:

// 假设你已经获取了大视频轨道largeTrack和小视频轨道smallTrack
let videoComposition = AVMutableVideoComposition()
videoComposition.renderSize = CGSize(width: 1920, height: 1080)
videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)

// 创建合成指令
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(start: .zero, duration: largeTrack.asset!.duration)

// 大视频轨道指令(默认全屏)
let largeLayerInst = AVMutableVideoCompositionLayerInstruction(assetTrack: largeTrack)
// 小视频轨道指令:缩放到右上角
let smallTransform = CGAffineTransform(scaleX: 0.3, y: 0.3)
    .translatedBy(x: 1920 - 0.3*smallTrack.naturalSize.width, y: 20)
let smallLayerInst = AVMutableVideoCompositionLayerInstruction(assetTrack: smallTrack)
smallLayerInst.setTransform(smallTransform, at: .zero)

instruction.layerInstructions = [largeLayerInst, smallLayerInst]
videoComposition.instructions = [instruction]

2. 用AVPlayerLayer叠加(适合实时播放)

如果只是播放时显示叠加效果,不需要导出,直接用图层叠加最方便:

// 大视频播放器
let largePlayer = AVPlayer(url: largeVideoURL)
let largePlayerLayer = AVPlayerLayer(player: largePlayer)
largePlayerLayer.frame = view.bounds
view.layer.addSublayer(largePlayerLayer)

// 小视频播放器
let smallPlayer = AVPlayer(url: smallVideoURL)
let smallPlayerLayer = AVPlayerLayer(player: smallPlayer)
// 设置小视频位置:右上角
smallPlayerLayer.frame = CGRect(x: view.bounds.width - 150, y: 20, width: 120, height: 213)
largePlayerLayer.addSublayer(smallPlayerLayer)

// 同步播放
largePlayer.play()
smallPlayer.play()

3. CoreImage滤镜合成(适合复杂特效)

如果需要添加透明度、混合模式等特效,可以用CoreImage的CISourceOverCompositing滤镜:

func composeFrames(largeBuffer: CVPixelBuffer, smallBuffer: CVPixelBuffer) -> CVPixelBuffer? {
    let largeCI = CIImage(cvImageBuffer: largeBuffer)
    let smallCI = CIImage(cvImageBuffer: smallBuffer)
        .transformed(by: CGAffineTransform(scaleX: 0.3, y: 0.3)
        .translatedBy(x: 1920 - 0.3*smallBuffer.width, y: 20))
    
    let filter = CIFilter(name: "CISourceOverCompositing")!
    filter.setValue(largeCI, forKey: kCIInputBackgroundImageKey)
    filter.setValue(smallCI, forKey: kCIInputImageKey)
    
    guard let outputCI = filter.outputImage else { return nil }
    let context = CIContext(options: [.useSoftwareRenderer: false])
    var outputBuffer: CVPixelBuffer?
    // 创建输出像素缓冲区
    CVPixelBufferCreate(kCFAllocatorDefault, 1920, 1080, kCVPixelFormatType_32BGRA, nil, &outputBuffer)
    if let buffer = outputBuffer {
        context.render(outputCI, to: buffer)
    }
    return outputBuffer
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 16:32:53