Я создал приложение, которое использую для акустических измерений.Приложение генерирует лог-синусоидальный стимул, и когда пользователь нажимает кнопку «пуск», приложение одновременно воспроизводит звук стимула и записывает вход микрофона.
Все довольно стандартные вещи.Я использую базовое аудио, так как я хочу по-настоящему углубиться в различные функциональные возможности и потенциально использовать несколько интерфейсов, поэтому мне нужно начать обучение где-нибудь.
Это для iOS, поэтому я создаю AUGraph с удаленным аудиоустройством для ввода и вывода.Я объявил аудио форматы, и они верны, поскольку ошибки не отображаются, и AUGraph инициализирует, запускает, воспроизводит звук и записывает.
У меня есть обратный вызов рендеринга в области ввода для ввода 1 моего микшера.(то есть, каждый раз, когда требуется больше звука, вызывается обратный вызов рендеринга, и это считывает несколько выборок в буфер из моего массива стимулов с плавающей запятой).
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))
Затем у меня есть входной обратный вызов, который называетсякаждый раз, когда буфер заполнен в области вывода входа удаленного ввода-вывода.Этот обратный вызов сохраняет образцы в массив.
var inputCallbackStruct = AURenderCallbackStruct(inputProc: recordingCallback,
inputProcRefCon: context)
AudioUnitSetProperty(remoteIOUnit!, kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, 0, &inputCallbackStruct,
UInt32(MemoryLayout<AURenderCallbackStruct>.size))
Как только стимул достигает последней выборки, AUGraph останавливается, и затем я записываю и стимул, и записанный массив в отдельные файлы WAV, чтобы я мог проверить свои данные.Я обнаружил, что между записанным входом и стимулом в настоящее время задерживается около 3000 отсчетов.
![enter image description here](https://i.stack.imgur.com/zeTdB.png)
В то время как трудно увидеть начало сигналов (динамики и микрофон могут не обнаружить такой низкий уровень), концыстимул (нижний WAV) и записанные должны примерно выстроиться в линию.
Будет время распространения звука, я это понимаю, но при частоте дискретизации 44100 Гц, это 68 мс.Базовое аудио предназначено для снижения задержки.
Итак, мой вопрос заключается в том, может ли кто-нибудь объяснить эту дополнительную задержку, которая кажется довольно высокой
Мой 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 очень тихие), но почему-то это не складывается.
Ниже приведен генератор Callback, используемый для воспроизведения моего стимула
let genCallback: AURenderCallback = { (
inRefCon,
ioActionFlags,
inTimeStamp,
inBusNumber,
frameCount,
ioData) -> OSStatus in
let audioObject = unsafeBitCast(inRefCon, to: AudioEngine.self)
for buffer in UnsafeMutableAudioBufferListPointer(ioData!) {
var frames = buffer.mData!.assumingMemoryBound(to: Float.self)
var j = 0
if audioObject.stimulusReadIndex < (audioObject.Stimulus.count - Int(frameCount)){
for i in stride(from: 0, to: Int(frameCount), by: 1) {
frames[i] = Float((audioObject.Stimulus[j + audioObject.stimulusReadIndex]))
j += 1
audioObject.in2Buf[j + audioObject.stimulusReadIndex] = Float((audioObject.Stimulus[j + audioObject.stimulusReadIndex]))
}
audioObject.stimulusReadIndex += Int(frameCount)
}
}
return noErr;
}