スポンサーリンク

Androidで音声入力した内容を認識し,そのまま音声合成。「おうむ返し」アプリのソースコード


音声入力した内容を,そのまま音声出力してみよう。

というAndroidアプリのサンプルコード。


RecognizerIntentを使う場合と,SpeechRecognizerを使う場合の2通り掲載。

前置き

Google音声入力は,とても便利だ。

いちいち手動で文字を打たなくても,端末が音声を聞き取って自動認識し,文字を入力してくれる。

アプリ開発者としても,自然言語処理を気軽にAPI経由で行なえる。

人間と自然に対話する高度なUIを実装していくために,活用シーンが増えてゆくだろう。(例:iOSのSiri)



また,テキストの読み上げ(自動スピーチ)も比較的かんたんに実現できる時代になった。

音声認識に比べ利用の機会が少ないかもしれないが,

合成音声を使った遊びは密かにブレイクしている。(例:初音ミクによる歌声合成)

UIのアクセシビリティ向上にもつながる。



では,両者を合体させて

  • 音声による入力
  • 音声による出力

の両方を兼ね備えたらどうなるか。

ソフトウェアと人間が,より自然に対話しやすくなる。



このようなUIを実現できるデバイスは,なんと言っても電話・スマホである。

最初からマイクもあるし,スピーカーもあるから。

音声によるI/Oが考慮された構造なので,PCよりもずっと上記機能を試しやすいのだ。



以下では,Androidによる「自動音声入力+自動音声出力の組み合わせ」の骨組みを掲載する。

両方ともAPI化されており,きわめて簡単に動作する。

※ただ,日本語に特化した読み上げは,標準SDKではまだ未対応。

漢字の読み上げを自動化するのが厳しいから。


さしあたり,英語でしゃべった内容は,正確に英語で「おうむ返し」される。

インプットが下手だと誤認識されるので,アウトプットも混乱して「おうむ返し」できないが…。

→上手にしゃべること。 自分の外国語の発音レベルを測定するツールにもなる。

サンプルコード

アクティビティ:

package com.example.speechtest;

import android.os.Bundle;
import android.app.Activity;

import java.util.ArrayList;
import java.util.Locale;

import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.speech.RecognizerIntent;
import android.speech.tts.TextToSpeech;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

/**
 * 音声入力(Input)と音声読み上げ(Output)のテスト。
 * マイクに入った音声を認識して,そのまま音声合成し,おうむ返しにスピーカ出力を試みる。
 * @author id:language_and_engineering
 *
 */
public class MainActivity extends Activity implements OnClickListener, TextToSpeech.OnInitListener
{
    // ダミーの識別子
    private static final int REQUEST_CODE = 0;

    // 音声合成用
    TextToSpeech tts = null;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener( this );

        tts = new TextToSpeech(this, this);
    }


    @Override
    public void onClick(View v)
    {
        try {
            // "android.speech.action.RECOGNIZE_SPEECH" を引数にインテント作成
            Intent intent = new Intent(
                    RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
            intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                    RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);

            // 「お話しください」の画面で表示される文字列
            intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "音声認識中です");

            // 音声入力開始
            startActivityForResult(intent, REQUEST_CODE);
        } catch (ActivityNotFoundException e) {
            // 非対応の場合
            Toast.makeText(this, "音声入力に非対応です。", Toast.LENGTH_LONG).show();
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        // インテントの発行元を限定
        if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {

            // 音声入力の結果の最上位のみを取得
            ArrayList<String> results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            String s = results.get(0);

            // 表示
            Toast.makeText(this, s, Toast.LENGTH_LONG).show();

            // 音声合成して発音
            if(tts.isSpeaking()) {
                tts.stop();
            }
            tts.speak(s, TextToSpeech.QUEUE_FLUSH, null);

        }

        super.onActivityResult(requestCode, resultCode, data);
    }


    @Override
    public void onInit(int status) {
        if(status == TextToSpeech.SUCCESS) {
            // 音声合成の設定を行う

            float pitch = 1.0f; // 音の高低
            float rate = 1.0f; // 話すスピード
            Locale locale = Locale.US; // 対象言語のロケール
                // ※ロケールの一覧表
                //   http://docs.oracle.com/javase/jp/1.5.0/api/java/util/Locale.html

            tts.setPitch(pitch);
            tts.setSpeechRate(rate);
            tts.setLanguage(locale);
        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();

        if( tts != null )
        {
            // 破棄
            tts.shutdown();
        }
    }

}


