AVAudioEngine inputNode installTap crash when restarting recording
Asked Answered
V

7

24

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

Vivie answered 23/1, 2017 at 11:31 Comment(1)
If this is Mac app, then you need Audio Input capabilityGdynia
P
23

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 { }
}
Proust answered 20/12, 2017 at 9:17 Comment(3)
The first part is wrong. We should use "outputFormat" of inputNode.Deuterogamy
Installs an audio tap on the bus to record, monitor, and observe the "output of the node". from developer.apple.com/documentation/avfoundation/avaudionode/… the output of the node => use outputFormat of that nodeDeuterogamy
Using the second snippet worked for me. Although syntax has to be updated in the first line to, AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, options: .mixWithOthers)Dardan
K
19

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()
Karakalpak answered 14/8, 2019 at 0:42 Comment(1)
In my case issue was channelCount = 0. Try with audioEngine.reset() or check your code how you managing audio object's life cycle.Abiogenetic
A
10

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)

Appetizing answered 18/6, 2020 at 2:7 Comment(2)
hi, can u provide more code? you mean reset engine then call installTap again?Ringside
@Ringside 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()Appetizing
P
6

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.

Platinocyanide answered 9/8, 2017 at 18:32 Comment(6)
thanks for this fix. But what is the reason that outputFormat(forBus: 0) not working?Interrogate
Because you are trying to listen inputNode with outputNode's format.Palaeography
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 methodNonproductive
@Palaeography OP is not using the outputNode's format. OP is using the inputNode's outputFormat.Treed
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 yourAVAudioSession.sharedInstance().sampleRate is the same prior to recording or you will crash.Mcmillon
Today I found out that when the Apple Car Play was in the car, it was also a different sampleRate, in Mitsubishi it was 24000Larkin
D
3

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...
        }
Deterrence answered 5/1, 2021 at 18:39 Comment(0)
S
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.

Sansone answered 14/3, 2023 at 14:3 Comment(0)
L
-1

try this, before start running each time:

audioEngine = AVAudioEngine()

Lemley answered 12/10, 2020 at 18:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.