2024-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>
※本記事内容の無断転載を禁じます。
ご連絡は以下アドレスまでお願いします★
Wav2Lipのオープンソース版を改造して外部から呼べるAPI化する
Wav2Lipのオープンソース版で静止画の口元のみを動かして喋らせる
【iOS】アプリアイコン・ロゴ画像の作成・設定方法
オープンソースリップシンクエンジンSadTalkerをAPI化してアプリから呼ぶ【2】
オープンソースリップシンクエンジンSadTalkerをAPI化してアプリから呼ぶ【1】
【Xcode】iPhone is not available because it is unpairedの対処法
【Let's Encrypt】Failed authorization procedure 503の対処法
【Debian】古いバージョンでapt updateしたら404 not foundでエラーになる場合
ファイアウォール内部のWindows11 PCにmacOS Sequoiaからリモートデスクトップする
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
進研ゼミチャレンジタッチをAndroid端末化する
VirtualBoxの仮想マシンをWindows起動時に自動起動し終了時に自動サスペンドする
Googleスプレッドシートを編集したら自動で更新日時を入れる
Androidホームで左にスワイプすると出てくるニュース共を一切表示させない方法
【C/C++】小数点以下の切り捨て・切り上げ・四捨五入
Windows11のコマンドプロンプトでテキストをコピーする
size_tとssize_tを使い分けてSegmentation Faultを予防する
【C++】staticメンバ変数がundefined referenceとエラーになる場合