Search code examples
iosavfoundationcore-audioavaudioengine

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


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...


Solution

  • 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.