How can I specify the format of AVAudioEngine Mic-Input?

Georg picture Georg · Nov 2, 2015 · Viewed 8k times · Source

I'd like to record the some audio using AVAudioEngine and the users Microphone. I already have a working sample, but just can't figure out how to specify the format of the output that I want...

My requirement would be that I need the AVAudioPCMBuffer as I speak which it currently does...

Would I need to add a seperate node that does some transcoding? I can't find much documentation/samples on that problem...

And I am also a noob when it comes to Audio-Stuff. I know that I want NSData containing PCM-16bit with a max sample-rate of 16000 (8000 would be better)

Here's my working sample:

private var audioEngine = AVAudioEngine()

func startRecording() {

  let format = audioEngine.inputNode!.inputFormatForBus(bus)

  audioEngine.inputNode!.installTapOnBus(bus, bufferSize: 1024, format: format) { (buffer: AVAudioPCMBuffer, time:AVAudioTime) -> Void in

     let audioFormat = PCMBuffer.format
     print("\(audioFormat)")
  }

  audioEngine.prepare()
  do {
     try audioEngine.start()
  } catch { /* Imagine some super awesome error handling here */ }
}

If I changed the format to let' say

let format = AVAudioFormat(commonFormat: AVAudioCommonFormat.PCMFormatInt16, sampleRate: 8000.0, channels: 1, interleaved: false)

then if will produce an error saying that the sample rate needs to be the same as the hwInput...

Any help is very much appreciated!!!

EDIT: I just found AVAudioConverter but I need to be compatible with iOS8 as well...

Answer

Josh picture Josh · May 31, 2016

You cannot change audio format directly on input nor output nodes. In the case of the microphone, the format will always be 44KHz, 1 channel, 32bits. To do so, you need to insert a mixer in between. Then when you connect inputNode > changeformatMixer > mainEngineMixer, you can specify the details of the format you want.

Something like:

var inputNode = audioEngine.inputNode
var downMixer = AVAudioMixerNode()

//I think you the engine's I/O nodes are already attached to itself by default, so we attach only the downMixer here:
audioEngine.attachNode(downMixer)

//You can tap the downMixer to intercept the audio and do something with it:
downMixer.installTapOnBus(0, bufferSize: 2048, format: downMixer.outputFormatForBus(0), block:  //originally 1024
            { (buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
                print(NSString(string: "downMixer Tap"))
                do{
                    print("Downmixer Tap Format: "+self.downMixer.outputFormatForBus(0).description)//buffer.audioBufferList.debugDescription)

        })

//let's get the input audio format right as it is
let format = inputNode.inputFormatForBus(0)
//I initialize a 16KHz format I need:
let format16KHzMono = AVAudioFormat.init(commonFormat: AVAudioCommonFormat.PCMFormatInt16, sampleRate: 11050.0, channels: 1, interleaved: true)

//connect the nodes inside the engine:
//INPUT NODE --format-> downMixer --16Kformat--> mainMixer
//as you can see I m downsampling the default 44khz we get in the input to the 16Khz I want 
audioEngine.connect(inputNode, to: downMixer, format: format)//use default input format
audioEngine.connect(downMixer, to: audioEngine.outputNode, format: format16KHzMono)//use new audio format
//run the engine
audioEngine.prepare()
try! audioEngine.start()

I would recommend using an open framework such as EZAudio, instead, though.