簡體   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