gpt4 book ai didi

ios - 用于音频测量的 Swift AVFoundation 计时信息

转载 作者:行者123 更新时间:2023-11-29 05:32:00 26 4
gpt4 key购买 nike

我正在创建一个应用程序,该应用程序将通过播放一些刺激数据并记录麦克风输入,然后分析数据来进行音频测量。

我无法计算初始化和启动音频引擎所需的时间,因为每次都会有所不同,并且还取决于所使用的硬件等。

所以,我有一个音频引擎,并安装了一个 Tap 硬件输入,输入 1 是麦克风录音,输入 2 是引用输入(也来自硬件)。输出在物理上进行 Y 分割并反馈到输入 2。

应用程序初始化引擎,播放刺激音频加上 1 秒的静音(以便麦克风有传播时间来记录整个信号),然后停止并关闭引擎。

我将两个输入缓冲区写入 WAV 文件,以便可以将其导入到现有的 DAW 中。目视检查信号。我可以看到,每次进行测量时,两个信号之间的时间差都是不同的(尽管麦克风没有移动并且硬件保持不变)。我假设这与硬件的延迟、初始化引擎所需的时间以及设备分配任务的方式有关。

我尝试使用每个 installTap 函数上第一个缓冲区回调的 mach_absolute_time 捕获绝对时间,然后将两者相减,我可以看到每次调用的情况确实有很大差异:

class newAVAudioEngine{

var engine = AVAudioEngine()
var audioBuffer = AVAudioPCMBuffer()
var running = true
var in1Buf:[Float]=Array(repeating:0, count:totalRecordSize)
var in2Buf:[Float]=Array(repeating:0, count:totalRecordSize)
var buf1current:Int = 0
var buf2current:Int = 0
var in1firstRun:Bool = false
var in2firstRun:Bool = false
var in1StartTime = 0
var in2startTime = 0

func measure(inputSweep:SweepFilter) -> measurement {
initializeEngine(inputSweep: inputSweep)
while running == true {

}
let measureResult = measurement.init(meas: meas,ref: ref)
return measureResult
}

func initializeEngine(inputSweep:SweepFilter) {
buf1current = 0
buf2current = 0
in1StartTime = 0
in2startTime = 0
in1firstRun = true
in2firstRun = true
in1Buf = Array(repeating:0, count:totalRecordSize)
in2Buf = Array(repeating:0, count:totalRecordSize)
engine.stop()
engine.reset()
engine = AVAudioEngine()

let srcNode = AVAudioSourceNode { _, _, frameCount, AudioBufferList -> OSStatus in

let ablPointer = UnsafeMutableAudioBufferListPointer(AudioBufferList)

if (Int(frameCount) + time) <= inputSweep.stimulus.count {

for frame in 0..<Int(frameCount) {
let value = inputSweep.stimulus[frame + time]
for buffer in ablPointer {
let buf: UnsafeMutableBufferPointer<Float> = UnsafeMutableBufferPointer(buffer)
buf[frame] = value
}
}
time += Int(frameCount)
return noErr
} else {
for frame in 0..<Int(frameCount) {
let value = 0
for buffer in ablPointer {
let buf: UnsafeMutableBufferPointer<Float> = UnsafeMutableBufferPointer(buffer)
buf[frame] = Float(value)
}
}
}
return noErr
}

let format = engine.outputNode.inputFormat(forBus: 0)
let stimulusFormat = AVAudioFormat(commonFormat: format.commonFormat,
sampleRate: Double(sampleRate),
channels: 1,
interleaved: format.isInterleaved)

do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord)

let ioBufferDuration = 128.0 / 44100.0

try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(ioBufferDuration)
} catch {
assertionFailure("AVAudioSession setup failed")
}

let input = engine.inputNode
let inputFormat = input.inputFormat(forBus: 0)

print("InputNode Format is \(inputFormat)")
engine.attach(srcNode)
engine.connect(srcNode, to: engine.mainMixerNode, format: stimulusFormat)

