2023-07-13
【Swift UI】マイク使用許可を得て音声をテキストに変換する(音声認識)のAndroid/Java版です。
Android端末のマイクでしゃべった内容をテキストに変換して出力するサンプルです。
「スピーチ開始」で、端末に向かって喋りかければ、変換内容を随時テキスト出力します。
プロジェクト一式はこちら。
https://github.com/servernote/AndroidSample/tree/master/VoiceToText
録音を許可するパーミッションRECORD_AUDIOをManifestに記載しますがこれだけでは不十分=デンジャラスパーミッションであるため、アプリの中でrequestPermissionを呼び、ユーザーに明示的に許可をもらわなくてはいけません。
XML | AndroidManifest.xml | GitHub Source |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.servernote.voicetotext"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Java | MainActivity.java | GitHub Source |
package net.servernote.voicetotext; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import android.Manifest; import android.app.AlertDialog; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.speech.RecognitionListener; import android.speech.RecognizerIntent; import android.speech.SpeechRecognizer; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity implements View.OnClickListener, RecognitionListener { private static final int PERMISSION_RECORD_AUDIO = 1; private Button mButton; private TextView mText; private SpeechRecognizer mRecorder; private AlertDialog.Builder mAlert; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button)findViewById(R.id.speech_button); mText = (TextView)findViewById(R.id.output_text); mButton.setOnClickListener(this); mRecorder = null; mAlert = new AlertDialog.Builder(this); mAlert.setTitle(getString(R.string.error)); mAlert.setPositiveButton(getString(R.string.ok), null); checkRecordable(); } public Boolean checkRecordable(){ if(!SpeechRecognizer.isRecognitionAvailable(getApplicationContext())) { //mAlert.setMessage(getString(R.string.speech_not_available)); //mAlert.show(); mText.setText(getString(R.string.speech_not_available)); return false; } if (Build.VERSION.SDK_INT >= 23) { if(ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { mText.setText(getString(R.string.speech_not_granted)); ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.RECORD_AUDIO }, PERMISSION_RECORD_AUDIO); return false; } } return true; } @Override public void onRequestPermissionsResult( int requestCode, String[] permission, int[] grantResults ){ Log.d("MainActivity","onRequestPermissionsResult"); if (grantResults.length <= 0) { return; } switch(requestCode){ case PERMISSION_RECORD_AUDIO: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { mText.setText(""); } else { } break; } } public void stopRecording(){ if(mRecorder != null && checkRecordable()) { mRecorder.stopListening(); mRecorder.cancel(); mRecorder.destroy(); mRecorder = null; mButton.setText(getString(R.string.start_speech)); } } public void startRecording(){ if(mRecorder == null && checkRecordable()) { mText.setText(getString(R.string.prepare_speech)); mRecorder = SpeechRecognizer.createSpeechRecognizer(this); mRecorder.setRecognitionListener(this); Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, getPackageName()); //以下指定で途中の認識を拾う intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true); mRecorder.startListening(intent); mButton.setText(getString(R.string.stop_speech)); } } @Override public void onClick(View v) { if(v.getId() != R.id.speech_button){ return; } if(mRecorder != null){ stopRecording(); } else{ startRecording(); } } @Override public void onReadyForSpeech(Bundle params) { Log.d("MainActivity","onReadyForSpeech"); mText.setText(getString(R.string.ready_speech)); } @Override public void onBeginningOfSpeech() { Log.d("MainActivity","onBeginningOfSpeech"); mText.setText(""); } @Override public void onBufferReceived(byte[] buffer) { Log.d("MainActivity","onBufferReceived"); } @Override public void onRmsChanged(float rmsdB) { Log.d("MainActivity","onRmsChanged"); } @Override public void onEndOfSpeech() { Log.d("MainActivity","onEndOfSpeech"); stopRecording(); } @Override public void onError(int error) { Log.d("MainActivity","onError.error="+error); //mAlert.setMessage(getString(R.string.speech_error) + "\nエラーコード:" + error); //mAlert.show(); mText.setText(getString(R.string.speech_error) + "\nエラーコード:" + error); stopRecording(); } @Override public void onEvent(int eventType, Bundle params) { Log.d("MainActivity","onEvent.eventType="+eventType); } @Override public void onPartialResults(Bundle partialResults) { Log.d("MainActivity","onPartialResults"); String str = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION).get(0); if(str.length() > 0) { mText.setText(str); } } @Override public void onResults(Bundle results) { Log.d("MainActivity","onResults"); } }
・onCreateでまずcheckRecordableを呼び、音声認識サポート外端末であればその旨をテキスト表示、録音が許可されていなかったらユーザーに許可を求めるシステムダイアログを出します。
・ボタン押下で音声認識の開始・終了をトグルします。現在録音中であるのか否かはSpeechRecognizer変数がnullかどうかで判別します。
・startRecordingでSpeechRecognizerを生成し開始します。スピーチ途中でも変換テキストを受け取る(onPartialResultsが呼ばれる)ためには、
intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
とします。これが重要なポイントです。
・stopRecordingでSpeechRecognizerをキャンセルして破棄します。
・あとはすべてSpeechRecognizerのイベントリスナー関数です。スピーチ途中でonPartialResults関数が呼ばれるので、第一候補を随時テキストに出力しています。このため、スピーチが終わった後に呼ばれるonResultsでは何もしていません。
・onErrorまたはonEndOfSpeechが呼ばれた時点でstopRecordingを呼んで終了しています。
XML | layout/activity_main.xml | GitHub Source |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@color/colorPrimaryDark" android:padding="10dp" android:orientation="vertical"> <Button android:id="@+id/speech_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="@string/start_speech" android:textColor="@color/colorPrimaryDark" android:textSize="18dp" /> <ScrollView android:layout_marginTop="5dp" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1"> <TextView android:id="@+id/output_text" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" android:textColor="@color/colorAccent" android:textSize="18dp" /> </ScrollView> </LinearLayout>
XML | values/strings.xml | GitHub Source |
<resources> <string name="app_name">VoiceToText</string> <string name="start_speech">スピーチ開始</string> <string name="stop_speech">スピーチ終了</string> <string name="error">エラー</string> <string name="ok">OK</string> <string name="speech_not_available">音声認識が使用できません</string> <string name="speech_not_granted">マイクの使用を許可してください</string> <string name="prepare_speech">準備中...</string> <string name="ready_speech">スピーチできます</string> <string name="speech_error">音声認識中にエラーが発生しました</string> </resources>
XML | values/colors.xml | GitHub Source |
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorWhite">#FFFFFF</color> <color name="colorPrimary">#6200EE</color> <color name="colorPrimaryDark">#3700B3</color> <color name="colorAccent">#03DAC5</color> </resources>
※本記事内容の無断転載を禁じます。
ご連絡は以下アドレスまでお願いします★
Windowsのデスクトップ画面をそのまま配信するための下準備
WindowsでGPUの状態を確認するには(ASUS系監視ソフトの自動起動を停止する)
CORESERVER v1プランからさくらインターネットスタンダートプランへ引っ越しメモ
さくらインターネットでPython MecabをCGIから使う
さくらインターネットのPHPでAnalytics-G4 APIを使う
インクルードパスの調べ方
【Git】特定ファイルを除外する.gitignore
【Ubuntu/Debian】NVIDIA関係のドライバを自動アップデートさせない
【Python】Spacyを使用して文章から出発地と目的地を抜き出す
Googleファミリーリンクで子供の端末の現在地がエラーで取得できない場合
Windows版Google Driveが使用中と言われアンインストールできない場合
【C/C++】小数点以下の切り捨て・切り上げ・四捨五入
【Apache】サーバーに同時接続可能なクライアント数を調整する
【Windows10】リモートデスクトップ間のコピー&ペーストができなくなった場合の対処法
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
GitLabにHTTPS経由でリポジトリをクローン&読み書きを行う
VirtualBoxの仮想マシンをWindows起動時に自動起動し終了時に自動サスペンドする
Windows11でMacのキーボードを使うには