アプリケーション開発ポータルサイト
ServerNote.NET
ServerNote.NET厳選キャンペーン・クーポンはこちら!
カテゴリー【AndroidJava
【Android】マイク使用許可を得て音声をテキストに変換する(音声認識)
POSTED BY
2022-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>

韓国発デイリー毎日着回したくなるレディース専門通販【EVERY NANA】
20代・30代デイリーでオシャレに楽しめるレディース服専門通販【EVERY NANA】です。 韓国で大人気のデイリー...READ MORE
あなたの腸内美容年齢は、なんと〇〇歳!?腸内フローラ検査キットで今すぐチェック→
\若さの9割は腸で決まる!/腸内フローラ検査キット【腸内博士】 ☆★【腸内博士】が売れている理由★☆ ...READ MORE
ねこちゃんは喋れないからDNAに聴きましょう!WEBで結果確認可能な猫遺伝子検査 【Pontely】
(1)WEBで検査結果を確認可能 (2)後から追加申込可能 (3)プランがシンプル (4)料金は業界最安水準 (5)検査結...READ MORE
※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
【webmaster/管理人】
自営業プログラマー
ご連絡は以下アドレスまで★

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