if internalRefLoop == true {
srcNode.installTap(onBus: 0, bufferSize: 1024, format: stimulusFormat, block: {(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
if self.in2firstRun == true {
var info = mach_timebase_info()
mach_timebase_info(&info)
let currentTime = mach_absolute_time()
let nanos = currentTime * UInt64(info.numer) / UInt64(info.denom)
self.in2startTime = Int(nanos)
self.in2firstRun = false
}
do {
let floatData = buffer.floatChannelData?.pointee
for frame in 0..<buffer.frameLength{
if (self.buf2current + Int(frame)) < totalRecordSize{
self.in2Buf[self.buf2current + Int(frame)] = floatData![Int(frame)]
}
}


self.buf2current += Int(buffer.frameLength)
if (self.numberOfSamples + Int(buffer.frameLength)) <= totalRecordSize{
try self.stimulusFile.write(from: buffer)
self.numberOfSamples += Int(buffer.frameLength) } else {
self.engine.stop()
self.running = false
}
} catch {
print(NSString(string: "write failed"))
}
})
}


let micAudioConverter = AVAudioConverter(from: inputFormat, to: stimulusFormat!)
var micChannelMap:[NSNumber] = [0,-1]
micAudioConverter?.channelMap = micChannelMap

let refAudioConverter = AVAudioConverter(from: inputFormat, to: stimulusFormat!)
var refChannelMap:[NSNumber] = [1,-1]
refAudioConverter?.channelMap = refChannelMap



//Measurement Tap
engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: inputFormat, block: {(buffer2: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
//print(NSString(string:"writing"))

if self.in1firstRun == true {
var info = mach_timebase_info()
mach_timebase_info(&info)
let currentTime = mach_absolute_time()
let nanos = currentTime * UInt64(info.numer) / UInt64(info.denom)
self.in1StartTime = Int(nanos)
self.in1firstRun = false
}
do {
let micConvertedBuffer = AVAudioPCMBuffer(pcmFormat: stimulusFormat!, frameCapacity: buffer2.frameCapacity)
let micInputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
outStatus.pointee = AVAudioConverterInputStatus.haveData
return buffer2
}
var error: NSError? = nil
//let status = audioConverter.convert(to: convertedBuffer!, error: &error, withInputFrom: inputBlock)



let status = micAudioConverter?.convert(to: micConvertedBuffer!, error: &error, withInputFrom: micInputBlock)
//print(status)
let floatData = micConvertedBuffer?.floatChannelData?.pointee
for frame in 0..<micConvertedBuffer!.frameLength{
if (self.buf1current + Int(frame)) < totalRecordSize{
self.in1Buf[self.buf1current + Int(frame)] = floatData![Int(frame)]

}
if (self.buf1current + Int(frame)) >= totalRecordSize {
self.engine.stop()
self.running = false
}


}
self.buf1current += Int(micConvertedBuffer!.frameLength)
try self.measurementFile.write(from: micConvertedBuffer!)

} catch {
print(NSString(string: "write failed"))
}

if internalRefLoop == false {
if self.in2firstRun == true{
var info = mach_timebase_info()
mach_timebase_info(&info)
let currentTime = mach_absolute_time()
let nanos = currentTime * UInt64(info.numer) / UInt64(info.denom)
self.in2startTime = Int(nanos)
self.in2firstRun = false
}
do {
let refConvertedBuffer = AVAudioPCMBuffer(pcmFormat: stimulusFormat!, frameCapacity: buffer2.frameCapacity)
let refInputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
outStatus.pointee = AVAudioConverterInputStatus.haveData
return buffer2

}

var error: NSError? = nil

let status = refAudioConverter?.convert(to: refConvertedBuffer!, error: &error, withInputFrom: refInputBlock)
//print(status)
let floatData = refConvertedBuffer?.floatChannelData?.pointee
for frame in 0..<refConvertedBuffer!.frameLength{
if (self.buf2current + Int(frame)) < totalRecordSize{
self.in2Buf[self.buf2current + Int(frame)] = floatData![Int(frame)]
}

}
if (self.numberOfSamples + Int(buffer2.frameLength)) <= totalRecordSize{
self.buf2current += Int(refConvertedBuffer!.frameLength)
try self.stimulusFile.write(from: refConvertedBuffer!) } else {
self.engine.stop()
self.running = false
}


} catch {
print(NSString(string: "write failed"))
}
}
}
)


assert(engine.inputNode != nil)
running = true
try! engine.start()

所以上面的方法就是我的整个类。目前,installTap 上的每个缓冲区调用都会将输入直接写入 WAV 文件。在这里我可以看到每次的两个最终结果都不同。我尝试添加 startTime 变量并减去两者,但结果仍然不同。

我是否需要考虑到我的输出也会有延迟,并且每次调用可能会有所不同?如果是这样,我该如何将这个时间添加到等式中?我正在寻找的是两个输入和输出都具有相对时间,以便我可以比较它们。只要我能够识别结束调用时间,不同的硬件延迟不会有太大影响。

最佳答案

如果您正在进行实时测量,您可能需要使用 AVAudioSinkNode 而不是 Tap。 Sink 节点是新的,与您正在使用的 AVAudioSourceNode 一起引入。安装 Tap 后,您将无法获得精确的计时。

关于ios - 用于音频测量的 Swift AVFoundation 计时信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57449338/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com