レイアウトXML:

<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" >


	<Button
	    android:id="@+id/button1"
	    android:text="recognize"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"/>


</RelativeLayout>


とても短いソースコードで,実現できてしまう。


動作にあたって下記の点に注意すること。

  • 読み上げのためには,音声データをインストールしておく必要がある。後述の手順を参照。
  • 音声を出すためには,マナーモードを解除する。


音声認識画面で端末のメニューキーを押せば設定画面が開き,言語を選択できる。



英語の読み上げのテスト材料としては,BBCのトップニュースなんかを即興で朗読してみるとよい。

http://www.bbc.co.uk/


インプット:音声認識または音声入力(ASR, Auto Speech Recognition)

標準APIのRecognizerIntentクラスで,音声入力して漢字かな交じりの日本語文を取得する方法

AndroidでGoogle音声入力のサンプルコード
http://www.adakoda.com/android/000164...

  • SDK1.5以降はインテントとして利用可能


音声で文字入力する
http://wiki.livedoor.jp/moonlight_ask...

  • Androidの音声認識機能は, 端末とサーバとで処理を分担する分散型音声認識(DSR:Distributed Speech Recognition)方式。音声認識機能を使用する際にはサーバに接続するために3GまたはWiFiが必要


SpeechRecognizerを使った音声認識を行う
http://techbooster.org/android/applic...

  • オプション類の詳しい使い方の説明

音声入力結果をひらがなで取得できるかどうか(できれば読み上げもやりやすくなるのだが)

日本Androidの会 / 音声認識のかな取得について
https://groups.google.com/forum/?from...

  • 認識結果をひらがなオンリーの文字列として取得することは不可能(端末によってブレもある)
  • Yahooの形態素解析APIと連携するなど,サードパーティーに頼るしかない

アウトプット:音声合成,読み上げ(TTS, Text To Speech)

標準APIのTextToSpeechクラスで,文字列から音声に変換して読み上げる方法のサンプルコード

テキストを読み上げる
http://wiki.livedoor.jp/moonlight_ask...

  • 2011年の情報。日本語はサポートされていない。英語, フランス語, ドイツ語, イタリア語, スペイン語が対象。


テキストの読み上げ(TextToSpeech)を利用する
http://techbooster.jpn.org/andriod/ap...

  • TextToSpeechはAndroid1.6から実装,音声合成を利用してテキストを読み上げる機能
  • 動作させる実機に音声データのインストールが必要。端末の設定画面で「音声データをインストール」がグレーアウトされていない状態だと、まだインストールされていないので,マーケットからダウンロードする必要がある。「SpeechSynthesis Data Installer」を無料で入手可能
  • 詳しい実装方法


SpeechSynthesis Data Installer / Android TextToSpeech
https://play.google.com/store/apps/de...


サードパーティ製,日本語を読み上げる機能

アクエストの軽量Android用音声合成エンジン
http://juggly.cn/archives/3781.html

  • ひらがなとアクセント記号の文章を音声に変換。漢字は不可能。


Android JaTTS
http://gimite.net/pukiwiki/index.php?...

  • Androidに日本語をしゃべらせる音声合成アプリケーションとライブラリ
  • サーバ側でGalatea Talkを動かして、音声ファイルに変換したものをAndroidで再生している


アンドロイドで日本語音声出力(TextToSpeech):音声読み上げ
http://foonyan.sakura.ne.jp/wisteriah...

  • KDDIが開発した「N2 TTS」をマーケットからインストール


音声データの「SpeechSynthesis Data Installer」について。

現時点での対応言語:

  1. 英語(アメリカ合衆国)
  2. 英語(イギリス)
  3. フランス語(フランス)
  4. イタリア語(イタリア)
  5. ドイツ語(ドイツ)
  6. スペイン語(スペイン)


アンインストール方法:

SpeechSynthesis Data Installer 最新レビュー
http://android.giveapp.jp/RecentRevie...

  • これ自体は単なる音声データなので単独では何もできないです。対応アプリを入れましょう。勝手に喋ってウルサイといってる人はユーザー補助の設定を見直しましょう。ちなみにアンインストはSDカード内のsvoxフォルダを消せばOK
  • SDカード内の SVOX というフォルダを削除すれば削除できました


音声データがインストール済みかどうかの判定ロジック:

    if(tts.isLanguageAvailable(locale) >= TextToSpeech.LANG_AVAILABLE)
    {
        tts.setLanguage(locale);
    }
    else
    {
        Toast.makeText(getApplicationContext(), "この言語の音声合成に対応していません。", Toast.LENGTH_LONG).show();
    }

