我创建了一个用于进行声学测量的应用程序。该应用程序会生成一个对数正弦波扫描激励,当用户按下“开始”时,该应用会同时播放激励声,并记录麦克风输入。
所有相当标准的东西。我正在使用核心音频,因为我想真正地深入研究不同的功能,并且可能使用多个接口,因此必须从某个地方开始学习。
这是针对iOS的,因此我正在创建带有remoteIO音频单元的AUGraph,用于输入和输出。我已经声明了音频格式,它们是正确的,因为未显示任何错误,并且AUGraph初始化,启动,播放声音和记录。
我在输入范围上有一个渲染回调,用于混频器的输入1。 (即,每当需要更多音频时,都会调用render回调,这将从我的刺激浮点数组中读取一些样本到缓冲区中。)
let genContext = Unmanaged.passRetained(self).toOpaque()
var genCallbackStruct = AURenderCallbackStruct(inputProc: genCallback,
inputProcRefCon: genContext)
AudioUnitSetProperty(mixerUnit!, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 1, &genCallbackStruct,
UInt32(MemoryLayout<AURenderCallbackStruct>.size))
然后我有一个输入回调,每次在remoteIO输入的输出范围上的缓冲区已满时都会调用该回调。此回调将样本保存到数组。
var inputCallbackStruct = AURenderCallbackStruct(inputProc: recordingCallback,
inputProcRefCon: context)
AudioUnitSetProperty(remoteIOUnit!, kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, 0, &inputCallbackStruct,
UInt32(MemoryLayout<AURenderCallbackStruct>.size))
一旦刺激到达最后一个样本,AUGraph将停止,然后我将刺激和记录的数组都写入到单独的WAV文件中,以便检查数据。我发现的是,当前录制的输入和刺激之间大约有3000个采样延迟。
虽然很难看到波形的开始(扬声器和麦克风可能都检测不到这么低的声音),但是刺激的末端(底部WAV)和录制的声音应该大致对齐。
我知道,音频会有传播时间,但是在44100Hz的采样率下,这是68ms。核心音频旨在降低延迟。
所以我的问题是,任何人都可以考虑这个额外的等待时间吗?
我的inputCallback如下:
let recordingCallback: AURenderCallback = { (
inRefCon,
ioActionFlags,
inTimeStamp,
inBusNumber,
frameCount,
ioData ) -> OSStatus in
let audioObject = unsafeBitCast(inRefCon, to: AudioEngine.self)
var err: OSStatus = noErr
var bufferList = AudioBufferList(
mNumberBuffers: 1,
mBuffers: AudioBuffer(
mNumberChannels: UInt32(1),
mDataByteSize: 512,
mData: nil))
if let au: AudioUnit = audioObject.remoteIOUnit! {
err = AudioUnitRender(au,
ioActionFlags,
inTimeStamp,
inBusNumber,
frameCount,
&bufferList)
}
let data = Data(bytes: bufferList.mBuffers.mData!, count: Int(bufferList.mBuffers.mDataByteSize))
let samples = data.withUnsafeBytes {
UnsafeBufferPointer<Int16>(start: $0, count: data.count / MemoryLayout<Int16>.size)
}
let factor = Float(Int16.max)
var floats: [Float] = Array(repeating: 0.0, count: samples.count)
for i in 0..<samples.count {
floats[i] = (Float(samples[i]) / factor)
}
var j = audioObject.in1BufIndex
let m = audioObject.in1BufSize
for i in 0..<(floats.count) {
audioObject.in1Buf[j] = Float(floats[I])
j += 1 ; if j >= m { j = 0 }
}
audioObject.in1BufIndex = j
audioObject.inputCallbackFrameSize = Int(frameCount)
audioObject.callbackcount += 1
var WindowSize = totalRecordSize / Int(frameCount)
if audioObject.callbackcount == WindowSize {
audioObject.running = false
}
return 0
}
因此,从引擎启动时起,应在从remoteIO收集第一组数据之后调用此回调。 512个样本是默认分配的缓冲区大小。它所做的只是将有符号整数转换为Float,然后保存到缓冲区。 in1BufIndex值是对写入数组的最后一个索引的引用,每个回调均对其进行引用和写入,以确保数组中的数据对齐。
目前看来,在听到捕获的扫描之前,记录的阵列中大约有3000个沉默样本。通过在Xcode中调试来检查记录的数组,所有样本都有值(是的,前3000个非常安静),但是以某种方式,这并没有加总。请帮助
答案 0 :(得分:2)
可能至少有四件事会导致往返延迟。
512个样本(即11毫秒)是在remoteIO可以调用您的回调之前收集足够的样本所需的时间。
声音的传播速度约为每毫秒1英尺,是往返行程的两倍。
DAC具有输出延迟。
多个ADC(iOS设备上有多个麦克风)需要时间来采样和后处理音频(用于sigma-delta,波束形成,均衡等)。后处理可能会以块为单位进行,因此会导致延迟为一个块收集足够的样本(未记录的编号)。
在ADC与系统内存之间移动数据(某些未知块大小的硬件DMA?)以及驱动程序和操作系统上下文切换开销方面,可能还会增加开销延迟。
在启动音频硬件子系统(放大器等)时还存在启动延迟,因此最好在输出声音(扫频)之前就开始良好地播放和录制音频。