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
相关产品推荐
相关产品推荐

