21

I am implementing Speech Recognition in my app. When I first present the view controller with the speech recognition logic, everything works fine. However, when I try present the view controller again, I get the following crash:

ERROR:    [0x190bf000] >avae> AVAudioNode.mm:568: CreateRecordingTap: required condition is false: IsFormatSampleRateAndChannelCountValid(format)
*** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: IsFormatSampleRateAndChannelCountValid(format)'

Here is the code used for starting and stopping recording:

@available(iOS 10.0, *)
extension DictationViewController {

fileprivate func startRecording() throws {
    guard let recognizer = speechRecognizer else {
        debugLog(className, message: "Not supported for the device's locale")
        return
    }

    guard recognizer.isAvailable else {
        debugLog(className, message: "Recognizer is not available right now")
        return
    }

    mostRecentlyProcessedSegmentDuration = 0
    guard let node = audioEngine.inputNode else {
        debugLog(className, message: "Could not get an input node")
        return
    }

    let recordingFormat = node.outputFormat(forBus: 0)
    node.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] (buffer, _) in
        self?.request.append(buffer)
    }

    audioEngine.prepare()
    try audioEngine.start()

    recognitionTask = recognizer.recognitionTask(with: request, resultHandler: {/***/})
}

fileprivate func stopRecording() {
    audioEngine.stop()
    audioEngine.inputNode?.removeTap(onBus: 0)
    request.endAudio()
    recognitionTask?.cancel()
}

}

startRecording() is called in viewDidLoad once we have requested authorization. stopRecording() is called when the view controller is dismissed.

Please assist. I'm struggling to find a solution to this crash

Appache99
  • 211
  • 1
  • 2
  • 5

7 Answers7

22

First, a small issue. When tapping the device's microphone, you'll want to use the format of the input bus:

let recordingFormat = node.inputFormat(forBus: 0)

Second, after some digging it seems like this crash most commonly stems from your application's shared AVAudioSession category settings. Make sure you have your audio session configured like so if you're going to be performing live microphone audio processing:

private func configureAudioSession() {
    do {
        try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: .mixWithOthers)
        try AVAudioSession.sharedInstance().setActive(true)
    } catch { }
}
WongWray
  • 2,414
  • 1
  • 20
  • 25
  • The first part is wrong. We should use "outputFormat" of inputNode. – joshmori Dec 23 '20 at 21:45
  • @joshmori care to elaborate? – WongWray Jan 04 '21 at 14:44
  • 1
    Installs an audio tap on the bus to record, monitor, and observe the "output of the node". from https://developer.apple.com/documentation/avfoundation/avaudionode/1387122-installtap the output of the node => use outputFormat of that node – joshmori Jan 04 '21 at 20:57
  • Its interesting that the example you linked uses outputFormat when tapping a mic. I would think that since a microphone only handles speech _input_, that you'd want an inputFormat. I've found several examples online, some using inputFormat some using outputFormat. Maybe someone can explain the discrepancy here? – WongWray Jan 05 '21 at 15:17
16

There are two possible ways to solve this problem.

  1. Check inputFormat.channelCount. It may be throwing the error because the mic is in use in another application or somewhere else you yours.
if(inputNode.inputFormat(forBus: 0).channelCount == 0){
    NSLog("Not enough available inputs!")
    return
}
  1. Try to reset the audioEngine.
audioEngine.reset()
Ignatius
  • 2,745
  • 2
  • 20
  • 32
8

I was getting the required condition is false: IsFormatSampleRateAndChannelCountValid(format) crash when attempting to use speech recognition while taking a phone call, which caused the sample rate to equal zero. My solution was to create the below audioInputIsBusy() function and call it before try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers) That prevented the crash and I displayed a message that the "speech recognition is unavailable" and then reset the audioEngine with audioEngine = AVAudioEngine().

func audioInputIsBusy(recordingFormat: AVAudioFormat) -> Bool {
    guard recordingFormat.sampleRate == 0 || recordingFormat.channelCount == 0 else {
        return false
    }

    return true
}

ps: let recordingFormat = audioEngine.inputNode.outputFormat(forBus: 0)

