4

I am trying to use Apple's Speech framework to do speech recognition on macOS 10.15.1. Before macOS 10.15, speech recognition was only available on iOS, but according to the documentation and this talk, should now be available on macOS as well.

However, all my my attempts to use it have resulted in the SFSpeechRecognizer's isAvailable property being set to false. Per that talk and the documentation I've enabled Siri and made sure that my app has the "Privacy - Speech Recognition Usage Description" key set to a string value in Info.plist.

I've also tried enabling code signing (which this question suggests might be necessary), enabling Dictation under Keyboard > Dictation in the System preferences.

Here's some example code, although the specifics probably aren't important; I've tried it using a Storyboard instead of SwiftUI, putting the instantiation of the SFSpeechRecognizer inside and outside the requestAuthorization callback, leaving the locale unspecified, etc. Nothing seems to have any effect:

import SwiftUI
import Speech

struct ContentView: View {

    func tryAuth() {
        SFSpeechRecognizer.requestAuthorization { authStatus in
            switch authStatus {
                case .authorized:
                    print("authorized")
                case .denied:
                    print("denied")
                case .restricted:
                    print("restricted")
                case .notDetermined:
                    print("notDetermined")
                @unknown default:
                    print("unanticipated auth status encountered")
            }
        }
    }

    func speechTest() {
        guard let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US")) else {
            // Not supported for device's locale
            print("couldnt get recognizer")
            return
        }

        if !recognizer.isAvailable {
            print("not available")
            return
        }

        print("Success")
    }

    var body: some View {
        VStack {
            Button("Try auth") {
                self.tryAuth()
            }
            Button("Test") {
                self.speechTest()
            }
        }
    }
}

What's especially odd is that if I run the app and then click the "Try auth" button, the authStatus returned from the callback is always .authorized. However, I've never been presented with a dialog asking me to authorize the app, and the app doesn't show up in the list of authorized apps under System Preferences > Security and Privacy > Privacy > Speech Recogniztion.

Nonetheless, clicking the "Test" button afterwards results in printing not available.

It seems like there's some hole in my understanding of the macOS privacy/permissions system, but I'm not sure how to debug further. I also think it should be possible to get this working, because I've seen other questions on StackOverflow suggesting that people have done so, for example here, here.

EDIT: At the suggestion of a comment, I tried simply ignoring the fact that isAvailable is false by replacing my check for it with code to actually try to transcribe a file, e.g.:

let request = SFSpeechURLRecognitionRequest(url: URL(fileURLWithPath: "/Users/james/Downloads/test.wav"))

recognizer.recognitionTask(with: request) { (result, error) in
    guard let result = result else {
        print("There was an error transcribing that file")
        print("print \(error!.localizedDescription)")
        return
    }

    if result.isFinal {
        print(result.bestTranscription.formattedString)
    }
}

Then it fails, printing: The operation couldn’t be completed. (kAFAssistantErrorDomain error 1700.). So it seems like it really is necessary to check for isAvailable, and my question remains: how to get it to be true?

James Porter
  • 1,853
  • 2
  • 17
  • 30
  • What is the source of audio? – El Tomato Nov 29 '19 at 23:15
  • @ElTomato So far I haven't even gotten as far as providing audio to be transcribed, since it won't work without the `SFSpeechRecognizer`'s `isAvailable` property being `true`. That said, if I ignore that, don't check for availability first, and just provide a file to be transcribed from a URL, it fails with `"The operation couldn’t be completed. (kAFAssistantErrorDomain error 1700.)"`. I've edited the original question to reflect that I've tried this. – James Porter Nov 29 '19 at 23:59
  • I have the same issue, but in my case `kAFAssistantErrorDomain` error code is 601. – Mateusz Dec 15 '19 at 01:40
  • This now appears to work fine! (on Ventura) – Greg Detre Mar 14 '23 at 00:20
  • You must give permissions to access computer microphone. See https://stackoverflow.com/a/76836073/4815438 – veladan Aug 04 '23 at 12:50

1 Answers1

0

Had similar problems with SFSpeechRecognizer... Perhaps you can set the delegate of SFSpeechRecognizer before requesting for authorization, as shown here.

For example:

class ViewController: NSViewController {
  var speechRecognizer: SFSpeechRecognizer!

  override func viewDidLoad() {
    super.viewDidLoad()
    speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))
    speechRecognizer.delegate = self
  }

  override func viewWillAppear() {
    SFSpeechRecognizer.requestAuthorization { authStatus in
       ...
    }

    if !speechRecognizer.isAvailable {
      print("Not available!")
    }

    let url = Bundle.main.url(forResource: "sample", withExtension: "mp3")!
    let request = SFSpeechURLRecognitionRequest(url: url)

    // will now ask for authorisation
    speechRecognizer.recognitionTask(with: request) { (result, error) in
        ...
    }
  }
}

extension ViewController: SFSpeechRecognizerDelegate {

}

Then the authorisation dialog will be properly shown.

In addition, it seems that only when there is a call to recognitionTask, that the user will be asked to give permission. Instead, calling requestAuthorization alone will not have any effect.

93xrli
  • 389
  • 3
  • 3
  • That worked, thank you very much! I probably should have tried setting a delegate, but frustrating that they don't explicitly state in the documentation that it's a requirement. – James Porter Dec 29 '19 at 03:22