Как сделать iOS речевым к тексту постоянным

Я провожу первоначальное исследование нового потенциального продукта. Часть этого продукта требует, чтобы Speech-To-Text на iPhone и iPad оставался включенным до тех пор, пока пользователь не отключит его. После того, как я сам его использовал, я заметил, что он либо автоматически отключается через 30 секунд, независимо от того, перестал ли пользователь говорить, либо он отключается после того, как из динамика произойдет определенное количество сомнительных слов. В любом случае, этот продукт требует, чтобы он оставался включенным все время, пока явно не будет указано, что нужно остановиться. Кто-нибудь работал с этим раньше? И да, я пробовал хороший поиск, я не мог найти ничего существенного, и особенно ничего написанного на правильном языке. Спасибо, друзья!

4 ответа

Решение

Лучший ответ на это, чтобы Google больше! Попробовав несколько разных ключевых слов, вы наткнетесь на этот документ, найденный здесь. Как видно из прочтения, в разделе распознавания речи они упоминают, что

iOS 10 представляет новый API, который поддерживает непрерывное распознавание речи и помогает создавать приложения, которые могут распознавать речь и транскрибировать ее в текст.

Еще более удивительно, что они даже включают некоторый код для использования при определении нового распознавателя речи, как показано ниже:

let recognizer = SFSpeechRecognizer()
let request = SFSpeechURLRecognitionRequest(url: audioFileURL)
recognizer?.recognitionTask(with: request, resultHandler: { (result, error) in
 print (result?.bestTranscription.formattedString)
})

Потрясающие! Так вот и все, легкий поиск. Надеюсь, что это поможет некоторым людям в будущем, так как я уверен, что придет день, когда нам больше не придется использовать наши пальцы, чтобы пользоваться телефоном.

Я нашел здесь учебник, который покажет вашу речь. Но смотрите заметки:

Apple ограничивает распознавание для каждого устройства. Предел не известен, но вы можете связаться с Apple для получения дополнительной информации. Apple ограничивает распознавание для каждого приложения.

Если вы регулярно превышаете лимиты, обязательно свяжитесь с Apple, они, вероятно, могут ее решить.

Распознавание речи использует много энергии и данных.

Распознавание речи длится около минуты за раз.

РЕДАКТИРОВАТЬ

Этот ответ был для iOS 10. Я ожидаю выхода iOS 12 в октябре 2018 года, но Apple по-прежнему говорит:

Запланируйте ограничение продолжительности звука в одну минуту. Распознавание речи может значительно увеличить время автономной работы и использования сети. В iOS 10 продолжительность звукового сопровождения ограничена примерно одной минутой, что аналогично пределу для диктовки, связанной с клавиатурой.

Смотрите: https://developer.apple.com/documentation/speech

Там нет никаких изменений API в Speech Framework для iOS 11 и 12. Подробно смотрите все изменения API, особенно для iOS 12, Пол Хадсон: API iOS 12 отличается

Так что мой ответ должен быть действительным.

Это поможет вам автоматически запускать запись каждые 40 секунд, даже если вы ничего не говорите. Если вы что-то говорите, а затем на 2 секунды наступает тишина, она прекращается и вызывается функция didfinishtalk.

