Reputation: 577
I am using AVAudioEngine to take a measurement. I play a stimulus sound out of my interface, and use a micTap to record the returned signal.
I am now looking at different Audio Interfaces which support a multitude of different formats. I am converting the input format of the inputNode via a mixer for two different reasons:
to downsample from the interfaces' preferred sampleRate to the sampleRate at which my app is working
to convert the incoming channel count to a single mono channel
I try this, however it does not always seem to work as expected. If my interface is running 96k and my app is running 48k, doing a format change via a mixer ends up with the following:
This looks like it is only getting one side of a stereo interleaved channel. Below is my audioEngine code:
func initializeEngine(inputSweep:SweepFilter) {
buf1current = 0
buf2current = 0
in1StartTime = 0
in2startTime = 0
in1firstRun = true
in2firstRun = true
in1Buf = Array(repeating:0, count:1000000)
in2Buf = Array(repeating:0, count:1000000)
engine.stop()
engine.reset()
engine = AVAudioEngine()
numberOfSamples = 0
var time:Int = 0
do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord)
try AVAudioSession.sharedInstance()
.setPreferredSampleRate(Double(sampleRate))
} catch {
assertionFailure("AVAudioSession setup failed")
}
let format = engine.outputNode.inputFormat(forBus: 0)
let stimulusFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32,
sampleRate: Double(sampleRate),
channels: 1,
interleaved: false)
let outputFormat = engine.outputNode.inputFormat(forBus: 0)
let inputFormat = engine.inputNode.outputFormat(forBus: 0)
let srcNode = AVAudioSourceNode { _, timeStamp, frameCount, AudioBufferList -> OSStatus in
let ablPointer = UnsafeMutableAudioBufferListPointer(AudioBufferList)
if self.in2firstRun == true {
let start2 = CACurrentMediaTime()
self.in2startTime = Double(CACurrentMediaTime())
self.in2firstRun = false
}
if Int(frameCount) + time >= inputSweep.stimulus.count{
self.running = false
print("AUDIO ENGINE STOPPED")
}
if (Int(frameCount) + time) <= inputSweep.stimulus.count {
for frame in 0..<Int(frameCount) {
let value = inputSweep.stimulus[frame + time] * Float(outputVolume)
for buffer in ablPointer {
let buf: UnsafeMutableBufferPointer<Float> = UnsafeMutableBufferPointer(buffer)
buf[frame] = value
}
}
time += Int(frameCount)
} 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
}
engine.attach(srcNode)
engine.connect(srcNode, to: engine.mainMixerNode, format: stimulusFormat)
engine.connect(engine.mainMixerNode, to: engine.outputNode, format: format)
let requiredFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32,
sampleRate: Double(sampleRate),
channels: 1,
interleaved: false)
let formatMixer = AVAudioMixerNode()
engine.attach(formatMixer)
engine.connect(engine.inputNode, to: formatMixer, format: inputFormat)
let MicSinkNode = AVAudioSinkNode() { (timeStamp, frames, audioBufferList) ->
OSStatus in
if self.in1firstRun == true {
let start1 = CACurrentMediaTime()
self.in1StartTime = Double(start1)
self.in1firstRun = false
}
let ptr = audioBufferList.pointee.mBuffers.mData?.assumingMemoryBound(to: Float.self)
var monoSamples = [Float]()
monoSamples.append(contentsOf: UnsafeBufferPointer(start: ptr, count: Int(frames)))
if self.buf1current >= 100000 {
self.running = false
}
for frame in 0..<frames {
self.in1Buf[self.buf1current + Int(frame)] = monoSamples[Int(frame)]
}
self.buf1current = self.buf1current + Int(frames)
return noErr
}
engine.attach(MicSinkNode)
engine.connect(formatMixer, to: MicSinkNode, format: requiredFormat)
engine.prepare()
assert(engine.inputNode != nil)
running = true
try! engine.start()
}
My sourceNode is an array of Floats synthesised to use the stimulusFormat. If I listen to this audioEngine with my interface at 96k, the output stimulus sounds completely clean. However this broken up signal is what is coming from the micTap. Physically the output of the interface is routed. directly to the input, so not going through any other device.
Further to this, I have the following function, which records my arrays to WAV files so that I can visually inspect in a DAW.
func writetoFile(buff:[Float], name:String){
let SAMPLE_RATE = sampleRate
let outputFormatSettings = [
AVFormatIDKey:kAudioFormatLinearPCM,
AVLinearPCMBitDepthKey:32,
AVLinearPCMIsFloatKey: true,
AVLinearPCMIsBigEndianKey: true,
AVSampleRateKey: SAMPLE_RATE,
AVNumberOfChannelsKey: 1
] as [String : Any]
let fileName = name
let DocumentDirURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let url = DocumentDirURL.appendingPathComponent(fileName).appendingPathExtension("wav")
//print("FilePath: \(url.path)")
let audioFile = try? AVAudioFile(forWriting: url, settings: outputFormatSettings, commonFormat: AVAudioCommonFormat.pcmFormatFloat32, interleaved: false)
let bufferFormat = AVAudioFormat(settings: outputFormatSettings)
let outputBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat!, frameCapacity: AVAudioFrameCount(buff.count))
for i in 0..<buff.count {
outputBuffer?.floatChannelData!.pointee[i] = Float(( buff[i] ))
}
outputBuffer!.frameLength = AVAudioFrameCount( buff.count )
do{
try audioFile?.write(from: outputBuffer!)
} catch let error as NSError {
print("error:", error.localizedDescription)
}
}
If I set my interface to be 48k, and my app is working at 48k, if I inspect my reference signal and. my measurement signal, i get the following:
The measured signal is clearly a lot longer than the original stimulus. The physical file size. is the same as it is initialised as an empty array of fixed size. However at some point doing the format conversion, it is not correct. If I put my interface at 44.1k and my app runs at 48k, I can see the regular 'glitches' in audio. So the format convert here is not working as it should do.
Can anyone see anything obviously wrong?
Upvotes: 1
Views: 952
Reputation: 11
put the non-interleaved option "AVLinearPCMIsNonInterleaved" inside the format settings:
let outputFormatSettings = [
**AVLinearPCMIsNonInterleaved: 0,**
AVFormatIDKey:kAudioFormatLinearPCM,
AVLinearPCMBitDepthKey:32,
AVLinearPCMIsFloatKey: true,
AVLinearPCMIsBigEndianKey: true,
AVSampleRateKey: SAMPLE_RATE,
AVNumberOfChannelsKey: 1
] as [String : Any]
it worked for me, let me know
Upvotes: 0