繁体   English   中英

AVAudioEngine 在 macOS/iOS 上协调/同步输入/输出时间戳

[英]AVAudioEngine reconcile/sync input/output timestamps on macOS/iOS

我正在尝试将录制的音频(从AVAudioEngine inputNode )同步到录制过程中正在播放的音频文件。 结果应该类似于多轨录制,其中每个后续的新轨道都与录制时正在播放的先前轨道同步。

因为sampleTime的 output 和输入节点之间的AVAudioEngine不同,所以我使用hostTime来确定原始音频和输入缓冲区的偏移量。

在 iOS 上,我假设我必须使用AVAudioSession的各种延迟属性( inputLatencyoutputLatencyioBufferDuration )来协调轨道以及主机时间偏移,但我还没有想出神奇的组合来制作他们工作。 各种AVAudioEngineNode属性也是如此,例如latencypresentationLatency.

在 macOS 上, AVAudioSession不存在(在 Catalyst 之外),这意味着我无权访问这些数字。 同时,在大多数情况下, AVAudioNodes上的latency / presentationLatency属性报告为0.0 在 macOS 上,我确实可以访问AudioObjectGetPropertyData并且可以向系统询问kAudioDevicePropertyLatency, kAudioDevicePropertyBufferSizekAudioDevicePropertySafetyOffset等,但对于协调所有这些的公式是什么,我又有点茫然了。

我在https://github.com/jnpdx/AudioEngineLoopbackLatencyTest有一个示例项目,它运行一个简单的环回测试(在 macOS、iOS 或 Mac Catalyst 上)并显示结果。 在我的 Mac 上,轨道之间的偏移量约为 720 个样本。 在其他人的 Mac 上,我已经看到多达 1500 个样本偏移。

在我的 iPhone 上,我可以使用AVAudioSessionoutputLatency + inputLatency使其接近完美样本。 然而,同样的公式在我的 iPad 上留下了偏差。

在每个平台上同步输入和 output 时间戳的神奇公式是什么? 我知道每个人可能会有所不同,这很好,而且我知道我不会获得 100% 的准确度,但我想在完成自己的校准过程之前尽可能接近

这是我当前代码的示例(可以在https://github.com/jnpdx/AudioEngineLoopbackLatencyTest/blob/main/AudioEngineLoopbackLatencyTest/AudioManager.swift找到完整的同步逻辑):

//Schedule playback of original audio during initial playback
let delay = 0.33 * state.secondsToTicks
let audioTime = AVAudioTime(hostTime: mach_absolute_time() + UInt64(delay))
state.audioBuffersScheduledAtHost = audioTime.hostTime

...

//in the inputNode's inputTap, store the first timestamp
audioEngine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (pcmBuffer, timestamp) in
            if self.state.inputNodeTapBeganAtHost == 0 {
                self.state.inputNodeTapBeganAtHost = timestamp.hostTime
            }
}

...

//after playback, attempt to reconcile/sync the timestamps recorded above

let timestampToSyncTo = state.audioBuffersScheduledAtHost
let inputNodeHostTimeDiff = Int64(state.inputNodeTapBeganAtHost) - Int64(timestampToSyncTo)
let inputNodeDiffInSamples = Double(inputNodeHostTimeDiff) / state.secondsToTicks * inputFileBuffer.format.sampleRate //secondsToTicks is calculated using mach_timebase_info

//play the original metronome audio at sample position 0 and try to sync everything else up to it
let originalAudioTime = AVAudioTime(sampleTime: 0, atRate: renderingEngine.mainMixerNode.outputFormat(forBus: 0).sampleRate)
originalAudioPlayerNode.scheduleBuffer(metronomeFileBuffer, at: originalAudioTime, options: []) {
  print("Played original audio")
}

//play the tap of the input node at its determined sync time -- this _does not_ appear to line up in the result file
let inputAudioTime = AVAudioTime(sampleTime: AVAudioFramePosition(inputNodeDiffInSamples), atRate: renderingEngine.mainMixerNode.outputFormat(forBus: 0).sampleRate)
recordedInputNodePlayer.scheduleBuffer(inputFileBuffer, at: inputAudioTime, options: []) {
  print("Input buffer played")
}