carlachip
  • 111
  • 1
  • 2
  • hi, can u provide more code? you mean reset engine then call installTap again? – famfamfam Mar 30 '21 at 17:56
  • @famfamfam yes, the order of events would be something like this...with other logic in between: 1. audioEngine = AVAudioEngine() 2. guard !audioInputIsBusy(recordingFormat: recordingFormat) else { 3. audioEngine.inputNode.installTap 4. try audioEngine.start() – carlachip May 06 '21 at 21:24
5

You can replace this code:

let recordingFormat = node.outputFormat(forBus: 0)

with the following:

let recordingFormat = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 1)

This code fixed the problem.

Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
Selva Murugan
  • 110
  • 1
  • 4
  • 1
    thanks for this fix. But what is the reason that outputFormat(forBus: 0) not working? – e.ozmen Jan 05 '18 at 11:08
  • Because you are trying to listen inputNode with outputNode's format. – Daedelus Dec 28 '18 at 12:09
  • 2
    But installTapOnBus method sample in AVAudioNode class shows developers to use this method like that; AVAudioEngine *engine = [[AVAudioEngine alloc] init]; AVAudioInputNode *input = [engine inputNode]; AVAudioFormat *format = [input outputFormatForBus: 0]; [input installTapOnBus: 0 bufferSize: 8192 format: format block: ^(AVAudioPCMBuffer *buf, AVAudioTime *when) { // ‘buf' contains audio captured from input node at time 'when' }]; they give outputformatonbus to installtaponbus method – Kaan Esin Mar 18 '19 at 07:13
  • 1
    @Daedelus OP is not using the outputNode's format. OP is using the inputNode's outputFormat. – R. Rincón Mar 14 '20 at 04:19
  • You should *never* assume the sample rate: devices iPhone X and beyond I believe have a sample rate of 48000, please determine your devices sample rate with `AVAudioEngine.inputNode.outputFormat.sampleRate` also make sure that your`AVAudioSession.sharedInstance().sampleRate` is the same prior to recording or you will crash. – Maximilian Feb 04 '21 at 22:59
  • Today I found out that when the Apple Car Play was in the car, it was also a different sampleRate, in Mitsubishi it was 24000 – neskafesha Nov 08 '21 at 14:15
2

I had to call the removeTap() before installTap in order to make it work. None of the solutions above worked for me.

//Remove tap first.
inputNode.removeTap(onBus: 0)

// Configure the microphone input.
let recordingFormat = inputNode.inputFormat(forBus: 0)            
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            //process buffer...
        }
Sabir Ali
  • 475
  • 2
  • 16
0
class AudioRecordProvider {
  
    var audioEngine = AVAudioEngine()
    let mixerNode = AVAudioMixerNode()

    
    func startListening() throws {
        guard !audioEngine.isRunning else { return }
        let audioSession = AVAudioSession.sharedInstance()
        
        try audioSession.setCategory(.playAndRecord)
        
        audioSession.requestRecordPermission { [weak self] success in
            guard success, let self = self else { return }
            try? self.audioEngine.start()
        }
    }
    
    func stopListening() {
        if audioEngine.isRunning {
            audioEngine.stop()
        }
    }


    
    func configureAudioEngine() {
        self.audioEngine = AVAudioEngine()
        let inputFormat = audioEngine.inputNode.inputFormat(forBus: 0)
        
        let outputFormat = AVAudioFormat(standardFormatWithSampleRate: 48000, channels: 1)
        
        audioEngine.attach(mixerNode)

        audioEngine.connect(audioEngine.inputNode, to: mixerNode, format: inputFormat)
        audioEngine.connect(mixerNode, to: audioEngine.outputNode, format: outputFormat)
            
    }
}

This Answer worked for me. It's the usage example. Lifecycle: configureAudioEngine -> startListening -> stopListening. Every call of configureAudioEngine reinit AVAudioEngine and it works.

cucusenok
  • 1
  • 1
-1

try this, before start running each time:

audioEngine = AVAudioEngine()

August Lin
  • 1,249
  • 12
  • 11