前言
ReplayKit实现app内屏幕录制(非直播)虽然官方提供的api很简洁,但是留下了很多巨坑问题。其中最大的问题在于ReplayKit的架构设计层面就存在音视频不同步的可能性,只是大部分情况下音视频不同步的概率很低,所以通常你肉眼看不出来而已,但是特殊情况下音频与视频的时间戳差异会很大。
1. 为什么说ReplayKit的架构设计层面就存在音视频不同步的可能性呢?
首先我们要明白,音视频 = 音频 + 视频,这是两条独立的数据流管道。 它们各自拥有独立的时钟源和处理管线。
- 音频与视频硬件层面存在延迟差
- 麦克风 → Audio Unit → Core Audio → 独立时钟A
- 视频管线:屏幕捕捉 → GPU → 图形合成器 → 独立时钟B2.
你可以将两者想象成两条平行铁轨:
- 铁轨A(音频)的"0公里"里程碑在 t = 0.0s 时放下
- 铁轨B(视频)的"0公里"里程碑在 t = 1.0s 时放下(视频硬件启动速度是会明显延迟与音频的)
结果:
- 音频首帧到达:t_audio_0 = 0.1s
- 视频首帧到达:t_video_0 = 1.1s
2. 分析
既然已知音频与视频的在硬件层面上的差异,如果两者发生严重延迟如何去做修复呢? 首先分析屏幕录制出现的几种可能性:
- 音频与视频录制完毕之后,看视频无明显的延迟感(两者延迟差在 50-100毫秒),视为正常录制
- 音频与视频录制完毕之后,看视频有明显的延迟感(两者延迟差在 1-n秒),视为异常录制
面对第 1 种场景,无需处理就好,主要是面对第 2 种场景下的处理。但是第2种中音频与视频两者的时间戳差异如何计算呢?我们首先先看官方的api 设计
recorder.startCapture {[weak self]buffer, bufferType, error in
///开发者只能被动接收系统返回的buffer和bufferType
}
从上面的api中,我们可以知晓buffer的类型是音频还是视频,同时buffer上是携带了时间戳的,所以我们可以通过两者第1帧的时间戳进行判断,如果音频与视频的时间差 > 1秒,则视为异常了。
///firstVideoBuffer仅赋值一次
if bufferType == .video && self.firstVideoBuffer == nil{
self.firstVideoBuffer = buffer
}
///firstAudioBuffer仅赋值一次
if bufferType == .audioMic && self.firstAudioBuffer == nil{
self.firstAudioBuffer = buffer
}
///只有分别收到了video和audio的第1次buffer才能往下
guard let videoBuffer = self.firstVideoBuffer,let audioBuffer = self**.firstAudioBuffer
else{return}
let videoStartTime = CMSampleBufferGetPresentationTimeStamp(videoBuffer)
let audioStartTime = CMSampleBufferGetPresentationTimeStamp(audioBuffer)
let videoSeconds:Float64 = CMTimeGetSeconds(videoStartTime)
let audioSeconds:Float64 = CMTimeGetSeconds(audioStartTime)
///两者差的绝对值, 单位:秒,假设设置一个阈值,音频与音频的时间差大于 1 秒的情况下视为异常
let difference = abs(videoSeconds - audioSeconds)
///video的启动时间是否晚于audio,
/*
如果true,则startSession就使用video,否则使用audio
*/
let isVideoLaterThanAudio = videoSeconds - audioSeconds > 0
那么以为到这里就结束了吗?too young too simple!!!
根据异常有区分为下列情况,而且修复难度越来越大,甚至感觉没法修。
- 1、音频与视频的时间差是恒定的
- 2、音频与视频的时间差是不恒定的,不恒定的情况有区分为:
- 音频buffer流时间戳是不恒定的
- 视频buffer流时间戳是不恒定的
- 音频与视频buffer流时间戳都是不恒定的
屏幕录制并不是相机录制视频,相机录制视频是音频与视频可以修改起始时间钟,两条管道在同一个时间线上起跑,哪怕后续录制过程中时间差很大,也给了开发者极大的调整支持,但是屏幕录制的api 你看看给了啥?几乎都是黑盒设计,改都改不了。
3. 修复
针对第一种场景,即音频与视频的时间差是恒定的,这种情况其实很好处理,记录好第一帧的时间差值,以及需要调整的buffer 类型,创建一个新的buffer 数据修改时间戳就行。
第二种场景看着也愁,之前想过统一时间钟的处理方式,但是难度很大而且逻辑上处理过于麻烦,那就留给在座的各位看官吧...溜了溜了。