【iOS】Speech Frameworkの実装

先日社内のWWDCキャッチアップイベントで調査した、
Speech Frameworkについてまとめます。

詳細な処理に関してはAppleのSampleCodeを参考ください。
[Using Speech Recognition with AVAudioEngine](https://developer.apple.com/library/prerelease/content/samplecode/SpeakToMe/Introduction/Intro.html#//apple_ref/doc/uid/TP40017110)

# 流れ

1. plistの設定
2. フレームワークのインポート
3. SFSpeechRecognizerの作成
4. ユーザー承認
5. リクエストの作成
6. リクエストの開始

# plistの設定
plistに以下の項目を加えます。
**NSSpeechRecognitionUsageDescription**

マイクを利用する場合は次の項目も加えます。
**Privacy - Microphone Usage Description**

# フレームワークのインポート
関連クラスはすべてSpeechフレームワークに入っています。

```swift
import Speech
```

# SFSpeechRecognizerの作成
ロケールを指定してSFSpeechRecognizerを作成します。
指定しない場合はユーザーのロケールを使用します。
対応している言語は以下を参照ください。
[【iOS 10】Speechフレームワークで音声認識 - 対応言語リスト付き](http://d.hatena.ne.jp/shu223/20160615/1466036672)

```swift
let speechRecognizer = SFSpeechRecognizer(locale: Locale(localeIdentifier: "en_US"))!
```

# ユーザー承認

SFSpeechRecognizerのrequestAuthorization(_:)メソッドを呼び出し、
ユーザーに許可を求めます。
ハンドラーの引数には認証の状態が入って来るので、
場合に応じて処理します。

```swift
SFSpeechRecognizer.requestAuthorization { [weak self] authStatus in
    // some operation
}
```

# リクエストの作成
SFSpeechRecognitionRequestオブジェクトを作成します。
実際には以下の二つのどちらかです。

- SFSpeechAudioBufferRecognitionRequest(マイク等のオーディオバッファを利用)
- SFSpeechURLRecognitionRequest(オーディオファイルを利用)

## マイクを利用する場合
SFSpeechAudioBufferRecognitionRequestを使用します。

```swift
recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
```

## オーディオファイルを使用する場合
SFSpeechURLRecognitionRequestを使用します。
生成時にファイルのパスを指定します。

```swift
let audioPath = Bundle.main().pathForResource("steve", ofType: "aiff")!
recognitionRequest = SFSpeechURLRecognitionRequest(url: URL(fileURLWithPath: audioPath))
```

オーディオファイルの作成は以下を参考にしました。
[Macのsayコマンドの使い方](http://qiita.com/zakuroishikuro/items/0c17acb21f119647c205)

# リクエストの開始
はじめに作成したSFSpeechRecognizerに、
先ほど作成したSFSpeechRecognitionRequestを投げると同時に、
結果のコールバックの設定をします。

コールバックの設定には以下の二つの方法があります。

- recognitionTask(with:resultHandler:)による、クロージャーでの登録
- recognitionTask(with:delegate:)による、デリゲートでの登録

デリゲートの方が状況に応じて呼ばれるメソッドが分かれているので、
詳細に処理することができます。

## クロージャーの場合
SFSpeechRecognitionResult?型のオブジェクトが渡されます。
このオブジェクトのbestTranscription.formattedStringにアクセスし、
文字列を受け取ります。

また、
isFinalには終了かどうかの状態が入っているので、
場合に応じて処理します。

```swift
recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
    var isFinal = false
            
    if let result = result {
                
        let text = result.bestTranscription.formattedString
        isFinal = result.isFinal
    }

    if isFinal {
        // 終了時の処理
    }
}
```

## デリゲートの場合
SFSpeechRecognitionTaskDelegateを実装することで結果を受け取ることができます。

受け取るメソッドはspeechRecognitionTask(_:didHypothesizeTranscription:)です。
これは、中間結果を受け取るメソッドです。
先ほどのSFSpeechRecognitionResultのbestTranscriptionと同じ、
SFTranscription型の引数を受け取るので、formattedStringを取り出します。

```swift
func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didHypothesizeTranscription transcription: SFTranscription) {
        
    let text = transcription.formattedString
}
```

デリゲートにはこの他、

- 終了時に呼ばれる、speechRecognitionTask(_:didFinishSuccessfully:)メソッド
- 処理のキャンセル時に呼ばれる、speechRecognitionTaskWasCancelled(_:)メソッド

など合計7つのメソッドがあります。

# その他の処理
オーディオファイルを利用する場合は以上です。
マイクを利用する場合はマイクからの入力を受け取る処理が必要となります。
この処理にはAVAudioEngineクラスを使用します。

## AVAudioEngineの作成

```swift
let audioEngine = AVAudioEngine()
```

## 入力を受けとった際の処理を登録
入力を受け取った際にSFAudioBufferRecognitionRequestに対して、
受け取ったバッファを追加します。

```swift
guard let inputNode = audioEngine.inputNode else {
    fatalError("Audio Engine has no input node")
}

let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            
    self?.recognitionRequest?.append(buffer)
}
```

## 収録の開始、終了
収録の開始は

```swift
audioEngine.prepare()
try audioEngine.start()
```

終了は

```swift
audioEngine.stop()
recognitionRequest?.endAudio()
```

# 制限
Speech Frameworkに関する制限は以下の記事が参考になりました。
[【iOS10】Speech Recognition API(音声認識API)の制約まとめ](http://qiita.com/nerd0geek1/items/af9f0555c3f6c8d878eb)

# 最後に
想像以上にお手軽に実装できました。

# 参考
大変お世話になりました。ありがとうございます。

[Using Speech Recognition with AVAudioEngine](https://developer.apple.com/library/prerelease/content/samplecode/SpeakToMe/Introduction/Intro.html#//apple_ref/doc/uid/TP40017110)
[【iOS 10】Speechフレームワークで音声認識 - 対応言語リスト付き](http://d.hatena.ne.jp/shu223/20160615/1466036672)
[【iOS10】Speech Recognition API(音声認識API)の制約まとめ](http://qiita.com/nerd0geek1/items/af9f0555c3f6c8d878eb)
[Macのsayコマンドの使い方](http://qiita.com/zakuroishikuro/items/0c17acb21f119647c205)