运行示例应用程序时,我得到的结果如下:

同步测试结果

此答案仅适用于本机 macOS

一般延迟测定

Output

在一般情况下,设备上 stream 的 output 延迟由以下属性的总和确定:

  1. kAudioDevicePropertySafetyOffset
  2. kAudioStreamPropertyLatency
  3. kAudioDevicePropertyLatency
  4. kAudioDevicePropertyBufferFrameSize

应为kAudioObjectPropertyScopeOutput检索设备安全偏移量 stream 和设备延迟值。

在我的 Mac 上,44.1 kHz 的音频设备MacBook Pro Speakers相当于 71 + 424 + 11 + 512 = 1018 帧。

输入

同样,输入延迟由以下属性的总和决定:

  1. kAudioDevicePropertySafetyOffset
  2. kAudioStreamPropertyLatency
  3. kAudioDevicePropertyLatency
  4. kAudioDevicePropertyBufferFrameSize

应为kAudioObjectPropertyScopeInput检索设备安全偏移量 stream 和设备延迟值。

在我的 Mac 上,44.1 kHz 的音频设备MacBook Pro Microphone相当于 114 + 2404 + 40 + 512 = 3070 帧。

AVAudioEngine

上述信息与AVAudioEngine的关系尚不清楚。 AVAudioEngine在内部创建一个私有聚合设备,Core Audio 基本上自动处理聚合设备的延迟补偿。

在对此答案的实验过程中,我发现某些(大多数?)音频设备无法正确报告延迟。 至少看起来是这样,这使得准确的延迟确定几乎是不可能的。

通过以下调整,我能够使用 Mac 的内置音频获得相当准确的同步:

// Some non-zero value to get AVAudioEngine running
let startDelay = 0.1

// The original audio file start time
let originalStartingFrame: AVAudioFramePosition = AVAudioFramePosition(playerNode.outputFormat(forBus: 0).sampleRate * startDelay)

// The output tap's first sample is delivered to the device after the buffer is filled once
// A number of zero samples equal to the buffer size is produced initially
let outputStartingFrame: AVAudioFramePosition = Int64(state.outputBufferSizeFrames)

// The first output sample makes it way back into the input tap after accounting for all the latencies
let inputStartingFrame: AVAudioFramePosition = outputStartingFrame - Int64(state.outputLatency + state.outputStreamLatency + state.outputSafetyOffset + state.inputSafetyOffset + state.inputLatency + state.inputStreamLatency)

在我的 Mac 上, AVAudioEngine聚合设备报告的值是:

// Output:
// kAudioDevicePropertySafetyOffset:    144
// kAudioDevicePropertyLatency:          11
// kAudioStreamPropertyLatency:         424
// kAudioDevicePropertyBufferFrameSize: 512

// Input:
// kAudioDevicePropertySafetyOffset:     154
// kAudioDevicePropertyLatency:            0
// kAudioStreamPropertyLatency:         2404
// kAudioDevicePropertyBufferFrameSize:  512

这相当于以下偏移量:

originalStartingFrame =  4410
outputStartingFrame   =   512
inputStartingFrame    = -2625

我可能无法回答您的问题,但我相信您的问题中未提及的属性确实报告了额外的延迟信息。

我只在 HAL/AUHAL 层工作过(从不AVAudioEngine ),但在讨论计算整体延迟时,会出现一些音频设备/流属性: kAudioDevicePropertyLatencykAudioStreamPropertyLatency

稍微戳了一下,我看到了AVAudioIONodepresentationLatency属性( https://developer.apple.com/documentation/avfoundation/avaudioionode/1385631-presentationlatency )的文档中提到的那些属性。 我希望驱动程序报告的硬件延迟会在那里。 (我怀疑标准latency属性报告输入样本出现在“正常”节点的 output 中的延迟,并且 IO 情况是特殊的)

它不在AVAudioEngine的上下文中,但这里有一条来自 CoreAudio 邮件列表的消息,其中谈到了使用可能提供一些额外背景的低级属性: https://lists.apple.com/archives/coreaudio-api/ 2017/7月/msg00035.html

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM