如何使用AVAssetWriter实现音视频同步?解决音频暂停后不同步问题
解决方案:音频暂停恢复后音视频不同步问题
问题根源
当AVAssetWriterInput的expectsMediaDataInRealTime设为true时,AVAssetWriter会按媒体数据的写入顺序生成时间线,而非严格遵循样本的presentationTimeStamp。暂停音频期间,视频持续写入导致时间线推进,但音频输入无数据写入;恢复后,新的音频样本会被直接追加到暂停前音频的总时长后,最终造成音视频时间线错位。你打印的样本presentationTime正确,但实时模式下AVAssetWriter并未使用该时间戳定位音频。
可行解决方法
1. 关闭音频输入的实时媒体数据预期
创建音频AVAssetWriterInput时,将expectsMediaDataInRealTime设为false,让AVAssetWriter严格按照样本的presentationTimeStamp放置音频数据:
let audioOutputSettings: [String: Any] = [ AVFormatIDKey: kAudioFormatMPEG4AAC, AVSampleRateKey: 44100, AVNumberOfChannelsKey: 2, AVEncoderBitRateKey: 128000 ] let audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioOutputSettings) audioInput.expectsMediaDataInRealTime = false // 关键设置
此方案无需修改现有样本处理逻辑,恢复后带有正确时间戳的音频会自动匹配视频时间线。
2. 实时模式下手动调整音频样本时间戳
若业务需求必须保留实时模式,需记录暂停期间的时间偏移量,修改恢复后所有音频样本的时间戳:
- 暂停开始时,记录当前最新的视频帧
presentationTimeStamp(比如lastVideoPauseTime) - 恢复音频时,记录当前最新的视频帧时间戳,计算偏移量:
timeOffset = currentVideoTime - lastVideoPauseTime - 对恢复后的每个音频样本,调整其
presentationTimeStamp和decodeTimeStamp
时间戳调整代码示例:
func adjustSampleBufferTime(_ buffer: CMSampleBuffer, timeOffset: CMTime) -> CMSampleBuffer? { var timingInfo = CMSampleTimingInfo() guard CMSampleBufferGetSampleTimingInfo(buffer, at: 0, timingInfoOut: &timingInfo) == noErr else { return nil } // 叠加时间偏移量 timingInfo.presentationTimeStamp = CMTimeAdd(timingInfo.presentationTimeStamp, timeOffset) timingInfo.decodeTimeStamp = CMTimeAdd(timingInfo.decodeTimeStamp, timeOffset) var adjustedBuffer: CMSampleBuffer? let status = CMSampleBufferCreateCopyWithNewTiming( allocator: kCFAllocatorDefault, sampleBuffer: buffer, sampleTimingEntryCount: 1, sampleTimingArray: &timingInfo, sampleBufferOut: &adjustedBuffer ) return status == noErr ? adjustedBuffer : nil }
在音频append逻辑中加入调整:
case .audio: if audioInput?.isReadyForMoreMediaData == true { guard buffer.dataBuffer != nil else { return } processQueue.async { [self] in var targetBuffer = buffer // 若存在未应用的时间偏移,调整样本时间戳 if let offset = self.audioTimeOffset { guard let adjusted = adjustSampleBufferTime(buffer, timeOffset: offset) else { return } targetBuffer = adjusted } audioInput?.append(targetBuffer) } }
3. 优化静音样本方案
如果继续使用写入静音样本的方式,可批量生成对应暂停时长的静音音频帧,减少频繁append的性能损耗:
- 根据暂停时长、音频采样率、通道数,计算所需静音样本的帧数量
- 生成包含静音数据的
CMSampleBuffer,一次性写入到音频输入
内容的提问来源于stack exchange,提问作者LiYanan2004
相关产品推荐
相关产品推荐

