音声をマイクで取得してテキスト化する方法をご紹介します。

サンプルコード(Xcode8、Swift3.0)

info.plist

ViewController.swift

import UIKit
import Speech
class ViewController: UIViewController {
// 音声認識
private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
private var recognitionTask: SFSpeechRecognitionTask?
private let audioEngine = AVAudioEngine()
private var recognitionResult: SFSpeechRecognitionResult?
//private var inputNode:AVAudioInputNode?
// View
let textView = UITextView()
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
// 音声認識初期化
speechRecognizer.delegate = self
self.requestRecognizerAuthorization()
viewInit()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//--------------------------------
// MARK: - View Init
//--------------------------------
private func viewInit(){
// TextView表示
textView.frame = CGRect(x: 0, y: 0, width: 300, height: 200)
textView.center = self.view.center
textView.layer.borderWidth = 1
textView.layer.borderColor = UIColor.black.cgColor
textView.layer.cornerRadius = 5
self.view.addSubview(textView)
// Button表示
button.frame = CGRect(x: 0, y: 0, width: 300, height: 45)
button.center = CGPoint(x: self.view.center.x, y: self.view.center.y+140)
button.setTitle("音声認識開始", for: .normal)
button.backgroundColor = UIColor.red
button.titleLabel?.textColor = UIColor.white
button.addTarget(self, action: #selector(ViewController.buttonTapped), for: .touchUpInside)
self.view.addSubview(button)
}
//--------------------------------
// MARK: - Action
//--------------------------------
func buttonTapped(){
if audioEngine.isRunning {
audioEngine.stop()
recognitionRequest?.endAudio()
button.isEnabled = false
button.setTitle("音声認識を再開", for: .normal)
print("#### 停止 ####")
} else {
try! startRecording()
button.setTitle("音声認識を中止", for: .normal)
print("#### スタート ####")
}
}
//--------------------------------
// MARK: - 音声認識
//--------------------------------
// 認証処理
private func requestRecognizerAuthorization() {
SFSpeechRecognizer.requestAuthorization { authStatus in
// メインスレッドで処理したい内容のため、OperationQueue.main.addOperationを使う
OperationQueue.main.addOperation { [weak self] in
guard let `self` = self else { return }
switch authStatus {
case .authorized:
self.textView.text = "音声認識へのアクセスが許可されています。"
case .denied:
self.textView.text = "音声認識へのアクセスが拒否されています。"
case .restricted:
self.textView.text = "音声認識へのアクセスが制限されています。"
case .notDetermined:
self.textView.text = "音声認識はまだ許可されていません。"
}
}
}
}
// 録音開始
private func startRecording() throws {
refreshTask()
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(AVAudioSessionCategoryRecord)
try audioSession.setMode(AVAudioSessionModeMeasurement)
try audioSession.setActive(true, with: .notifyOthersOnDeactivation)
recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
guard let inputNode = audioEngine.inputNode else {
fatalError("Audio engine has no input node")
}
guard let recognitionRequest = recognitionRequest else {
fatalError("Unable to created a SFSpeechAudioBufferRecognitionRequest object")
}
recognitionRequest.shouldReportPartialResults = true
recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { [weak self] result, error in
guard let `self` = self else { return }
var isFinal = false
if let result = result {
self.textView.text = result.bestTranscription.formattedString
isFinal = result.isFinal
}
if error != nil || isFinal {
print("error:(error)")
self.audioEngine.stop()
inputNode.removeTap(onBus: 0)
self.recognitionRequest = nil
self.recognitionTask = nil
self.button.isEnabled = true
self.textView.text = "音声認識スタート"
}
}
// マイクから取得した音声バッファをリクエストに渡す
let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
self.recognitionRequest?.append(buffer)
}
try startAudioEngine()
}
private func refreshTask() {
if let recognitionTask = recognitionTask {
recognitionTask.cancel()
self.recognitionTask = nil
}
}
private func startAudioEngine() throws {
// startの前にリソースを確保しておく。
audioEngine.prepare()
try audioEngine.start()
button.setTitle("音声認識停止", for: .normal)
print("#### 音声認識停止 ####")
}
}
extension ViewController: SFSpeechRecognizerDelegate {
// 音声認識の可否が変更したときに呼ばれるdelegate
func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
if available {
button.isEnabled = true
button.setTitle("音声認識スタート", for: .normal)
print("#### 音声認識スタート ####")
} else {
button.isEnabled = false
button.setTitle("音声認識ストップ", for: .normal)
print("#### 音声認識ストップ ####")
}
}
}

解説

ユーザ許可設定

ユーザにマイク利用、音声認識機能を許可してもらうためにinfo.plistNSMicrophoneUsageDescriptionNSSpeechRecognitionUsageDescriptionを追加します。

info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
:
:
:
//////////////// ▼▼ 追加 ▼▼ ////////////////
<key>NSMicrophoneUsageDescription</key>
<string>学習のためにマイクを利用します。</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>学習のために音声認識を利用します。</string>
//////////////// ▲▲ 追加 ▲▲ ////////////////
</dict>
</plist>

フレームワークインポート

まずSpeechフレームワークをインポートします。

import Speech

初期化

次にロケールを指定してSFSpeechRecognizerをインスタンス化します。

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

ユーザー承認

SFSpeechRecognizerのrequestAuthorization(_:)メソッドを呼び出し、
ユーザーに許可を求めます。

ハンドラーの引数には認証の状態が入って来るので、場合に応じて処理します。

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

リクエストの作成

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

オブジェクト名 説明
SFSpeechAudioBufferRecognitionRequest マイク等のオーディオバッファを利用
SFSpeechURLRecognitionRequest オーディオファイルを利用

マイクを利用する場合

SFSpeechAudioBufferRecognitionRequestを使用します。

recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

オーディオファイルを使用する場合

SFSpeechURLRecognitionRequestを使用します。

生成時にファイルのパスを指定します。

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

リクエストの開始

作成したSFSpeechRecognizerに、先ほど作成したSFSpeechRecognitionRequestを投げると同時に、結果のコールバックの設定をします。

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

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

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

クロージャーの場合

SFSpeechRecognitionResult?型のオブジェクトが渡されます。

このオブジェクトのbestTranscription.formattedStringにアクセスし、文字列を受け取ります。
また、isFinalには終了かどうかの状態が入っているので、場合に応じて処理します。

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を取り出します。

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

デリゲートにはこの他、終了時に呼ばれる、speechRecognitionTask(_:didFinishSuccessfully:)メソッド
処理のキャンセル時に呼ばれる、speechRecognitionTaskWasCancelled(_:)メソッド
など合計7つのメソッドがあります。

Github URL

https://github.com/imagepit/ios-speech-to-text-sample