Как правильно настроить AVAudioSession и AVAudioEngine при использовании как SFSpeechRecognizer, так и AVSpeechSythesizer - PullRequest
0 голосов
/ 05 ноября 2018

Я пытаюсь создать приложение, которое одновременно использует 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

}

Когда используется один, этот код работает как шарм. Однако, когда я хочу прочитать этот расшифрованный текст, используя объект AVSpeechSynthesizer, кажется, что ничего не ясно.

Я просмотрел несколько сообщений о переполнении стека, в которых предлагалось изменить

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])

Это полностью ошеломило меня, поскольку я действительно понятия не имею, что происходило сложно. Буду очень признателен за любое соответствующее объяснение!

1 Ответ

0 голосов
/ 08 ноября 2018

Я разрабатываю приложение как с SFSpeechRecognizer, так и с AVSpeechSythesizer, и для меня .setCategory(.playAndRecord, mode: .default) отлично работает, и это лучшая категория для наших нужд, согласно Apple . Даже я могу .speak() каждую транскрипцию SFSpeechRecognitionTask, пока аудио движок работает без каких-либо проблем. Мое мнение где-то в логике вашей программы вызывает сбой. Было бы хорошо, если вы можете обновить свой вопрос с соответствующей ошибкой.

И о том, почему категория .multiRoute работает: я думаю, что есть проблема с AVAudioInputNode. Если вы видите в консоли и ошибка, как это

Завершение приложения из-за необработанного исключения «com.apple.coreaudio.avfaudio», причина: «обязательное условие - ложь: IsFormatSampleRateAndChannelCountValid (hwFormat)

или как это

Завершение работы приложения из-за необработанного исключения 'com.apple.coreaudio.avfaudio', причина: 'обязательное условие: false: nullptr == Tap ()

вам нужно только переупорядочить некоторые части кода, например, переместить настройку аудио-сеанса куда-нибудь, где вызывается только один раз , или убедиться, что ответвление входного узла всегда удалено перед установкой новой, даже если задача распознавания успешно завершена или нет. И, возможно (я никогда не работал с ним) .multiRoute может повторно использовать один и тот же входной узел благодаря своей природе работы с различными аудиопотоками и маршрутами.

Я оставляю ниже логику, которую я использую с моей программой после сеанса Apple WWDC :

Настройка категории

override func viewDidLoad() { //or init() or when necessarily
    super.viewDidLoad()
    try? AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default)
}

Validations / разрешение

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()
}

Начиная STT

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

Окончание STT

func endSpeechRecognition() {
    recognitionTask?.finish()
    stopAudioEngine()
}

Отмена STT

func cancelSpeechRecognition() {
    recognitionTask?.cancel()
    stopAudioEngine()
}

Остановка звукового движка

func stopAudioEngine() {
    audioEngine.stop()
    audioEngine.inputNode.removeTap(onBus: 0)
    recognitionRequest.endAudio()        
}

И с этим в любом месте моего кода я могу вызвать AVSpeechSynthesizer экземпляр и произнести высказывание.

...