しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

【Swift】Speech Recognitionの制限について検証する

iOS10から登場した音声認識API(Speech Recognition)の挙動について調べてみました。

60秒を超えた時の挙動

テストプロジェクトを作り、ViewControllerを下のようにして検証しました。
このコードでは5秒時点と55秒時点と62秒時点でのタスクステータスを見ています。

import UIKit
import Speech

class ViewController: UIViewController {
    private var recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
    private var speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
    private var recognitionTask: SFSpeechRecognitionTask?
    private var audioEngine = AVAudioEngine()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        SFSpeechRecognizer.requestAuthorization { _ in }
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        start()
    }
    
    func start() {
        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
        audioEngine = AVAudioEngine()
        
        let recordingFormat = audioEngine.inputNode.outputFormat(forBus: 0)
        audioEngine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            self.recognitionRequest.append(buffer)
        }
        try! audioEngine.start()
        
        recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
            print("resultHandler")
            print(result?.bestTranscription.formattedString ?? "")
            
            if let error = error {
                print("ERROR!")
                print(error.localizedDescription)
            }
        }
        print(recognitionTask!.state.rawValue)
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0, execute: {
            print(self.recognitionTask!.state.rawValue)
            print("5!")
        })
        DispatchQueue.main.asyncAfter(deadline: .now() + 55.0, execute: {
            print(self.recognitionTask!.state.rawValue)
            print("55!")
        })
        DispatchQueue.main.asyncAfter(deadline: .now() + 62.0, execute: {
            print(self.recognitionTask!.state.rawValue)
            print("62!")
        })
    }
}

検証結果は下の通りです。

  • 60秒経過するとresultHandlerが「kAFAssistantErrorDomain Code=203 "Retry"」エラーを引数にして呼ばれる
  • 5秒時点でのタスクのステータスはrunning
  • 55秒時点でのタスクのステータスはrunning
  • 62秒時点でのタスクのステータスはcompleted

それとこの挙動は「マイクに一度でも話しかけたかどうか」で変化がありました。
一度でもマイクに話しかけると下のような挙動になりました。

  • 60秒経過してもresultHandlerは呼ばれない
  • 55秒時点でのタスクのステータスはrunningではなくcompleted

自分でキャンセルした場合の挙動

制限ではないのですが、自分でタスクをキャンセルした時はresultHandlerが下エラーを引数に呼ばれます。

The operation couldn’t be completed. (kAFAssistantErrorDomain error 216.)

ユーザー辺りの制限

下のように延々と音声認識をして、どのくらいで制限が引っかかるか試しました。
連続で実施すると60秒制限に引っかかるので、5秒おきに開始と終了を繰り返しています。

import UIKit
import Speech

class ViewController: UIViewController {
    private var recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
    private var speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
    private var recognitionTask: SFSpeechRecognitionTask?
    private var audioEngine = AVAudioEngine()
    var startCount = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        SFSpeechRecognizer.requestAuthorization { _ in }
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        start()
    }
    
    func start() {
        startCount += 1
        print(startCount)
        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
        audioEngine = AVAudioEngine()
        
        let recordingFormat = audioEngine.inputNode.outputFormat(forBus: 0)
        audioEngine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            self.recognitionRequest.append(buffer)
        }
        try! audioEngine.start()
        
        recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
            print("resultHandler")
            print(result?.bestTranscription.formattedString ?? "")
            
            if let error = error {
                print("ERROR!")
                print(error.localizedDescription)
            }
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0, execute: {
            self.recognitionTask?.cancel()
            self.recognitionTask?.finish()
            self.audioEngine.stop()
            print("RESTART")
            self.start()
        })
    }
}

結果としては2時間ほどは音声認識を使う事ができました。
時間がなかったのでそこまでしか検証しなかったのですが、結構長い時間使う事ができそうです。