AVFoundation存储实时CMSampleBuffer到数组时回调停止问题
解决AVCaptureVideoDataOutput缓冲区耗尽导致回调停止的问题
问题背景
修改苹果AVCam示例代码,想要存储最新的20个CMSampleBuffer到数组中。实现AVCaptureVideoDataOutputSampleBufferDelegate协议、创建并连接VideoDataOutput并设置代理后,在回调中直接将sampleBuffer存入数组:
var bufferArray:[CMSampleBuffer] = [] func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { if bufferArray.count > 20{ bufferArray.removeFirst() } bufferArray.append(sampleBuffer) }
但回调执行5-10次后就停止,同时其他相机应用预览出现卡顿。仅持有单个buffer引用时一切正常,调整捕获设置无效。后续在didDrop回调中发现报错:DroppedFrameReason(P) = OutOfBuffers,对应苹果文档说明:
提供sample buffer的模块已耗尽源缓冲区。此情况通常由客户端长时间持有缓冲区导致,可通过将缓冲区返回给提供者缓解。
问题根源
CMSampleBuffer是AVFoundation内部缓冲区池管理的对象,直接将原始sampleBuffer存入数组会持续持有其引用,导致AVFoundation无法回收这些缓冲区进行复用。当缓冲区池被耗尽后,AVFoundation就会停止触发didOutput回调,同时出现丢帧、其他相机应用卡顿的情况。
而仅持有单个buffer时,新的buffer会覆盖旧的引用,旧的buffer引用释放后能被AVFoundation回收,因此不会出现缓冲区耗尽问题。
解决方案
核心是不要直接持有原始CMSampleBuffer,而是创建副本存入数组,同时及时释放原始buffer归还到缓冲区池:
代码实现
var bufferArray:[CMSampleBuffer] = [] func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // 创建原始buffer的副本,保留数据内容 var copiedBuffer: CMSampleBuffer? CMSampleBufferCreateCopy(nil, sampleBuffer, &copiedBuffer) guard let validCopiedBuffer = copiedBuffer else { // 副本创建失败时,直接释放原始buffer CMSampleBufferInvalidate(sampleBuffer) return } // 维护数组最多保留20个副本,同时释放旧的副本 if bufferArray.count >= 20 { let oldBuffer = bufferArray.removeFirst() CMSampleBufferInvalidate(oldBuffer) } bufferArray.append(validCopiedBuffer) // 释放原始buffer,归还到AVFoundation的缓冲区池 CMSampleBufferInvalidate(sampleBuffer) }
关键说明
- 使用
CMSampleBufferCreateCopy创建副本:数组中存储独立的buffer副本,不会占用AVFoundation的缓冲区池资源。 - 调用
CMSampleBufferInvalidate释放资源:无论是原始buffer还是不再需要的副本,调用该方法可释放对应资源,避免内存泄漏和缓冲区池耗尽。 - 控制数组容量:当元素超过20个时,移除最旧的副本并释放资源,保证内存占用可控。
内容的提问来源于stack exchange,提问作者Perry Wang
相关产品推荐
相关产品推荐

