はじめに
VRヘビーユーザーからすれば当たり前のことかもしれませんが、
OculusQuestにマイクが搭載されているのはご存じでしたでしょうか。
ボイスチャットに利用されることがほとんどで、
その他の用途で使われている事例をあまり見たことがありませんでした。
(たぶん世の中にはたくさんある)
しかし、最近目にした記事にボイスチャット以外の用途でマイクを使った事例がありました。
【参考リンク】:Synamon、ロゼッタと「リアルタイム多言語翻訳システム装備のVRオフィス」を共同開発
一言で説明すると、翻訳VRアプリです。
マイクを音声認識の受け口として利用しています。
そこで、私も勉強がてら"OculusQuestのマイクを利用したVRアプリ作りに挑戦してみたい"と思い、実際に作りました。
勉強がてら作成していた翻訳VRできました!😎
— KENTO⚽️XRエンジニア😎Zenn100記事マラソン挑戦中29/100 (@okprogramming) April 3, 2021
OculusQuestのマイクで拾った音声を音声認識APIに渡して、認識結果を翻訳APIに渡す、、、というやり方です💪
次はマルチ対応していきます😆#OculusQuest#Unitypic.twitter.com/k95D73gEnh
Androidネイティブの音声認識機能
Androidには音声認識の機能が搭載されており、開発者がアプリに組み込めるようにその機能が公開されています。
【参考リンク】:RecognitionListener
そして、そのAndroidのネイティブの機能をUnityからネイティブプラグインとして呼び出すことができます。
OculusQuestはAndroidベースであり、Androidアプリとしてビルドすることから、
"もしかしたら、Androidのネイティブプラグインを導入すれば動くんじゃないか?"という仮説に辿り着きました。
その検証を行った結果を記していきます。
ネイティブプラグイン作成
まずは下記リンクを参考にネイティブプラグインを作成します。
【参考リンク】:Unity向けAndroidネイティブプラグインの作り方
【参考リンク】: AndroidのSpeechRecognizerをネイティブプラグイン化してUnityで使う
記事の通りでほとんど詰まることなくいけました。
なんか動かん、という場合には地味にやることが多いので原因の特定はそこそこの苦行となります。
ですので面倒かもしれませんが最初からやり直した方が早い場合もあるかもしれません。
Javaで書いたプラグイン側のコードとC#で書いたUnity側のコードだけメモ残します。
プラグイン側のコード
packagecom.kento.speechtest;importjava.util.ArrayList;importjava.util.Locale;importandroid.content.Context;importandroid.os.Bundle;importandroid.speech.RecognitionListener;importandroid.speech.RecognizerIntent;importandroid.speech.SpeechRecognizer;importandroid.content.Intent;importstaticcom.unity3d.player.UnityPlayer.UnitySendMessage;publicclassSpeech{staticpublicvoidStartRecognizer(Contextcontext,finalStringcallbackTarget,finalStringcallbackMethod){Intentintent=newIntent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,Locale.JAPAN.toString());intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,context.getPackageName());SpeechRecognizerrecognizer=SpeechRecognizer.createSpeechRecognizer(context);recognizer.setRecognitionListener(newRecognitionListener(){@OverridepublicvoidonReadyForSpeech(Bundleparams){// On Ready for speech.UnitySendMessage(callbackTarget,callbackMethod,"onReadyForSpeech");}@OverridepublicvoidonBeginningOfSpeech(){// On begining of speech.UnitySendMessage(callbackTarget,callbackMethod,"onBeginningOfSpeech");}@OverridepublicvoidonRmsChanged(floatrmsdB){// On Rms changed.UnitySendMessage(callbackTarget,callbackMethod,"onRmsChanged");}@OverridepublicvoidonBufferReceived(byte[]buffer){// On buffer received.UnitySendMessage(callbackTarget,callbackMethod,"onBufferReceived");}@OverridepublicvoidonEndOfSpeech(){// On end of speech.UnitySendMessage(callbackTarget,callbackMethod,"onEndOfSpeech");}@OverridepublicvoidonError(interror){// On error.UnitySendMessage(callbackTarget,callbackMethod,"onError");}@OverridepublicvoidonResults(Bundleresults){ArrayList<String>list=results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);Stringstr="";for(Strings:list){if(str.length()>0){str+="\n";}str+=s;}UnitySendMessage(callbackTarget,callbackMethod,"onResults\n"+str);}@OverridepublicvoidonPartialResults(BundlepartialResults){// On partial results.UnitySendMessage(callbackTarget,callbackMethod,"onPartialResults");}@OverridepublicvoidonEvent(inteventType,Bundleparams){// On event.UnitySendMessage(callbackTarget,callbackMethod,"onEvent");}});recognizer.startListening(intent);}}Unity側のコード
usingSystem;usingUnityEngine;usingUnityEngine.Android;usingUnityEngine.UI;// <summary>/// Androidのネイティブ音声認識機能呼び出し/// </summary>publicclassAndroidNativeSpeech:MonoBehaviour{[SerializeField]privateTextrecText;[SerializeField]privateImagemicroPhoneImage;privatevoidStart(){#if UNITY_ANDROID
if(!Permission.HasUserAuthorizedPermission(Permission.Microphone)){Debug.Log("Request");Permission.RequestUserPermission(Permission.Microphone);}#endif
}privatevoidUpdate(){if(Input.touchCount>0){Touchtouch=Input.touches[0];if(touch.phase==TouchPhase.Began){StartSpeech();}}}/// <summary>/// 認識開始/// </summary>privatevoidStartSpeech(){#if UNITY_ANDROID
varnativeRecognizer=newAndroidJavaClass("com.kento.speechtest.Speech");varunityPlayer=newAndroidJavaClass("com.unity3d.player.UnityPlayer");varcontext=unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");context.Call("runOnUiThread",newAndroidJavaRunnable(()=>{nativeRecognizer.CallStatic("StartRecognizer",context,gameObject.name,"CallbackMethod");}));#endif
}/// <summary>/// 音声認識後のコールバックとして実行するメソッド/// </summary>/// <param name="message">認識したメッセージ</param>privatevoidCallbackMethod(stringmessage){varmessages=message.Split('\n');//ユーザーが話すのを開始した際のコールバックif(messages[0]=="onBeginningOfSpeech"){microPhoneImage.enabled=true;}//認識した音量変化のコールバックif(messages[0]=="onRmsChanged"){recText.text="認識中...";}//ユーザーが話すのを終了した際のコールバックif(messages[0]=="onEndOfSpeech"){microPhoneImage.enabled=false;}//認識結果の準備が完了したコールバックif(messages[0]=="onResults"){varmsg="";for(vari=1;i<messages.Length;i++){msg+=messages[i]+"\n";}Debug.Log(msg);recText.text=msg;}}}デモ
VRで検証
結果から言うと動きませんでした。adb logcat -s Unity:*とコマンドプロンプトに入力することで
動作中のUnity製Androidアプリのログを出力できます。(教えてくださった方ありがとうございます!)
その方法で実行中のVRアプリのログを確認しましたが、特にエラーを吐くことも無くただただ動いていませんでした。
同じ検証で四苦八苦している先駆者がOculusの公式コミュティに質問を記していました。
(そして、それらしい回答もなく終了していました)
【引用元】:On-device Speech Recognition on the Quest with Unity
冒頭の翻訳VRは下記リンクの手法で実現しました。
【参考リンク】:【Unity(C#)】Watson API × OculusQuestで音声認識
【参考リンク】:【Unity(C#)】Microsoft Translatorの使い方
ちなみに、海外ユーザーは音声入力や音声コマンドが利用可能なようです。
音声コマンドは「ヘイ、フェイスブック!」ってやつです。
【参考リンク】:音声コマンド・音声入力
そのうち、日本語にも対応して開発者に公開されるのを期待します。
おわりに
今回の検証結果はOculusQuestでAndroidネイティブの音声認識機能は呼び出せないとなりました。
もし成功した方がいらっしゃったら遠慮なくツッコんでください。(あわよくば方法知りたい)

