目次
概要
「システム開発をしていると、端末を操作して便利な機能を使える」ということがよくある。
しかし、会社ではない、誰か個人が「こういうものがあったらなあ」と思い浮かべるものは、
大抵の場合、音声認識や画像認識をする機能があることが多い。
その求められるであろう機能のうち、音声入力を行う処理を実装する。
ソースコード
まずはInfo.plistに以下の二項目を追加する。
- Privacy – Speech Recognition Usage Description
- Privacy – Microphone Usage Description
ソースコードは以下の通りになる。
import SwiftUI
@main
struct TestApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
LiveAudioView()
}
}
}
View表示部分
import SwiftUI
import AVFoundation
struct LiveAudioView: View {
@ObservedObject var viewModel: LiveAudioViewModel = LiveAudioViewModel()
var body: some View {
VStack {
Spacer()
ScrollView {
if self.viewModel.voiceText == "" {
Text("ここに入力した文字列が表示されます")
} else {
Text(self.viewModel.voiceText)
}
}
Spacer()
Button {
self.viewModel.toggleRecording()
} label: {
Image(systemName: self.viewModel.audioRunning ? "stop.circle" : "record.circle")
.font(.system(size: 120))
.foregroundColor(self.viewModel.audioRunning ? Color.black : Color.red)
}
}
}
}
処理部分
import Foundation
import Speech
class LiveAudioViewModel: NSObject, ObservableObject {
// 音声入力した文字列
@Published var voiceText: String = ""
// 音声入力中かどうか
@Published var audioRunning: Bool = false
private var audioEngine = AVAudioEngine()
private var speechRecognizer: SFSpeechRecognizer?
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
private var recognitionTask: SFSpeechRecognitionTask?
func toggleRecording() {
if self.audioEngine.isRunning {
self.stopRecording()
}
else{
self.startRecording()
}
}
// 音声認識中止処理
private func stopRecording() {
self.recognitionTask?.cancel()
self.recognitionTask?.finish()
self.recognitionTask = nil
self.recognitionRequest?.endAudio()
self.recognitionRequest = nil
self.audioEngine.stop()
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSession.Category.playback)
try audioSession.setMode(AVAudioSession.Mode.default)
} catch {
print("AVAudioSession error")
}
self.audioRunning = false
}
// 音声認識開始処理
private func startRecording() {
audioSessionRecordMode()
recognizeVoice()
startAudioEngine()
}
// audioSessionの録音モード
private func audioSessionRecordMode() {
// AVAudioSessionのインスタンス化
let audioSession = AVAudioSession.sharedInstance()
do {
// モードとして、音声入力モード
try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
// AVAudioSessionをアクティブにする(モードは他の非アクティブなアプリを知らせる)
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
print("AVAudioSession error")
}
}
// audioEngineの開始メソッド
private func startAudioEngine() {
self.audioEngine.prepare()
do {
try self.audioEngine.start()
} catch {
print("AudioEngine error")
}
self.audioRunning = true
}
// 音声認識の処理の準備と認識開始処理
private func recognizeVoice() {
self.speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))
guard let speechRecognizer = self.speechRecognizer else {
print("speechRecognizer nil")
return
}
self.recognitionTask = SFSpeechRecognitionTask()
self.recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
guard let recognitionRequest = self.recognitionRequest else {
self.stopRecording()
return
}
recognitionRequest.shouldReportPartialResults = true
if #available(iOS 13, *) {
recognitionRequest.requiresOnDeviceRecognition = false
}
// 入力用のノードを取得
let inputNode = audioEngine.inputNode
inputNode.removeTap(onBus: 0)
// 0番のaudio node busを表すインスタンス
let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0,
bufferSize: 1024,
format: recordingFormat) { (buffer: AVAudioPCMBuffer, _: AVAudioTime) in
recognitionRequest.append(buffer)
}
self.voiceText = ""
self.recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
if(error != nil){
print (String(describing: error))
self.stopRecording()
return
}
var isFinal = false
if let result = result {
isFinal = result.isFinal
self.voiceText = result.bestTranscription.formattedString
print(result.bestTranscription.formattedString)
}
if isFinal {
print("recording time limit")
self.stopRecording()
inputNode.removeTap(onBus: 0)
}
}
}
}
デモ動画
詳細
結論から言うと、以下の手順で実装するわけですな。
- AudioSessionを使用して、OSの音響システムにアクセスする準備をする
- AVAudioEngineを使用して、データをやり取りするための経路を確保する
- SFSpeechRecognizerを使用して、音声認証をするための初期設定を行う
- SFSpeechAudioBufferRecognitionRequestを使用して、音声認証をデータを保存するためのバッファを確保する
- SFSpeechRecognizerの
recognitionTask(with:resultHandler:)
メソッドを使用して音声認証処理を行う
AudioSession
詳細はApple公式ページ(AVAudioSession)を参照。また、本サイトのAudioSessionって?も参照。
AVAudioSessionは、OSとアプリの仲介の役割をしてくれる。
そのため、専門的な知識などなくても、簡単に音響関係の機能が使えるらしい。(余談だが、tvOSではサポート外。)
使い方としては、シングルトンクラスなので、AVAudioSession.sharedInstance()で初期化し、setCategoryメソッドでカテゴリの設定をする。
そして、その処理をアプリ起動時に行うものらしい。
今回の場合、入力するからカテゴリはrecordとなるわけですな。
そして、setActiveメソッドでtrueを設定することで、アクティブ状態にすると。
AVAudioEngine
詳細はApple公式ページ(AVAudioEngine)を参照。また、本サイトのAudioSessionって?も参照。
AVAudioEngineはAVAudioNode
をもっている。このオブジェクトは音響関連の機能がある。
今回の場合、まずAVAudioEngineの入力のノードを取得(audioEngine.inputNode)して、
出力のバス(システムのモジュール間で、データや制御情報などをやり取りするための専用の通信路)からタップ(ローカルネットワーク上のイベントを監視するもの)を取り除く。
SFSpeechRecognitionTask
詳細はApple公式ページ(SFSpeechRecognitionTask)を参照。
speech recognition taskの状態を決めたり、進行中のタスクをキャンセルしたり、終了を検知したりする。
cancelメソッドを使用すると、現在のspeech recognition taskをキャンセルし、
finishメソッドを使用すると、新しくaudioを追加しなくなる。
SFSpeechRecognizer
詳細はApple公式ページ(SFSpeechRecognizer)を参照。
speech recognizer processを使うためのオブジェクト。
主に以下の処理を行う。
- speech recognition servicesを使うための認証を行う
- recognition process(認証処理)の言語設定を行う
- recognition tasks(認証タスク)の初期化
具体的に使う時は以下の手順を踏む
- 音声認識の認証を行う
- SFSpeechRecognizerのインスタンスを生成する
- speech recognizer objectのisAvailableプロパティを使用しているサービスの可用性の確認
- 音響のコンテンツを準備
- recognition request objectを生成する。
-
recognitionTask(with:delegate:)
もしくはrecognitionTask(with:resultHandler:)
メソッドを読んで認証処理を開始する
そして、認証の時間はおおよそ一分間らしい。
recognitionTask(with:resultHandler:)メソッドは認証処理を行う。その中のレスポンス?はSFSpeechRecognitionResult型。認証した文字列は「bestTranscription.formattedString」に格納されている。
SFSpeechAudioBufferRecognitionRequest
詳細はApple公式ページ(SFSpeechAudioBufferRecognitionRequest)を参照。
リアルタイムで音声認識を行うためのオブジェクト。もしくはaudioバッファをセットする。
マイクから音声を入力する際に使う。