2023-07-13
iPhone端末のマイクでしゃべった内容をテキストに変換して出力するサンプルです。
「スピーチ開始」で、端末に向かって喋りかければ、変換内容を随時テキスト出力します。
ネイティブのSFSpeechRecognizerクラスを使うことになりますが、以下の特徴があります。
・無料
・内部ではマイクからの音声データをネットワーク通信でAppleサーバに送信し翻訳を受け取るので動作は重い
・ネットワーク通信をしない=オフライン辞書もあるがこれは英語専用
・1回のスピーチ入力制限時間は1分
今回日本語をターゲットにするのでオフライン辞書は利用しません。
プロジェクト一式はこちら。
https://github.com/servernote/iPhoneSample/tree/master/VoiceToText
以下ソースです。
まず、Info.plistにてマイクの使用と音声認識の使用を宣言します。
Info.plist をクリック
Information Property List をクリックして+
Privacy - Microphone Usage Description
Privacy - Speech Recognition Usage Description
を追加、説明Stringはそれぞれ
このアプリはマイクを使用します
このアプリは音声を認識します
などとする。
最小限のサンプルなので作成クラスは表示クラスContentView.swiftと音声認識クラスSpeechRecorder.swiftの2つです。
Swift | ContentView.swift | GitHub Source |
// // ContentView.swift // VoiceToText // // Created by webmaster on 2020/06/14. // Copyright © 2020 SERVERNOTE.NET. All rights reserved. // import SwiftUI import Speech import AVFoundation struct ContentView: View { @ObservedObject private var speechRecorder = SpeechRecorder() @State var showingAlert = false var body: some View { ScrollView{ VStack(alignment: .leading, spacing: 5) { HStack() { Spacer() Button(action: { if(AVCaptureDevice.authorizationStatus(for: AVMediaType.audio) == .authorized && SFSpeechRecognizer.authorizationStatus() == .authorized){ self.showingAlert = false self.speechRecorder.toggleRecording() if !self.speechRecorder.audioRunning { DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { } } } else{ self.showingAlert = true } }) { if !self.speechRecorder.audioRunning { Text("スピーチ開始") .padding() .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color.blue, lineWidth: 1)) } else { Text("スピーチ終了") .padding() .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color.red, lineWidth: 1)) } } .alert(isPresented: $showingAlert) { Alert(title: Text("マイクの使用または音声の認識が許可されていません")) } Spacer() } Text(self.speechRecorder.audioText) } .onAppear{ AVCaptureDevice.requestAccess(for: AVMediaType.audio) { granted in OperationQueue.main.addOperation { } } SFSpeechRecognizer.requestAuthorization { status in OperationQueue.main.addOperation { //switch status { // case .authorized: // // default: // //} } } } }.padding(.vertical) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
・まずViewのonAppearでマイクと音声認識の許可を求めるダイアログを出しておきます。
AVCaptureDevice.requestAccess(for: AVMediaType.audio) { granted in //マイク
SFSpeechRecognizer.requestAuthorization { status in //音声認識
それぞれ許可されているかどうかは別のシステム関数で取得できるので、ここで結果を保存しておく必要はないです。
・ボタンタップで上記2つの許可がおりているかを確認し、許可されていなかったらアラートを出します。
許可されていれば、スピーチ認識を開始、もしくは終了します。
・SpeechRecorderクラスで音声認識されたテキストを常時表示します。
Swift | SpeechRecorder.swift | GitHub Source |
// // SpeechRecorder.swift // VoiceToText // // Created by webmaster on 2020/06/14. // Copyright © 2020 SERVERNOTE.NET. All rights reserved. // import Foundation import Combine import AVFoundation import Speech final class SpeechRecorder: ObservableObject { @Published var audioText: String = "" @Published var audioRunning: Bool = false private var audioEngine = AVAudioEngine() private var speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))! private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest? private var recognitionTask: SFSpeechRecognitionTask? func toggleRecording(){ if self.audioEngine.isRunning { self.stopRecording() } else{ try! self.startRecording() } } func stopRecording(){ self.recognitionTask?.cancel() self.recognitionTask?.finish() self.recognitionRequest?.endAudio() self.recognitionRequest = nil self.recognitionTask = 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 } func startRecording() throws { self.speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))! let audioSession = AVAudioSession.sharedInstance() try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers) try audioSession.setActive(true, options: .notifyOthersOnDeactivation) let inputNode = audioEngine.inputNode inputNode.removeTap(onBus: 0) self.recognitionTask = SFSpeechRecognitionTask() self.recognitionRequest = SFSpeechAudioBufferRecognitionRequest() if(self.recognitionTask == nil || self.recognitionRequest == nil){ self.stopRecording() return } self.audioText = "" recognitionRequest?.shouldReportPartialResults = true if #available(iOS 13, *) { recognitionRequest?.requiresOnDeviceRecognition = false } 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.audioText = result.bestTranscription.formattedString print(result.bestTranscription.formattedString) } if isFinal { //録音タイムリミット print("recording time limit") self.stopRecording() inputNode.removeTap(onBus: 0) } } let recordingFormat = inputNode.outputFormat(forBus: 0) inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in self.recognitionRequest?.append(buffer) } self.audioEngine.prepare() try self.audioEngine.start() self.audioRunning = true } }
・スピーチの制限時間(1分)にい到達して終わってしまった場合recognitionTask.result.isFinalがtrueになるので、このタイミングでstartRecordingを呼んでやれば、自動で再度録音を開始させることができますが、翻訳バッファーテキストはクリアされるので、どこかに蓄積しておくなど工夫が必要です。
※本記事内容の無断転載を禁じます。
ご連絡は以下アドレスまでお願いします★
Windowsのデスクトップ画面をそのまま配信するための下準備
WindowsでGPUの状態を確認するには(ASUS系監視ソフトの自動起動を停止する)
CORESERVER v1プランからさくらインターネットスタンダートプランへ引っ越しメモ
さくらインターネットでPython MecabをCGIから使う
さくらインターネットのPHPでAnalytics-G4 APIを使う
インクルードパスの調べ方
【Git】特定ファイルを除外する.gitignore
【Ubuntu/Debian】NVIDIA関係のドライバを自動アップデートさせない
【Python】Spacyを使用して文章から出発地と目的地を抜き出す
【Windows10】リモートデスクトップ間のコピー&ペーストができなくなった場合の対処法
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
Windows版Google Driveが使用中と言われアンインストールできない場合
VirtualBoxの仮想マシンをWindows起動時に自動起動し終了時に自動サスペンドする
【Apache】サーバーに同時接続可能なクライアント数を調整する
【C/C++】小数点以下の切り捨て・切り上げ・四捨五入
Googleファミリーリンクで子供の端末の現在地がエラーで取得できない場合
【Linux】iconv/libiconvをソースコードからインストール
Ubuntu Server 21.10でイーサリアムブロックチェーン【その5】