アプリケーション開発ポータルサイト
ServerNote.NET
Amazon.co.jpでPC関連商品タイムセール開催中!
カテゴリー【AndroidJava
【Android】マイク使用許可を得て音声をテキストに変換する(音声認識)
POSTED BY
2023-07-13
【Swift UI】マイク使用許可を得て音声をテキストに変換する(音声認識)のAndroid/Java版です。
Android端末のマイクでしゃべった内容をテキストに変換して出力するサンプルです。
「スピーチ開始」で、端末に向かって喋りかければ、変換内容を随時テキスト出力します。

プロジェクト一式はこちら。

https://github.com/servernote/AndroidSample/tree/master/VoiceToText

録音を許可するパーミッションRECORD_AUDIOをManifestに記載しますがこれだけでは不十分=デンジャラスパーミッションであるため、アプリの中でrequestPermissionを呼び、ユーザーに明示的に許可をもらわなくてはいけません。

XMLAndroidManifest.xmlGitHub 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>

JavaMainActivity.javaGitHub 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を呼んで終了しています。

XMLlayout/activity_main.xmlGitHub 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>

XMLvalues/strings.xmlGitHub 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>

XMLvalues/colors.xmlGitHub 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>

※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
【WEBMASTER/管理人】
自営業プログラマーです。お仕事ください!
ご連絡は以下アドレスまでお願いします★

☆ServerNote.NETショッピング↓
ShoppingNote / Amazon.co.jp
☆お仲間ブログ↓
一人社長の不動産業務日誌
【キーワード検索】