この判定ができれば,未インストール時に音声データのインストールを促すような処理が可能。


補足:Google音声入力について

本稿執筆時点での,Google音声認識の全対応言語の一覧

  1. アフリカーンス語(南アフリカ)
  2. アラビア語(エジプト)
  3. アラビア語(イスラエル)
  4. アラビア語(ヨルダン)
  5. アラビア語(クウェート)
  6. アラビア語(レバノン)
  7. アラビア語(カタール)
  8. アラビア語(サウジアラビア)
  9. アラビア語(アラブ首長国連邦)
  10. ブルガリア語(ブルガリア)
  11. カタロニア語(スペイン)
  12. 中国語,標準語(中国,簡体)
  13. 中国語,標準語(香港,簡体)
  14. 中国語,標準語(台湾,繁体)
  15. 中国語,広東語(香港,繁体)
  16. チェコ語(チェコ共和国)
  17. オランダ語(オランダ)
  18. 英語(オーストラリア)
  19. 英語(カナダ)
  20. 英語(インド)
  21. 英語(ニュージーランド)
  22. 英語(南アフリカ)
  23. 英語(イギリス)
  24. 英語(米国)
  25. 英語(一般)
  26. フィンランド語(フィンランド)
  27. フランス語(フランス)
  28. ガリシア語(スペイン)
  29. ドイツ語(ドイツ)
  30. ヘブライ語(イスラエル)
  31. ハンガリー語(ハンガリー)
  32. アイスランド語(アイスランド)
  33. インドネシア語(インドネシア)
  34. ズールー語(南アフリカ)
  35. イタリア語(イタリア)
  36. 日本語(日本)
  37. 韓国語(韓国)
  38. マレー語(マレーシア)
  39. ノルウェー語ボークモール(ノルウェー)
  40. ポーランド語(ポーランド)
  41. ポルトガル語(ブラジル)
  42. ポルトガル語(ポルトガル)
  43. ルーマニア語(ルーマニア)
  44. ロシア語(ロシア)
  45. セルビア語(セルビア,キリル)
  46. スロバキア語(スロバキア)
  47. スペイン語(アルゼンチン)
  48. スペイン語(ボリビア)
  49. スペイン語(チリ)
  50. スペイン語(コロンビア)
  51. スペイン語(コスタリカ)
  52. スペイン語(ドミニカ共和国)
  53. スペイン語(エクアドル)
  54. スペイン語(エルサルバドル)
  55. スペイン語(グアテマラ)
  56. スペイン語(ホンジュラス)
  57. スペイン語(メキシコ)
  58. スペイン語(ニカラグア)
  59. スペイン語(パナマ)
  60. スペイン語(パラグアイ)
  61. スペイン語(ペルー)
  62. スペイン語(プエルトリコ)
  63. スペイン語(スペイン)
  64. スペイン語(米国)
  65. スペイン語(ウルグアイ)
  66. スペイン語(ベネズエラ)
  67. スウェーデン語(スウェーデン)
  68. トルコ語(トルコ)
  69. Euskara(=スペイン・フランスの国境のバスク語。http://www.weblio.jp/content/Euskara
  70. Latin(=ラテン語)

補足2:処理対象となる言語をプログラムから指定


上述のサンプルコードでは,音声合成の対象言語をプログラム中で指定している。


しかし,音声入力の画面で,手動で言語を選ぶことになる。

それだとアプリのユーザにとっては面倒。


プログラム中から,対象言語を動的に指定することができる。

たとえば音声入力の対象言語を英語に固定したい場合,RecognizerIntentに下記のようなextraを渡す。

// Englishの場合
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.US.toString());
            //intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.JAPAN.toString());
            //intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.CHINA.toString());

これで,端末の言語設定などとは関係なく,このIntentによる音声入力の画面ではロケールとして英語が利用される事になる。


逆に,音声入力画面から対象言語を変えたとしても,プログラム的にはこのインテントで指定した言語として処理されることになり,変更できないので注意。

How to set the language in speech recognition on android?
http://stackoverflow.com/questions/10...

  • RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE を "en-US" としても,認識対象の言語は変わらない


日本語の音声認識
http://d.hatena.ne.jp/android_dev/201...

補足3:専用の画面を表示しない場合

音声入力用のダイアログを表示せず,画面遷移なしで音声入力させることもできる。

その場合,RecognizerIntentではなく,SpeechRecognizerを使う。

