Unityで音声認識のテストプログラム作成したけど、UIのテキストが更新されなくて苦労した

マイクからの入力をテキストに変換して画面に表示する簡単なテストプログラムを作成しました。 [スタート]ボタンを押すと開始され、[停止]ボタンで停止します。 エンジンはAzure Speech Servicesを使っています。

実行画面

苦労したところ

音声認識ができても、画面上のテキストが更新されず原因不明で1週間ほど悩みました。 原因はUIの制御はメインスレッドから行われていて、別のスレッドからでは更新できないことにありました。 当初音声認識は別スレッドで処理していて、UIの更新もその中でやろうとしていました。 UI更新部分もメインスレッドで実行するように下記のようにコードを変更したところうまくいきました。

//メインスレッドをcontext変数に保存
//  Start()メソッドはメインスレッドで実行されるので、その時のスレッドをストアする。
context = SynchronizationContext.Current; 
            context.Post(_ =>
            {
//メインスレッドで実行したい処理( recognizer.Recognizedイベントハンドラの中身)を記入

            }, null);

当初何が原因がわからず、あいまいだった部分(ラムダ式、タスク、async、await、イベントなど)を本やudemy、youtubeなどで 勉強。そうすると、どうも非同期でStartContinuousRecognitionAsync()を呼び出して、その中からUIを更新しようとしているところに問題がありそうだなと思ったので その観点で調べたところ下記のブログを見つけました。まさにそのものずばりで解決しました。ありがとうございます。

goodbyegirl1974.hatenablog.com

daiki-iijima.github.io

全コード

using UnityEngine;
using UnityEngine.UI;
using Microsoft.CognitiveServices.Speech;
using Microsoft.CognitiveServices.Speech.Audio;
using System.Threading;
using System.Threading.Tasks;

public class SpeechRecognition3 : MonoBehaviour
{
    SynchronizationContext context; //スレッドを保存する変数

    public Text speechText; // 認識したテキストを表示するためのTextコンポーネント
    public Text btnText; // ボタン上のテキスト

    private SpeechRecognizer recognizer; // SpeechRecognizerオブジェクト

    private string status = "stopped"; //動作状態

    // 音声認識を行うための非同期メソッド
    private async Task RecognizeSpeechAsync()
    {
        Debug.Log("音声認識を開始します");
        await recognizer.StartContinuousRecognitionAsync().ConfigureAwait(true);
        Debug.Log("音声認識を開始しました");
    }
    private void Start()
    {
        context = SynchronizationContext.Current; //  StartはUIスレッドで実行されるので、その時のスレッドをストアする。

        // SpeechConfigオブジェクトを作成して、サブスクリプションキーとリージョンを指定します
        var config = SpeechConfig.FromSubscription("キー", "japaneast");

        config.SpeechRecognitionLanguage = "ja-JP"; // 音声認識の言語を日本語に設定します

        var audioConfig = AudioConfig.FromDefaultMicrophoneInput(); // マイクから音声を受信するためのAudioConfigオブジェクトを作成します

        recognizer = new SpeechRecognizer(config, audioConfig); // SpeechRecognizerオブジェクトを作成します

        //音声認識処理中のイベントハンドラ
        recognizer.Recognizing += (s, e) =>
        {
            Debug.Log("認識中です");
        };

        // 音声認識が成功したときのイベントハンドラ
        recognizer.Recognized += (s, e) =>
        {
            Debug.Log("認識できました");
            context.Post(_ =>
            {
                //  メインスレッドで実行したいコード
                if (e.Result.Reason == ResultReason.RecognizedSpeech)
                {
                    speechText.text += e.Result.Text;
                }
                else if (e.Result.Reason == ResultReason.NoMatch)
                {
                    speechText.text = "認識できません";
                }

            }, null);
        };
    }

    // OnDestroyメソッドはGameObjectが破棄されるときに呼び出されます
    private void OnDestroy()
    {
        // SpeechRecognizerオブジェクトを停止し、解放します
        if (recognizer != null)
        {
            recognizer.StopContinuousRecognitionAsync().Wait();
            recognizer.Dispose();
        }
    }

    public void ButtonClick()
    {
        if(status == "stopped")
        {
            Debug.Log("音声認識を開始します");
            speechText.text = "";
            Task ts = RecognizeSpeechAsync();
            status = "running";
            btnText.text = "停止";
        }else
        {
            speechText.text = "認識を開始します。スタートを押してください。";
            Debug.Log("音声認識を停止します");
            recognizer.StopContinuousRecognitionAsync().Wait();
            status = "stopped";
            btnText.text = "スタート";
        }
        
    }
}

コードの説明

Start()メソッドの中で SpeechRecognizerオブジェクトrecognizerを作成して、イベントハンドラを登録しています

// 音声認識が成功したときのイベントハンドラ
        recognizer.Recognized += (s, e) =>
        {
            Debug.Log("認識できました");
            context.Post(_ =>
            {
                //  メインスレッドで実行したいコード
                if (e.Result.Reason == ResultReason.RecognizedSpeech)
                {
                    speechText.text += e.Result.Text;
                }
                else if (e.Result.Reason == ResultReason.NoMatch)
                {
                    speechText.text = "認識できません";
                }

            }, null);
        };

スタートボタンを押すとRecognizeSpeechAsync()メソッドが呼び出されます

    private async Task RecognizeSpeechAsync()
    {
        Debug.Log("音声認識を開始します");
        await recognizer.StartContinuousRecognitionAsync().ConfigureAwait(false);
        Debug.Log("音声認識を開始しました");
    }

停止ボタンを押すとStopContinuousRecognitionAsync()メソッドが呼び出されます