You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 22:46:05