如何使 SFSpeechRecognizer 在 macOS 上可用?

How to make SFSpeechRecognizer available on macOS?

我正在尝试使用 Apple 的语音框架在 macOS 10.15.1 上进行语音识别。在 macOS 10.15 之前,语音识别仅在 iOS 上可用,但根据 the documentation and this talk,现在应该也可在 macOS 上使用。

但是,我所有使用它的尝试都导致 SFSpeechRecognizerisAvailable 属性 被设置为 false。根据那次谈话和文档,我启用了 Siri 并确保我的应用程序将 "Privacy - Speech Recognition Usage Description" 键设置为 Info.plist.

中的字符串值

我还尝试启用代码签名(this question 建议可能有必要),在“系统”偏好设置中的“键盘”>“听写”下启用“听写”。

这是一些示例代码,尽管细节可能并不重要;我已经尝试使用 Storyboard 而不是 SwiftUI,将 SFSpeechRecognizer 的实例化放在 requestAuthorization 回调的内部和外部,未指定语言环境等。似乎没有任何效果:

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

特别奇怪的是,如果我 运行 应用程序然后单击 "Try auth" 按钮,回调返回的 authStatus 始终是 .authorized。但是,我从未看到要求我授权该应用程序的对话框,并且该应用程序未显示在“系统偏好设置”>“安全和隐私”>“隐私”>“语音识别”下的授权应用程序列表中。

尽管如此,之后单击 "Test" 按钮会导致打印 not available

看来我对macOS privacy/permissions系统的理解有一些漏洞,但我不确定如何进一步调试。我也认为应该可以让它工作,因为我在 Whosebug 上看到其他问题表明人们已经这样做了,例如 here, here.

编辑:根据评论的建议,我尝试简单地忽略 isAvailable 是错误的事实,方法是用实际尝试转录文件的代码替换我的检查,例如:

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

然后失败,打印:The operation couldn’t be completed. (kAFAssistantErrorDomain error 1700.)。所以看起来确实有必要检查 isAvailable,我的问题仍然是:如何让它成为 true?

SFSpeechRecognizer 有类似的问题...也许你可以在请求授权之前设置 SFSpeechRecognizer 的委托,如图 here.

例如:

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 {

}

然后 the authorisation dialog 将正常显示。

另外,好像只有在调用recognitionTask的时候,才会要求用户给予权限。相反,单独调用 requestAuthorization 不会有任何效果。