見出し画像

【IT技術】【Swift】Speechフレームワークを使用した音声認識

こんにちは。初めまして。
ラフアンドレディ(株) iOS大好き春蔵です。

Swiftでは、画像認識、音声認識、テキスト認識など、AI技術を利用したフレームワークが追加されてきています。
その内の音声認識、Speechフレームワークを使用した音声認識について、サンプルをもとに紹介します。

サンプルについては最後にリンクを記載しています。
興味ある方はぜひ見ていって下さい!

権限の付与

info.plistに以下のキーを追加する事により、マイク、音声認識にアクセスする際の使用許諾の文章を設定します。(多言語に対応している場合、infoPlist.stringsに設定)
・Privacy - Speech Recognition Usage Description
    (NSSpeechRecognitionUsageDescription)
・Privacy - Microphone Usage Description
   (NSMicrophoneUsageDescription)

    /// マイク・音声認識使用許可確認
    /// - Returns: True:許諾済 False:許諾拒否
    func authorization()->Bool{
        if(AVCaptureDevice.authorizationStatus(for: AVMediaType.audio) == .authorized &&
            SFSpeechRecognizer.authorizationStatus() == .authorized){
            return true
        } else {
            return false
        }
    }    

    /// マイク・音声認識使用許可要求
    /// - Parameter completion: コールバック関数
    func requestAccess(completion:@escaping ()->Void){
        /// マイク使用許可
        AVCaptureDevice.requestAccess(for: AVMediaType.audio) { granted in
            if granted {
                /// 音声認識使用許可
                SFSpeechRecognizer.requestAuthorization { status in
                    completion()
                }
            } else {
                completion()
            }
        }
    }
struct ContentView: View {
    @StateObject var viewModel = ContentViewViewModel()
    
    var body: some View {
        VStack(spacing:20) {
・・・
        }
        .padding()
        .onAppear {
            viewModel.onAppear()
        }
    }
}

class ContentViewViewModel : ObservableObject{
    /// 音声認識クラス
    lazy var speechRecorder = SpeechRecognitionEngine(silenceTime:3
                                                      ,onFinishTask:onFinishTask
                                                      ,onRecognition:onRecognition)

   /// 初期表示時
    func onAppear(){
        // 許可チェック
        if !speechRecorder.authorization(){
            // マイク認識要求
            self.speechRecorder.requestAccess(){
                // 許可チェック
                if self.speechRecorder.authorization(){
                    // 正常時処理
                    print("Authorization OK")
                } else {
                    // 異常時処理
                    print("Authorization NG")
                    // 異常時処理
                    self.result = "音声認識又は、マイクのアクセス権限がありません。設定画面よりアクセス権限を設定しアプリを再起動してください。"
                }
            }
        }
    }

・・・

サンプルでは、画面表示時にspeechRecorder.authorizationメソッドで現在のアクセス権限の確認を行い、speechRecorder.requestAccess()メソッドでアクセス権限の要求を行います。

ユーザーによりマイク、音声認識へのアクセスが拒否された場合、OSの設定画面よりアクセス権限の付与を行う必要があります。

音声認識を開始

