SpeechRecognizerで「メリークリスマス」を音声認識してみよう



最近寒くなりましたね！
クリスマスも近づいていて、寂しいなーと思いながらGoogleホームにでも話しかけたいのですが購入してないのでこの機会に音声認識を使ってみました！


## SpeechRecognizer とは？
[SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer.html)とは音声認識機能を使うためのサービスにアクセスするクラスです。
[createSpeechRecognizer(Context)](https://developer.android.com/reference/android/speech/SpeechRecognizer.html#createSpeechRecognizer(android.content.Context))でインスタンスを作ってアクセス出来て、メインスレッドに限って呼び出せます！
あと`RECORD_AUDIO`permissionが必要になります。

継続的に使うことを意図してないので、その場合はかなりの量のバッテリーを使ってしまうらしいです。

## 試してみる
スタートボタンを押してから音声を取り始め、その音声をテキストに出すまでのコードを書いてみます！

まずはpermissionが必要なため`AndroidManifest.xml`に`RECORD_AUDIO`permissionを追加します！
あと音声認識の処理がサーバーで行われるため、通信が繋がってないと使えないので`INTERNET`permissionも追加します。

```AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
```


次はレイアウトです。
テキスト表示のためのTextViewとスタートボタン、5秒のカウントダウンを表示するためのTextViewで構成されてます。

```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/buttonView"
        android:layout_centerHorizontal="true"
        android:textSize="18sp"
        tools:text="ここにテキスト表示" />

    <Button
        android:id="@+id/buttonView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="スタート" />

    <TextView
        android:id="@+id/countTextViwe"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/buttonView"
        android:layout_centerHorizontal="true"
        android:textSize="23sp"
        tools:text="あと5秒!" />

</RelativeLayout>
```


最後に`MainActivity.java`ですが、permission拒否の処理とかエラーハンドリングはしてないです。
スタートボタンを押して5秒の間なにか言うとそれがテキストに表示されます！

ただし「メリークリスマス」と言った場合だけ文字列を比較してToastが出るようにしました。

簡単な単語や短い文章ぐらいしか認識できないのでクォリティーを高めるためにはエラーハンドリングをしっかり書いたり、RecognitionListenerの中身をカスタムしないといけない気がします。

```MainActivity.java
public class MainActivity extends AppCompatActivity {

    private static final int MILLIS_IN_FUTURE = 11 * 500;
    private static final int COUNT_DOWN_INTERVAL = 1000;

    private Intent intent;
    private SpeechRecognizer mRecognizer;
    private TextView textView;

    private int count = 5;
    private TextView countTextView;
    private CountDownTimer countDownTimer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        textView = (TextView) findViewById(R.id.textView);        
        countTextView = (TextView) findViewById(R.id.countTextViwe);
        
		 // permission チェック
        if (ContextCompat.checkSelfPermission(this, RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, RECORD_AUDIO)) {
                // 拒否した場合
            } else {
                // 許可した場合
                int MY_PERMISSIONS_RECORD_AUDIO = 1;
                ActivityCompat.requestPermissions(this, new String[]{RECORD_AUDIO}, MY_PERMISSIONS_RECORD_AUDIO);
            }
        }

        intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, getPackageName());
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.JAPAN.toString());

        mRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
        mRecognizer.setRecognitionListener(recognitionListener);

		 // 音声認識スタートボタン
        Button button = (Button) findViewById(R.id.buttonView);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                
                // 音声テキスト初期化
                if (!TextUtils.isEmpty(textView.getText())) {
                    textView.setText("");
                }

                // カウントダウンスタート
                countDownTimer();
                countDownTimer.start();

                // レコーディングスタート
                mRecognizer.startListening(intent);
            }
        });
    }

    private RecognitionListener recognitionListener = new RecognitionListener() {
        @Override
        public void onReadyForSpeech(Bundle bundle) {
            Log.d("log:: ", "準備できてます");
        }

        @Override
        public void onBeginningOfSpeech() {
            Log.d("log:: ", "始め！");
        }

        @Override
        public void onRmsChanged(float v) {
            Log.d("log:: ", "音声が変わった");
        }

        @Override
        public void onBufferReceived(byte[] bytes) {
            Log.d("log:: ", "新しい音声");
        }

        @Override
        public void onEndOfSpeech() {
            Log.d("log:: ", "終わりました");
        }
        
        @Override
        public void onError(int i) {
            switch (i) {
                case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
                    resetText();
                    textView.setText("ネットワークタイムエラー");
                    break;
                case SpeechRecognizer.ERROR_NETWORK:
                    resetText();
                    textView.setText("その外ネットワークエラー");
                    break;
                case SpeechRecognizer.ERROR_AUDIO:
                    resetText();
                    textView.setText("Audio エラー");
                    break;
                case SpeechRecognizer.ERROR_SERVER:
                    resetText();
                    textView.setText("サーバーエラー");
                    break;
                case SpeechRecognizer.ERROR_CLIENT:
                    resetText();
                    textView.setText("クライアントエラー");
                    break;
                case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
                    resetText();
                    textView.setText("何も聞こえてないエラー");
                    break;
                case SpeechRecognizer.ERROR_NO_MATCH:
                    resetText();
                    textView.setText("適当な結果を見つけてませんエラー");
                    break;
                case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
                    resetText();
                    textView.setText("RecognitionServiceが忙しいエラー");
                    break;
                case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
                    resetText();
                    textView.setText("RECORD AUDIOがないエラー");
                    break;
            }
        }

        @Override
        public void onResults(Bundle bundle) {
            String key = SpeechRecognizer.RESULTS_RECOGNITION;
            ArrayList<String> mResult = bundle.getStringArrayList(key);

            String[] result = new String[0];
            if (mResult != null) {
                result = new String[mResult.size()];
            }
            if (mResult != null) {
                mResult.toArray(result);
            }

            textView.setText(result[0]);

            // テキスト比較
            if (TextUtils.equals(result[0], "メリークリスマス")) {
                Toast.makeText(MainActivity.this, "あなたもね！！", Toast.LENGTH_SHORT).show();
                countDownTimer.cancel();
                countTextView.setText("");
            }
        }

        @Override
        public void onPartialResults(Bundle bundle) {
        }

        @Override
        public void onEvent(int i, Bundle bundle) {
        }
    };

    private void resetText() {
        countDownTimer.cancel();
        countTextView.setText("やり直し！");
    }

    public void countDownTimer() {
        if (countDownTimer != null) {
            countDownTimer.cancel();
            count = 5;
        }

        countDownTimer = new CountDownTimer(MILLIS_IN_FUTURE, COUNT_DOWN_INTERVAL) {
            public void onTick(long millisUntilFinished) {
                String countString = getString(R.string.count_string, count);
                countTextView.setText(countString);
                count--;
            }

            public void onFinish() {
                countDownTimer.cancel();
                countTextView.setText(String.valueOf("やり直し？"));
            }
        };
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        try {
            countDownTimer.cancel();
        } catch (Exception e) {
            Log.e("EXCEPTION LOG ", "message:: " + e.getMessage());
        }
        countDownTimer = null;
    }

}
```

## 結果
こちらが実行結果になります！

![スクリーンショット 2017-12-09 10.27.39.png](https://qiita-image-store.s3.amazonaws.com/0/75978/9994a2a3-3b90-1278-954e-c8fd2e85347f.png "スクリーンショット 2017-12-09 10.27.39.png")

すごく雑な感じなのですが、なんとなく返事をもらうことができました…！

体育会係の部活とか、スポーツはやってなくて「返事だけはイイ」で参加させて頂いたのですが
コード書いててとても楽しかったです！
それではまだ少し早いのですが皆さん、 
#### メリークリスマス！

## 参照
https://developer.android.com/reference/android/speech/SpeechRecognizer.html
https://techbooster.org/android/application/14927/