理解AVCaptureSession中时间对CMSampleBuffers的作用及同步方法
关于iOS相机预录10秒功能的音视频时序与缓冲管理方案
核心机制澄清
AVCaptureSession的synchronizationClock是音视频同步的核心——所有输出的CMSampleBuffer的时间戳都基于这个系统维护的时钟生成,音视频流的时间线天然对齐,不需要手动设置或替换这个时钟。你不用纠结如何修改捕获会话的时间尺度,依赖这个时钟的时间戳就能保证同步。
样本缓冲的时长控制与对应关系
视频缓冲(60fps单帧)
你可以通过配置AVCaptureVideoDataOutput的videoSettings强制输出60fps的单帧样本:
videoOutput.videoSettings = [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, AVVideoWidthKey as String: 1920, AVVideoHeightKey as String: 1080, AVVideoFrameRateKey as String: 60 ]
此时每个视频CMSampleBuffer包含1帧,时长固定为1/60秒,可通过以下代码验证:
let duration = CMSampleBufferGetDuration(videoSampleBuffer) let durationSeconds = CMTimeGetSeconds(duration) // 输出约0.016666秒
音频缓冲的适配
音频缓冲的时长由硬件采样率和输出缓冲区大小决定(比如48kHz采样率下,800样本的缓冲时长约为800/48000≈0.0167秒),无法强行设置为1/60秒。但你不需要追求音视频缓冲的数量一一对应,只要基于synchronizationClock的时间戳来对齐时间线即可。
预录10秒的缓冲管理方案
不要用简单数组存储所有缓冲(会导致内存暴涨),建议基于时间戳维护带过期清理的队列:
- 为音视频分别创建线程安全的队列(比如
DispatchQueue保护的[CMSampleBuffer]) - 每次收到新样本时,先记录其
presentationTimeStamp,然后过滤队列中所有时间戳早于当前时间戳 - 10秒的缓冲:
let currentTime = CMSampleBufferGetPresentationTimeStamp(newSample) let tenSecondsAgo = CMTimeSubtract(currentTime, CMTimeMakeWithSeconds(10, preferredTimescale: 600)) // 清理视频队列 videoBufferQueue = videoBufferQueue.filter { CMSampleBufferGetPresentationTimeStamp($0) >= tenSecondsAgo } // 音频队列执行同样的清理逻辑 audioBufferQueue = audioBufferQueue.filter { CMSampleBufferGetPresentationTimeStamp($0) >= tenSecondsAgo }
- 注意:存储
CMSampleBuffer时需要调用CMSampleBufferMakeDataReady并保留引用,避免缓冲区被提前释放。
音视频写入AVAssetWriter的同步要点
- 启动
AVAssetWriter时,确保使用与捕获会话一致的时间基准:
assetWriter.startWriting() assetWriter.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(firstVideoSample))
- 分别将音视频队列中的样本写入对应的
AVAssetWriterInput,不需要刻意匹配缓冲数量——只要所有样本的时间戳基于synchronizationClock,AVAssetWriter会自动处理音视频同步。
关键误解纠正
- 不需要手动设置捕获会话的时间尺度或CMClock,系统会自动维护
synchronizationClock保证音视频时间线对齐; - 音视频缓冲的时长不需要严格相等,时间戳才是同步的核心依据;
- 预录功能更优的实现是用临时文件存储(而非内存队列),比如持续向
AVAssetWriter写入临时文件,录制时截取最后10秒片段,避免内存压力。
内容的提问来源于stack exchange,提问作者casvandergun
相关产品推荐
相关产品推荐