@objc  func startRecording() {


    self.fullsTring = ""
    audioEngine.reset()

    if recognitionTask != nil {
        recognitionTask?.cancel()
        recognitionTask = nil

    }



    let audioSession = AVAudioSession.sharedInstance()
    do {
        try audioSession.setCategory(.record)
        try audioSession.setMode(.measurement)
        try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
        try audioSession.setPreferredSampleRate(44100.0)

        if audioSession.isInputGainSettable {
            let error : NSErrorPointer = nil

            let success = try? audioSession.setInputGain(1.0)

            guard success != nil else {
                print ("audio error")
                return
            }
            if (success != nil) {
                print("\(String(describing: error))")
            }
        }
        else {
            print("Cannot set input gain")
        }
    } catch {
        print("audioSession properties weren't set because of an error.")
    }
    recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

    let inputNode = audioEngine.inputNode
    guard let recognitionRequest = recognitionRequest else {
        fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object")
    } 

    recognitionRequest.shouldReportPartialResults = true 
    self.timer4 = Timer.scheduledTimer(timeInterval: TimeInterval(40), target: self, selector: #selector(againStartRec), userInfo: nil, repeats: false)

    recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest, resultHandler: { (result, error ) in  

        var isFinal = false  //8

        if result != nil {
            self.timer.invalidate()
            self.timer = Timer.scheduledTimer(timeInterval: TimeInterval(2.0), target: self, selector: #selector(self.didFinishTalk), userInfo: nil, repeats: false)

            let bestString = result?.bestTranscription.formattedString
            self.fullsTring = bestString!

     self.inputContainerView.inputTextField.text = result?.bestTranscription.formattedString

           isFinal = result!.isFinal

        }
        if error == nil{

        }
        if  isFinal {

            self.audioEngine.stop()
            inputNode.removeTap(onBus: 0)

            self.recognitionRequest = nil
            self.recognitionTask = nil
            isFinal = false

        }
        if error != nil{
            URLCache.shared.removeAllCachedResponses()



            self.audioEngine.stop()
                           inputNode.removeTap(onBus: 0)

                                    guard let task = self.recognitionTask else {
                                                      return
                                                  }
                                                  task.cancel()
                                                  task.finish()



        }
    })
    audioEngine.reset()
    inputNode.removeTap(onBus: 0)

  let recordingFormat = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 1)
    inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in
        self.recognitionRequest?.append(buffer)
    }

    audioEngine.prepare()

    do {
        try audioEngine.start()
    } catch {
        print("audioEngine couldn't start because of an error.")
    }


    self.hasrecorded = true



}


@objc func againStartRec(){

    self.inputContainerView.uploadImageView.setBackgroundImage( #imageLiteral(resourceName: "microphone") , for: .normal)
      self.inputContainerView.uploadImageView.alpha = 1.0
            self.timer4.invalidate()
    timer.invalidate()
           self.timer.invalidate()

            if ((self.audioEngine.isRunning)){

                self.audioEngine.stop()
                self.recognitionRequest?.endAudio()
                self.recognitionTask?.finish()


            }
   self.timer2 = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(startRecording), userInfo: nil, repeats: false)

}


@objc func didFinishTalk(){


    if self.fullsTring != ""{

     self.timer4.invalidate()
     self.timer.invalidate()
     self.timer2.invalidate()


          if ((self.audioEngine.isRunning)){

                 self.audioEngine.stop()
                 guard let task = self.recognitionTask else {
                    return
                 }
                 task.cancel()
                 task.finish()


             }



    }
}
      
///
/// Code lightly adopted by  from https://developer.apple.com/documentation/speech/recognizing_speech_in_live_audio?language=swift
///
/// Modifications from original:
/// - Color of text changes every time a new "chunk" of text is transcribed
/// -- This was a feature I added while playing with my nephews. They loved it (2 and 6) (we kept saying rainbow)
/// - I added a bit of logic to scroll to the end of the text once new chunks were added
/// - I formatted the code using swiftformat
///

import Speech
import UIKit

