見出し画像

iOS の 音声認識 と 音声合成 を試す

「iOS」の「音声認識」と「音声合成」を試したので、まとめました。

・iOS17


1. iOSの音声認識

(1) 「Info」で以下の2項目を設定。

	<key>Privacy - NSSpeechRecognitionUsageDescription</key>
	<string>このアプリは音声認識機能を使用します。</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>このアプリはマイクを使用します。</string>

(2) コードの記述。

・ContentView.swift

import SwiftUI
import Speech

// SpeachRecognizer
class SpeechRecognizer: ObservableObject {
    @Published var recognizedText = ""
    @Published var isRecording = false

    private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
    private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
    private var recognitionTask: SFSpeechRecognitionTask?
    private let audioEngine = AVAudioEngine()

    // 音声認識の開始
    func startRecording() {
        guard !isRecording else { return }
        recognitionTask?.cancel()
        recognitionTask = nil

        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
            try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
        } catch {
            print(error.localizedDescription)
            return
        }

        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        guard let recognitionRequest = recognitionRequest else {
            fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object")
        }
        recognitionRequest.shouldReportPartialResults = true

        let inputNode = audioEngine.inputNode
        recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { [weak self] result, error in
            guard let self = self else { return }
            if let result = result {
                self.recognizedText = result.bestTranscription.formattedString
                if result.isFinal || error != nil {
                    self.stopRecording()
                }
            }
        }

        inputNode.installTap(onBus: 0, bufferSize: 1024, format: inputNode.outputFormat(forBus: 0)) { buffer, _ in
            self.recognitionRequest?.append(buffer)
        }
        audioEngine.prepare()
        do {
            try audioEngine.start()
            isRecording = true
        } catch {
            print(error.localizedDescription)
        }
    }

    // 音声認識の停止
    func stopRecording() {
        audioEngine.stop()
        audioEngine.inputNode.removeTap(onBus: 0)
        recognitionRequest?.endAudio()
        recognitionRequest = nil
        recognitionTask = nil
        isRecording = false
    }
}

// コンテンツビュー
struct ContentView: View {
    @StateObject private var speechRecognizer = SpeechRecognizer()

    // UI
    var body: some View {
        VStack {
            Text(speechRecognizer.recognizedText)
                .padding()
            Button(action: {
                speechRecognizer.isRecording ? speechRecognizer.stopRecording() : speechRecognizer.startRecording()
            }) {
                Text(speechRecognizer.isRecording ? "音声認識の停止" : "音声認識の開始")
                    .padding()
                    .background(speechRecognizer.isRecording ? Color.red : Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
        .onAppear {
            // 音声認識の許可
            SFSpeechRecognizer.requestAuthorization { authStatus in
                DispatchQueue.main.async {
                    print(authStatus == .authorized ? "音声認識が許可されました" : "音声認識が許可されていません")
                }
            }
        }
    }
}

(3) コードの実行。

2. iOSの音声合成

(1) コードの編集。

・ContentView.swift

import SwiftUI
import AVFoundation

// コンテンツビュー
struct ContentView: View {
    // @StateでspeechSysthesizerを保持
    @State private var speechSynthesizer: AVSpeechSynthesizer!
    
    // UI
    var body: some View {
        Button {
            speak("こんにちは")
        } label: {
            Text("音声合成")
        }
        .buttonStyle(.borderedProminent)
    }
    
    // 音声合成
    func speak(_ text: String) {
        // オーディオセッションをplaybackに切り替え
        let audioSession = AVAudioSession()
        do {
            try audioSession.setCategory(.playback, mode: .default, options: .duckOthers)
            try audioSession.setActive(false)
        } catch let error {
            print(error.localizedDescription)
        }
        
        // 音声合成
        speechSynthesizer = AVSpeechSynthesizer()
        let utterance = AVSpeechUtterance(string: text)
        utterance.voice = AVSpeechSynthesisVoice(language: "ja-JP")
        speechSynthesizer.speak(utterance)
    }
}

iOS17では、以下の2つを対応しないと動きませんでした。

(1) @StateでspeechSysthesizerを保持
(2) オーディオセッションをplaybackに切り替え

詳しくは、以下を参照。

How to fix AV Speech Synthesizer error "Unable to list voice folder" even after moving synthesizer outside class?

(2) コードの実行。



この記事が気に入ったらサポートをしてみませんか?