package com.example.speechtest;

import android.os.Bundle;
import android.app.Activity;

import java.util.ArrayList;
import java.util.Locale;

import android.content.Intent;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.speech.tts.TextToSpeech;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

/**
 * 音声入力(Input)と音声読み上げ(Output)のテスト。
 * マイクに入った音声を認識して,そのまま音声合成し,おうむ返しにスピーカ出力を試みる。
 * @author id:language_and_engineering
 *
 */
public class MainActivity extends Activity implements OnClickListener, TextToSpeech.OnInitListener
{
    // 音声入力用
    SpeechRecognizer sr;

    // 音声合成用
    TextToSpeech tts = null;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener( this );

        tts = new TextToSpeech(this, this);
    }


    @Override
    public void onClick(View v)
    {
        // 音声認識APIに自作リスナをセット
        sr = SpeechRecognizer.createSpeechRecognizer(this);
        sr.setRecognitionListener(new MyRecognitionListener());

        // インテントを作成
        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_LANGUAGE, Locale.US.toString());
        //intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.JAPAN.toString());
        //intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.CHINA.toString());

        // 音声認識APIにインテントを処理させる
        sr.startListening(intent);
    }



    @Override
    public void onInit(int status) {
        if(status == TextToSpeech.SUCCESS) {
            // 音声合成の設定を行う

            float pitch = 1.0f; // 音の高低
            float rate = 1.0f; // 話すスピード
            Locale locale = Locale.US; // 対象言語のロケール
                // ※ロケールの一覧表
                //   http://docs.oracle.com/javase/jp/1.5.0/api/java/util/Locale.html

            tts.setPitch(pitch);
            tts.setSpeechRate(rate);
            tts.setLanguage(locale);
        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();

        if( tts != null )
        {
            // 破棄
            tts.shutdown();
        }
    }



    // 音声認識のリスナ
    class MyRecognitionListener implements RecognitionListener {

        @Override
        public void onBeginningOfSpeech() {
        }

        @Override
        public void onBufferReceived(byte[] buffer) {
        }

        @Override
        public void onEndOfSpeech() {
        }

        @Override
        public void onError(int error) {
            Toast.makeText(getApplicationContext(), "エラー: " + error, Toast.LENGTH_LONG).show();
                // エラーコードの一覧表
                // http://developer.android.com/intl/ja/reference/android/speech/SpeechRecognizer.html#ERROR_AUDIO

                // 認識結果の候補が存在しなかった場合や,RECORD_AUDIOのパーミッションが不足している場合など
        }

        @Override
        public void onEvent(int eventType, Bundle params) {
        }

        @Override
        public void onPartialResults(Bundle partialResults) {
        }

        @Override
        public void onReadyForSpeech(Bundle params) {
            Toast.makeText(getApplicationContext(), "認識を開始します。", Toast.LENGTH_LONG).show();
        }


        @Override
        public void onResults(Bundle results) {
            // 結果を受け取る
            ArrayList<String> candidates = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
            String s = candidates.get(0);

            // トーストで結果を表示
            Toast.makeText(getApplicationContext(), s, Toast.LENGTH_LONG).show();

            // 音声合成して発音
            if(tts.isSpeaking()) {
                tts.stop();
            }
            tts.speak(s, TextToSpeech.QUEUE_FLUSH, null);
        }


        @Override
        public void onRmsChanged(float rmsdB) {
        }

    }

}

マニフェストXMLのmanifestタグ直下に,パーミッションの追記が必要。

    <uses-permission android:name="android.permission.RECORD_AUDIO" />

SpeechRecognizerを使った音声認識を行う
http://techbooster.org/android/applic...

  • RecognizerIntentを利用した方法では、マイクの絵が表示されているダイアログが表示される。このAndroid標準のダイアログを表示せず、独自の画像等で音声入力状態を表示したい場合などに利用するのがSpeechRecognizer
  • SpeechRecognizerは、Androidの音声認識用に用意されているServiceにアクセスするためのクラス
  • エラーハンドリングつきの詳しいサンプルコード


音声認識(SpeechRecognizer)
http://office-matsunaga.biz/android/d...

  • マイクのプロンプトが表示されないですむ


エラーコードの一覧表
http://developer.android.com/intl/ja/...



このエントリの続き:

Androidで,音声入力と音声合成をシンプルに記述するためのライブラリ案
http://language-and-engineering.hatenablog.jp/entry/20121107/AndroidTTSAndSpe...