public class ViewController: UIViewController, SFSpeechRecognizerDelegate {
  private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))!

  private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?

  private var recognitionTask: SFSpeechRecognitionTask?

  private let audioEngine = AVAudioEngine()

  @IBOutlet var textView: UITextView!

  @IBOutlet var recordButton: UIButton!

  let colors: [UIColor] = [.red, .orange, .yellow, .green, .blue, .purple]

  var colorIndex = 0

  override public func viewDidLoad() {
    super.viewDidLoad()

    textView.textColor = colors[colorIndex]
    // Disable the record buttons until authorization has been granted.
    recordButton.isEnabled = false
  }

  override public func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // Configure the SFSpeechRecognizer object already
    // stored in a local member variable.
    speechRecognizer.delegate = self

    // Asynchronously make the authorization request.
    SFSpeechRecognizer.requestAuthorization { authStatus in

      // Divert to the app's main thread so that the UI
      // can be updated.
      OperationQueue.main.addOperation {
        switch authStatus {
        case .authorized:
          self.recordButton.isEnabled = true

        case .denied:
          self.recordButton.isEnabled = false
          self.recordButton.setTitle("User denied access to speech recognition", for: .disabled)

        case .restricted:
          self.recordButton.isEnabled = false
          self.recordButton.setTitle("Speech recognition restricted on this device", for: .disabled)

        case .notDetermined:
          self.recordButton.isEnabled = false
          self.recordButton.setTitle("Speech recognition not yet authorized", for: .disabled)

        default:
          self.recordButton.isEnabled = false
        }
      }
    }
  }

  private func startRecording() throws {
    // Cancel the previous task if it's running.
    recognitionTask?.cancel()
    recognitionTask = nil

    // Configure the audio session for the app.
    let audioSession = AVAudioSession.sharedInstance()
    try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
    try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
    let inputNode = audioEngine.inputNode

    // Create and configure the speech recognition request.
    recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////
    /// The below lines are responsible for keeping the recording active longer
    /// than just short bursts. I've had the recording going all day in somewhat
    /// rudimentary attempts.
    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////
    if #available(iOS 13, *) {
      let supportsOnDeviceRecognition = speechRecognizer.supportsOnDeviceRecognition
      if !supportsOnDeviceRecognition {
        fatalError("On device transcription not supported on this device. It is safe to remove this error but I wanted to add it as a warning that you'd actually see.")
      }
      recognitionRequest!.requiresOnDeviceRecognition = /* only appears to work on device; not simulator */ supportsOnDeviceRecognition
    }

    guard let recognitionRequest = recognitionRequest else { fatalError("Unable to create a SFSpeechAudioBufferRecognitionRequest object") }
    recognitionRequest.shouldReportPartialResults = true

    // Create a recognition task for the speech recognition session.
    // Keep a reference to the task so that it can be canceled.
    recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
      var isFinal = false

      if let result = result {
        // Update the text view with the results.
        self.colorIndex = (self.colorIndex + 1) % self.colors.count
        self.textView.text = result.bestTranscription.formattedString
        self.textView.textColor = self.colors[self.colorIndex]
        self.textView.scrollRangeToVisible(NSMakeRange(result.bestTranscription.formattedString.count - 1, 0))
        isFinal = result.isFinal
        print("Text \(result.bestTranscription.formattedString)")
      }

      if error != nil || isFinal {
        // Stop recognizing speech if there is a problem.
        self.audioEngine.stop()
        inputNode.removeTap(onBus: 0)

        self.recognitionRequest = nil
        self.recognitionTask = nil

        self.recordButton.isEnabled = true
        self.recordButton.setTitle("Start Recording", for: [])
      }
    }

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

    audioEngine.prepare()
    try audioEngine.start()

    // Let the user know to start talking.
    textView.text = "(Go ahead, I'm listening)"
  }

  // MARK: SFSpeechRecognizerDelegate

  public func speechRecognizer(_: SFSpeechRecognizer, availabilityDidChange available: Bool) {
    if available {
      recordButton.isEnabled = true
      recordButton.setTitle("Start Recording", for: [])
    } else {
      recordButton.isEnabled = false
      recordButton.setTitle("Recognition Not Available", for: .disabled)
    }
  }

  // MARK: Interface Builder actions

  @IBAction func recordButtonTapped() {
    if audioEngine.isRunning {
      audioEngine.stop()
      recognitionRequest?.endAudio()
      recordButton.isEnabled = false
      recordButton.setTitle("Stopping", for: .disabled)
    } else {
      do {
        try startRecording()
        recordButton.setTitle("Stop Recording", for: [])
      } catch {
        recordButton.setTitle("Recording Not Available", for: [])
      }
    }
  }
}

Приведенный выше фрагмент кода при запуске на физическом устройстве будет непрерывно («настойчиво») расшифровывать звук с использованием Apple Speech Framework.

Волшебная линия здесь request.requiresOnDeviceRecognition = ...

Если request.requiresOnDeviceRecognitionверно и SFSpeechRecognizer#supportsOnDeviceRecognitionявляется true, то аудио будет непрерывно транскрибироваться до тех пор, пока не разрядится батарея, пользователь не отменит транскрипцию или не произойдет какая-либо другая ошибка/условие завершения. По крайней мере, это верно в моих испытаниях.

Документы:

https://developer.apple.com/documentation/speech/recognizing_speech_in_live_audio

Заметки:

Первоначально я пытался отредактировать этот ответ [0], но хотел добавить так много деталей, что почувствовал, что он полностью захватил исходный ответчик. В идеале я буду поддерживать свой собственный ответ: подход, который переводит его в SwiftUI, а также в компонуемую архитектуру (принимая их пример [1]) в качестве канонического источника быстрого старта для транскрипции голоса на платформах Apple.

0: /questions/33639372/kak-sdelat-ios-rechevyim-k-tekstu-postoyannyim/33639378#33639378

1: https://github.com/pointfreeco/swift-composable-architecture/tree/main/Examples/SpeechRecognition/SpeechRecognition

Другие вопросы по тегам