2022-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を呼んでやれば、自動で再度録音を開始させることができますが、翻訳バッファーテキストはクリアされるので、どこかに蓄積しておくなど工夫が必要です。



※本記事内容の無断転載を禁じます。

高濃度水素水ウォーターサーバー【PURE WATER+H2】

デザイナーサングラス、メガネ、コンタクト、カラコン海外販売サイト!【SmartBuyGlasses】

<夕食.net> 日替わりメニュー初回50%OFF ※一部エリアのサービスとなります

東京ガスのさすてな電気

レンタル無料の天然水宅配サービス★【ワンウェイウォーター】

短期間でお金を貯めるなら、リゾートバイト!お仕事検索・紹介なら【ダイブ】

【【フレッツ光法人向けサイト(NTT東日本のフレッツVPNを利用することにより公衆回線の中に特定のユーザーのみ通信できる仮想的なネットワークをつくりセキュアな通信を行うことが可能になります。)】】

補聴器とは違う、全く新しい会話サポートイヤホン「Olive Smart Ear Plus(オリーブスマートイヤープラス)」

多種多様なパソコン・周辺機器を絶賛買取しています!【パソコン買取アローズ】

CORESERVER v1 CORE-MINIプランでownCloudをインストールする
size_tとssize_tを使い分けてSegmentation Faultを予防する
Ubuntuでcore dumpedとなってるのにcoreが出力されない場合
【C++】uint8_tバイトvector配列をstring文字列に変換する
Ubuntu Server 21.10でイーサリアムブロックチェーン【その8】
Ubuntu Server 21.10でイーサリアムブロックチェーン【その7】
Ubuntu Server 21.10でイーサリアムブロックチェーン【その6】
githubにpushしようとしたらerror: failed to push some refs toと言われた場合
【Linux】PHP8をソースからインストールする
Windows版Google Driveが使用中と言われアンインストールできない場合
【Windows10】リモートデスクトップ間のコピー&ペーストができなくなった場合の対処法
Googleスプレッドシートを編集したら自動で更新日時を入れる
【Javascript】JSON配列内にある特定要素の取得法【Node.js】
5chブラウザJane Styleの板一覧にゲーム板等が表示されない場合
Googleファミリーリンクで子供の端末の現在地がエラーで取得できない場合
【ウマ娘】「全身全霊」スキルを取得するには
【Anaconda3】指定した仮想環境でJupyter Notebookを動かす
【Windows PC】iPadが突然6桁のパスコードを要求してきて詰んだ場合の初期化法