    /// 音声認識開始
    func startRecording() throws {
        print("startRecording")

        // 開始済みなら処理終了
        if status == .start { return }
        
        // ロック
        self.lock.lock()
        defer { self.lock.unlock() }  // unlock を保証
        
        // 録音開始
        status = .start

        // AVAudioSessionの初期化
        // AVAudioSessionインタンスの取得
        let audioSession = AVAudioSession.sharedInstance()
        // カテゴリーの指定(アプリの音の扱い方法を指定)
        try audioSession.setCategory(.playAndRecord, mode: .measurement, options: .duckOthers)
        // AVAutdioのアクティブ化
        try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
        // 入力モードの指定
        let inputNode = audioEngine.inputNode
        inputNode.removeTap(onBus: 0)
        
        // 音声認識の初期化
        // SFSpeechRecognizerのインスタンス化
        self.speechRecognizer = SFSpeechRecognizer(locale: Locale.current)!
        // オンデバイス判定
        if speechRecognizer.supportsOnDeviceRecognition {
            recognitionRequest?.requiresOnDeviceRecognition = self.requiresOnDeviceRecognition
        }
        // タスクを設定 値を受け取った時のタスク
        self.recognitionTask = SFSpeechRecognitionTask()

        // リクエストの作成 マイク等のオーディオバッファを利用 ライブストリーム用
        self.recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        if(self.recognitionTask == nil || self.recognitionRequest == nil){
            self.stopRecording()
            return
        }
        // 単語
        if let contextualStrings = contextualStrings {
            self.recognitionRequest?.contextualStrings = contextualStrings
        }
        
        // タイマー設定
        timer = Timer.scheduledTimer(timeInterval: silenceTime, target: self, selector: #selector(self.didFinishTalk), userInfo: nil, repeats: false)
        
        // true:途中報告 false:結果報告
        recognitionRequest?.shouldReportPartialResults = shouldReportPartialResults
        
        // 音声認識
        recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest!) { [self] result, error in
            if status != .start { return }
            if(error != nil){
                var errorCode = 0
                
                // エラーコード取得
                if let e = error as NSError? {
                    errorCode = e.code
                }
                // エラー発生時
                print ("SpeechRecognizer error:" + String(describing: error))

                // エラーコード301は無視
                if errorCode == 301 {
                    print("recognition cancel status:\(status)")
                    // タイマーキル
                    makeFinishTimer(0)
                    return
                } else if errorCode != 209 && errorCode != 1110 {
                    // エラーコード209,1110以外はエラー
                    self.stopRecording()
                    onError(error)
                    return
                }
                                
                return
            }
            
            /// タイマー再設定
            makeFinishTimer(self.silenceTime)

            var isFinal = false
            if let result = result {
                // 60秒の結果に達したらisFinalがtrue
                isFinal = result.isFinal
                // 音声認識結果
                print("result:" + result.bestTranscription.formattedString)
                
                let substrings = result.bestTranscription.segments.map({$0.substring})
        
                self.onRecognition(substrings)
            }
            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()
        
        print("Start Recording End")
    }
     // AVAudioSessionインタンスの取得
        let audioSession = AVAudioSession.sharedInstance()
        // カテゴリーの指定(アプリの音の扱い方法を指定)
        try audioSession.setCategory(.playAndRecord, mode: .measurement, options: .duckOthers)
        // AVAutdioのアクティブ化
        try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
        // 入力モードの指定
        let inputNode = audioEngine.inputNode
        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()

マイクやスピーカー等、音の入出力を管理しているAVAudioEngineと音声認識クラスの関連付けを行いマイクからの入力を監視します。

       // 音声認識の初期化
        // SFSpeechRecognizerのインスタンス化
        self.speechRecognizer = SFSpeechRecognizer(locale: Locale.current)!
        // オンデバイス判定
        if speechRecognizer.supportsOnDeviceRecognition {
            recognitionRequest?.requiresOnDeviceRecognition = self.requiresOnDeviceRecognition
        }

音声認識クラスをインスタンス化します。その際、音声をどの言語で認識するかセットアップします。
実行環境がオフラインでの音声認識をサポートしている場合(speechRecognizer.supportsOnDeviceRecognition)、オフラインでの音声認識を行います(recognitionRequest.requiresOnDeviceRecognition = true)。日本語環境はiOS15.0よりオフラインで動作します。

        // 単語
        if let contextualStrings = contextualStrings {
            self.recognitionRequest?.contextualStrings = contextualStrings
        }

辞書にない単語を音声認識させるため、単語を追加で設定します。

     // true:途中報告 false:結果報告
        recognitionRequest?.shouldReportPartialResults = shouldReportPartialResults

認識毎に中間結果を返却するか否かのフラグです。falseの場合、最終結果のみ返却されます。

        // 音声認識
        recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest!) { [self] result, error in
・・・
            var isFinal = false
            if let result = result {
                // 結果に達したらisFinalがtrue
                isFinal = result.isFinal
                // 音声認識結果
                print("result:" + result.bestTranscription.formattedString)
                
                let substrings = result.bestTranscription.segments.map({$0.substring})
        
                self.onRecognition(substrings)
            }
            if isFinal { //録音タイムリミット
                print("recording time limit")
                // 録音停止
                self.stopRecording()
                inputNode.removeTap(onBus: 0)
            }

音声が認識された場合、又はタイムリミットに達した場合、resultが設定され、エラーが発生した場合、errorが設定されコールバックされます。
正常に単語が認識された場合、result.bestTranscriptionに最も精度の高い単語が設定されます。

音声認識の停止

   /// 音声認識停止
    func stopRecording(){
        print("Stop Recording")
        
        // 既に停止済の場合、停止処理を行わない
        if status == .stop { return }
        // 録音停止
        status = .stop

        // タイマー停止
        timer?.invalidate()
        timer = nil
        
        // 音声認識の終了処理
        self.recognitionTask?.cancel()
        self.recognitionTask?.finish()
        self.recognitionTask = nil

        // AVAudioSessionの停止初期化
        self.audioEngine.inputNode.removeTap(onBus: 0)
        self.audioEngine.reset()
        self.audioEngine.stop()

        // 終了
        self.recognitionRequest?.endAudio()
        self.recognitionRequest = nil

        let audioSession = AVAudioSession.sharedInstance()
        do {
            // playAndRecordの場合、音量が小さいため、元に戻す
            try audioSession.setCategory(AVAudioSession.Category.playback)
            try audioSession.setMode(AVAudioSession.Mode.default)
        } catch{
            print("AVAudioSession error")
        }
        
        print("Stop Recording End")
    }

AVAudioEngineの停止、音声認識クラスの関連付け解除、停止を行います。


お読み頂き有り難うございました!

以上、Speechフレームワークを使用した音声認識の基本的な使用方法でした。
以下にコードサンプル、アプリケーションも置いておきます。

コードサンプル

今回紹介した音声認識のサンプルコードです

アプリケーション

今回紹介した音声認識の技術を利用したiOSアプリです。

それではまた!

───-- - - - 

フォロー Me!
↓ ↓
Twitter : @RandR_inc

◆───-- - - - 

ラフアンドレディでの採用はこちら ↓ ↓ ↓

ラフアンドレディでは、みんなのびのびと仕事をしています!エンジニアが長く幸せに活躍できる環境で、仲間と楽しく働いてみませんか?


この記事が参加している募集

つくってみた

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