Method to capture voice input when using CPVoiceControlTemplate

In my navigation CarPlay app I am needing to capture voice input and process that into text. Is there a built in way to do this in CarPlay?

I did not find one, so I used the following, but I am running into issues where the AVAudioSession will throw an error when I am trying to set active to false after I have captured the audio.

public func startRecording(completionHandler: @escaping (_ completion: String?) -> ()) throws {
    
    // Cancel the previous task if it's running.
    if let recognitionTask = self.recognitionTask {
        recognitionTask.cancel()
        self.recognitionTask = nil
    }
    
    // Configure the audio session for the app.
    let audioSession = AVAudioSession.sharedInstance()
    try audioSession.setCategory(.record, mode: .default, options: [.duckOthers, .interruptSpokenAudioAndMixWithOthers])
    try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
    let inputNode = self.audioEngine.inputNode

    // Create and configure the speech recognition request.
    self.recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
    guard let recognitionRequest = self.recognitionRequest else { fatalError("Unable to created a SFSpeechAudioBufferRecognitionRequest object") }
    recognitionRequest.shouldReportPartialResults = true
    
    // Keep speech recognition data on device
    recognitionRequest.requiresOnDeviceRecognition = true
    
    // Create a recognition task for the speech recognition session.
    // Keep a reference to the task so that it can be canceled.
    self.recognitionTask = self.speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
        
        var isFinal = false
        
        if let result = result {
            
            // Update the text view with the results.
            let textResult = result.bestTranscription.formattedString
            isFinal = result.isFinal
            
            let confidence = result.bestTranscription.segments[0].confidence
            
            if confidence > 0.0 {
                
                isFinal = true
                
                completionHandler(textResult)
                
            }
            
        }
        
        if error != nil || isFinal {
            
            // Stop recognizing speech if there is a problem.
            self.audioEngine.stop()
            do {
                
                try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
                
            } catch {
                
                print(error)
                
            }
            inputNode.removeTap(onBus: 0)
            
            self.recognitionRequest = nil
            self.recognitionTask = nil
            
            if error != nil {
                
                completionHandler(nil)
                
            }
            
        }
        
    }

    // Configure the microphone input.
    let recordingFormat = inputNode.outputFormat(forBus: 0)
    inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
        self.recognitionRequest?.append(buffer)
    }
    
    self.audioEngine.prepare()
    try self.audioEngine.start()

}

Again, is there a build-in method to capture audio in CarPlay? If not, why would the AVAudioSession throw this error?

Error Domain=NSOSStatusErrorDomain Code=560030580 "Session deactivation failed" UserInfo={NSLocalizedDescription=Session deactivation failed}

Hello @zedsaid,

The setActive(_:options:) documentation states:

"Deactivating an audio session with running audio objects stops the objects, makes the session inactive, and returns an AVAudioSessionErrorCodeIsBusy error."

AVAudioSessionErrorCodeIsBusy corresponds to the error code you are receiving:

AVAudioSessionErrorCodeIsBusy = '!act',                // 0x21616374, 560030580

So, it appears that your app has running audio objects when you attempt to deactivate the audio session.

-- Greg

I actually think I figured it out. It seems I need to capture the category before I change it:

// Save previous category
self.previousCategory = audioSession.category

And then set it back before I stop the audio session being active:

do {
    if let category = self.previousCategory {
        try audioSession.setCategory(category)
    }
    try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
} catch {
    print(error)
}

I no longer get the error that an audio session/object is running.

Hey @zedsaid,

I don't think you should have to store and then re-set the category before session deactivation. If that worked here, it seems to be a side-effect. Can you provide a focused sample that reproduces the issue?

--Greg

Method to capture voice input when using CPVoiceControlTemplate
 
 
Q