2024-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を呼んでやれば、自動で再度録音を開始させることができますが、翻訳バッファーテキストはクリアされるので、どこかに蓄積しておくなど工夫が必要です。
※本記事内容の無断転載を禁じます。
ご連絡は以下アドレスまでお願いします★
Wav2Lipのオープンソース版を改造して外部から呼べるAPI化する
Wav2Lipのオープンソース版で静止画の口元のみを動かして喋らせる
【iOS】アプリアイコン・ロゴ画像の作成・設定方法
オープンソースリップシンクエンジンSadTalkerをAPI化してアプリから呼ぶ【2】
オープンソースリップシンクエンジンSadTalkerをAPI化してアプリから呼ぶ【1】
【Xcode】iPhone is not available because it is unpairedの対処法
【Let's Encrypt】Failed authorization procedure 503の対処法
【Debian】古いバージョンでapt updateしたら404 not foundでエラーになる場合
ファイアウォール内部のWindows11 PCにmacOS Sequoiaからリモートデスクトップする
進研ゼミチャレンジタッチをAndroid端末化する
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
【Apache】サーバーに同時接続可能なクライアント数を調整する
Windows11のコマンドプロンプトでテキストをコピーする
Androidホームで左にスワイプすると出てくるニュース共を一切表示させない方法
【Linux】mkfsコマンドでProceed anyway?と確認を求められるのを回避する
VirtualBoxの仮想マシンをWindows起動時に自動起動し終了時に自動サスペンドする
タスクスケジューラで変更を適用できません。ユーザーアカウントが不明であるか、パスワードが正しくないか、またはユーザーアカウントにタスクを変更する許可がありません。と出た
Googleスプレッドシートを編集したら自動で更新日時を入れる