Swift中AVFoundation视频帧多线程处理实现 解决预览卡顿问题
iOS AVFoundation 多线程帧处理方案落地实现
核心问题分析
你当前的卡顿主要来自两个原因:
- 摄像头采集回调直接绑定了主队列,所有帧转换、处理逻辑都占用主线程资源
- 帧处理耗时过长阻塞主线程,导致UI渲染、系统响应都被卡住
改造方案实现
我们通过「采集队列生产帧→缓冲队列控流→处理队列消费帧→主队列回调UI」的四层结构实现多线程方案,完全避免主线程阻塞。
第一步:改造CaptureManager,新增队列与缓冲配置
首先在CaptureManager类中新增私有属性,替换原有采集队列:
class CaptureManager: NSObject { internal static let shared = CaptureManager() weak var delegate: CaptureManagerDelegate? var session: AVCaptureSession? var isBackCamera = true // 新增属性 private let frameBuffer = NSMutableArray() private let bufferLock = NSLock() private let maxBufferSize = 2 // 缓冲队列最多存2帧,避免内存溢出和画面延迟 private let captureQueue = DispatchQueue(label: "com.yourapp.capture_serial_queue") // 采集专用队列 private let processQueue = DispatchQueue(label: "com.yourapp.process_serial_queue") // 处理专用队列 private let callbackQueue = DispatchQueue.main // UI回调专用队列 // ... 原有其他属性保持不变
然后修改init方法中采集输出的队列绑定,把原来的DispatchQueue.main替换为自定义的采集队列:
// 原来的代码 // output.setSampleBufferDelegate(self, queue: DispatchQueue.main) // 替换为 output.setSampleBufferDelegate(self, queue: captureQueue)
第二步:实现帧生产消费逻辑
改造captureOutput代理方法作为生产者,同时新增消费逻辑:
extension CaptureManager: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // 先 retain sampleBuffer 避免被系统提前释放 guard let buffer = CMSampleBufferCreateCopy(allocator: kCFAllocatorDefault, sampleBuffer: sampleBuffer) else { return } bufferLock.lock() // 超过缓冲上限就丢弃最早的帧 if frameBuffer.count >= maxBufferSize { let oldBuffer = frameBuffer.removeObject(at: 0) as! CMSampleBuffer CFRelease(oldBuffer) } frameBuffer.add(buffer) bufferLock.unlock() // 唤醒处理队列消费帧 processQueue.async { [weak self] in self?.consumeFrame() } } // 帧消费逻辑 private func consumeFrame() { bufferLock.lock() guard frameBuffer.count > 0 else { bufferLock.unlock() return } let buffer = frameBuffer.removeObject(at: 0) as! CMSampleBuffer bufferLock.unlock() // 帧转换、处理逻辑全部在子线程完成 guard let outputImage = getImageFromSampleBuffer(sampleBuffer: buffer) else { CFRelease(buffer) return } CFRelease(buffer) // 处理完成后回调到主队列更新UI callbackQueue.async { [weak self] in self?.delegate?.processCapturedImage(image: outputImage) } } }
第三步:优化停止会话逻辑,清空缓冲避免内存泄漏
在stopSession方法中补充缓冲队列清空逻辑:
func stopSession() { session?.stopRunning() bufferLock.lock() frameBuffer.forEach { buffer in CFRelease(buffer as! CMSampleBuffer) } frameBuffer.removeAllObjects() bufferLock.unlock() }
第四步:ViewController层代码无需大幅修改
原有processCapturedImage回调已经自动跑在主队列,直接更新UI即可,重处理逻辑建议提前放到consumeFrame方法中完成,避免占用主线程:
extension ViewController: CaptureManagerDelegate { func processCapturedImage(image: UIImage) { self.imageView.image = image // 仅保留极轻量的UI操作 } }
优化建议
- 如果不需要严格保证帧处理顺序,可以把
processQueue改成并发队列提升处理速度,但需要额外处理帧顺序错乱的问题 - 缓冲队列大小
maxBufferSize根据你的处理耗时调整,建议不要超过3,否则会出现明显的画面延迟 - 如果不需要保留原始sampleBuffer,也可以在采集队列直接转成UIImage再存入缓冲队列,省去手动管理CF对象的步骤
内容的提问来源于stack exchange,提问作者Parham Khamsepour
相关产品推荐
相关产品推荐

