使用SFSpeechRecognitor和AVSpeechSythesizer时如何正确设置AVAudioSession和AVAudioEngine

使用SFSpeechRecognitor和AVSpeechSythesizer时如何正确设置AVAudioSession和AVAudioEngine,ios,swift,avfoundation,avaudiosession,avaudioengine,Ios,Swift,Avfoundation,Avaudiosession,Avaudioengine,我正在尝试创建一个同时利用STT(语音到文本)和TTS(文本到语音)的应用程序。然而,我遇到了一些模糊不清的问题,非常感谢您的专业知识 该应用程序由屏幕中央的一个按钮组成,单击该按钮后,使用下面的代码启动所需的语音识别功能 // MARK: - Constant Properties let audioEngine = AVAudioEngine() // MARK: - Optional Properties var recognitionRequest: SFSpeechAudio

我正在尝试创建一个同时利用STT(语音到文本)和TTS(文本到语音)的应用程序。然而,我遇到了一些模糊不清的问题,非常感谢您的专业知识

该应用程序由屏幕中央的一个按钮组成,单击该按钮后,使用下面的代码启动所需的语音识别功能

// MARK: - Constant Properties

let audioEngine = AVAudioEngine()



// MARK: - Optional Properties

var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
var recognitionTask: SFSpeechRecognitionTask?
var speechRecognizer: SFSpeechRecognizer?



// MARK: - Functions

internal func startSpeechRecognition() {

    // Instantiate the recognitionRequest property.
    self.recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

    // Set up the audio session.
    let audioSession = AVAudioSession.sharedInstance()
    do {
        try audioSession.setCategory(.record, mode: .measurement, options: [.defaultToSpeaker, .duckOthers])
        try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
    } catch {
        print("An error has occurred while setting the AVAudioSession.")
    }

    // Set up the audio input tap.
    let inputNode = self.audioEngine.inputNode
    let inputNodeFormat = inputNode.outputFormat(forBus: 0)

    self.audioEngine.inputNode.installTap(onBus: 0, bufferSize: 512, format: inputNodeFormat, block: { [unowned self] buffer, time in
        self.recognitionRequest?.append(buffer)
    })

    // Start the recognition task.
    guard
        let speechRecognizer = self.speechRecognizer,
        let recognitionRequest = self.recognitionRequest else {
            fatalError("One or more properties could not be instantiated.")
    }

    self.recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest, resultHandler: { [unowned self] result, error in

        if error != nil {

            // Stop the audio engine and recognition task.
            self.stopSpeechRecognition()

        } else if let result = result {

            let bestTranscriptionString = result.bestTranscription.formattedString

            self.command = bestTranscriptionString
            print(bestTranscriptionString)

        }

    })

    // Start the audioEngine.
    do {
        try self.audioEngine.start()
    } catch {
        print("Could not start the audioEngine property.")
    }

}



internal func stopSpeechRecognition() {

    // Stop the audio engine.
    self.audioEngine.stop()
    self.audioEngine.inputNode.removeTap(onBus: 0)

    // End and deallocate the recognition request.
    self.recognitionRequest?.endAudio()
    self.recognitionRequest = nil

    // Cancel and deallocate the recognition task.
    self.recognitionTask?.cancel()
    self.recognitionTask = nil

}
单独使用时,此代码就像一个符咒。然而,当我想使用
avspeechsynthesis
对象读取转录的文本时,似乎没有什么是清晰的

我浏览了多个堆栈溢出帖子的建议,其中建议修改

audioSession.setCategory(.record, mode: .measurement, options: [.defaultToSpeaker, .duckOthers])
以下

audioSession.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .duckOthers])
但这是徒劳的。分别运行STT和TTS后,应用程序仍在崩溃

对我来说,解决办法是使用这个,而不是前面提到的

audioSession.setCategory(.multiRoute, mode: .default, options: [.defaultToSpeaker, .duckOthers])

这让我完全不知所措,因为我真的不知道到底发生了什么。如有任何相关解释,我将不胜感激

我正在开发一款同时使用SFSpeechRecognizer和AVSpeechSythezer的应用程序,对我来说,
.setCategory(.playAndRecord,mode:.default)
运行良好,是满足我们需求的最佳类别。甚至,当音频引擎运行时,我能够
.speak()
对SFSpeechRecognitionTask的每一次转录都没有任何问题。我的意见是在你的程序逻辑的某个地方导致了崩溃。如果你能用相应的错误更新你的问题就好了

关于
.multiRoute
类别工作的原因:我想
AVAudioInputNode
有问题。如果您在控制台中看到这样的错误

由于未捕获的异常“com.apple.coreaudio.avfaudio”而终止应用程序,原因:“必需条件为false:IsFormatSampleRate和ChannelCountValid(hwFormat)

还是像这样

由于未捕获的异常“com.apple.coreaudio.avfaudio”而终止应用程序,原因:“必需条件为false:nullptr==Tap()

您只需要对代码的某些部分进行重新排序,例如将音频会话的设置移动到某个地方,该位置只调用一次,或者确保在安装新的音频会话之前,始终删除输入节点的点击,即使识别任务是否成功完成。也许(我从未使用过它)
.multiRoute
能够通过使用不同的音频流和路由来重用相同的输入节点

下面是我在程序中使用的逻辑,如下所示:

设置类别 验证/权限 启动STT 结束STT 取消STT 停止音频引擎
这样,在我的代码中的任何地方,我都可以调用
AVSpeechSynthesizer
实例并说出一句话。

您好,谢谢您的建设性回答。您的代码运行得非常好,但是您是否尝试过在
AVAudioSession.sharedInstance().setCategory(.playAndRecord,mode:.default)
方法中添加
.defaultToSpeaker
?当我这样做的时候,一切都停止了!是的,我已经做了,一切都很好。我在两个物理设备上运行它:12.1版的iPhone6和10.3.2版的越狱iPhone6。你的解决方案终于成功了。我意识到我正在呼叫
.setCategory
我正在启动我的音频引擎。将那行代码移到
viewDidLoad
中对我来说是一个合适的解决方案,你的解释是完美的。谢谢你的帮助!
override func viewDidLoad() { //or init() or when necessarily
    super.viewDidLoad()
    try? AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default)
}
func shouldProcessSpeechRecognition() {
    guard AVAudioSession.sharedInstance().recordPermission == .granted,
        speechRecognizerAuthorizationStatus == .authorized,
        let speechRecognizer = speechRecognizer, speechRecognizer.isAvailable else { return }
        //Continue only if we have authorization and recognizer is available

        startSpeechRecognition()
}
func startSpeechRecognition() {
    let format = audioEngine.inputNode.outputFormat(forBus: 0)
    audioEngine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: format) { [unowned self] (buffer, _) in
        self.recognitionRequest.append(buffer)
    }
    audioEngine.prepare()
    do {
        try audioEngine.start()
        recognitionTask = speechRecognizer!.recognitionTask(with: recognitionRequest, resultHandler: {...}
    } catch {...}
}
func endSpeechRecognition() {
    recognitionTask?.finish()
    stopAudioEngine()
}
func cancelSpeechRecognition() {
    recognitionTask?.cancel()
    stopAudioEngine()
}
func stopAudioEngine() {
    audioEngine.stop()
    audioEngine.inputNode.removeTap(onBus: 0)
    recognitionRequest.endAudio()        
}