喋るドーナツ

(2009/09/25:追記)
すみません、この記事の内容間違っています。
次の日に公式ブログの新情報により正しい情報を書きましたのでそちらをご覧ください。

おひさしぶりです。
すっかり更新をしていない昨今でありますが皆さんお元気でしょうか。


さて、連休に入りすっかり生活のペースが駄目人間になり寝たはいいけど悪夢で飛び起きたりしたある早朝にふとSDK1.6が公開されたまま放っておいたことに気付きました。


早速眠い目をこすりつつSDKのサンプルをあさりました。
するとGestureBuilderなる異様なアプリが追加されているではありませんか。
しかし、ソースを見ても実行してみてもこれはジェスチャーを作ってファイルに残すだけで今一面白みがありません。


# でもTextViewだけでサムネイル表示できるんだよなとか復習にはなりました。


そこでじゃぁ新機能のTextToSpeechでも喋らせてみようかねぇという気になりました。
それがいつものはまり道だったのでございます。べんべん。


とりあえず結論というか答は人のサイトで紹介されていましたのでこちらをご覧ください。
http://almondmendoza.com/2009/09/19/using-the-official-texttospeech-in-android-1-6-tutorial/


暇な方は以下、どうぞ。

如何にして私は再びはまり道へと向かったのか

Googleさんは相変らず冷酷で(笑)新規にリリースされたSDKには全くもって新APIの使用方法の解説ががありません。
APIのリファレンスはあるにしても、メソッドの説明が全くないなどは序の口です。


今回なぜはまったのかと言えば以下の3つの利用が考えられます。

  • TextToSpeechのAPI設計が変
  • GoogleさんがSDKのリリースミス
  • Localeの問題とTTSの実行時間


最初のTextToSpeechですが、コンストラクタが一つしかありません。

http://developer.android.com/reference/android/speech/tts/TextToSpeech.html

public TextToSpeech (Context context, TextToSpeech.OnInitListener listener)

さて第一引数はお馴染ですので問題なしとして、第二引数をどう理解するかです。
ドキュメントには以下のようにしか書いてありません。

listener	The TextToSpeech.OnInitListener that will be called when the TextToSpeech engine has initialized.

これだけでは何が何やらさっぱりです。
OnInitListenerにはメソッドが一つしか無く、返り値はvoidです。

public abstract void onInit (int status)

通常のJavaでかつ、オブジェクト指向な設計ならまずインスタンスを取得して、その後、そのインスタンスにてメソッドを実行するはずです。
ですので私はとりあえず、第二引数を適当に実装してお茶を濁し、インスタンスをいじくってみることにしました。


さて、そうするととりあえずAPI一覧を見て、まず話させる命令はspeechであることがわかります。
TTSは自然言語依存であり、Localeにて使用する言語を指定しなければなりません。
当然SpeechEngineがサポートした言語しか話せないことが予想されます。
そこでisLanguageAvailable(Locale loc)です。
このメソッドはLocaleを渡すとそのロケールがサポートされているかどうか判断してくれるようです。


で、早速コードを書きました。

        tts = new TextToSpeech(this, new OnInitListener() {
			public void onInit(int status) {
				Log.d("TEST", "status = " + status);
			}
		});

        Log.d("TEST", "Locale = " + tts.getLanguage());
        isAvailable(tts, Locale.CANADA);
        isAvailable(tts, Locale.CANADA_FRENCH);
        isAvailable(tts, Locale.CHINA);
        isAvailable(tts, Locale.CHINESE);
        isAvailable(tts, Locale.ENGLISH);
        isAvailable(tts, Locale.FRANCE);
        isAvailable(tts, Locale.FRENCH);
        isAvailable(tts, Locale.GERMAN);
        isAvailable(tts, Locale.GERMANY);
        isAvailable(tts, Locale.ITALIAN);
        isAvailable(tts, Locale.ITALY);
        isAvailable(tts, Locale.JAPAN);
        isAvailable(tts, Locale.JAPANESE);
        isAvailable(tts, Locale.KOREA);
        isAvailable(tts, Locale.KOREAN);
        isAvailable(tts, Locale.PRC);
        isAvailable(tts, Locale.SIMPLIFIED_CHINESE);
        isAvailable(tts, Locale.TAIWAN);
        isAvailable(tts, Locale.TRADITIONAL_CHINESE);
        isAvailable(tts, Locale.UK);
        isAvailable(tts, Locale.US);
        
        Log.d("TEST", "ret of setLang: " + tts.setLanguage(Locale.ENGLISH));
        Log.d("TEST", "result = " + tts.speak("This is a pen.", TextToSpeech.QUEUE_FLUSH, null));


実行結果は以下のとおりです。
なんと全てのロケールがサポートされておりません。

09-22 18:09:55.461: DEBUG/TEST(769): isAvailable: en_CA = -2
09-22 18:09:55.461: DEBUG/TEST(769): isAvailable: fr_CA = -2
09-22 18:09:55.461: DEBUG/TEST(769): isAvailable: zh_CN = -2
09-22 18:09:55.461: DEBUG/TEST(769): isAvailable: zh = -2
09-22 18:09:55.461: DEBUG/TEST(769): isAvailable: en = -2
09-22 18:09:55.461: DEBUG/TEST(769): isAvailable: fr_FR = -2
09-22 18:09:55.461: DEBUG/TEST(769): isAvailable: fr = -2
09-22 18:09:55.461: DEBUG/TEST(769): isAvailable: de = -2
09-22 18:09:55.461: DEBUG/TEST(769): isAvailable: de_DE = -2
09-22 18:09:55.461: DEBUG/TEST(769): isAvailable: it = -2
09-22 18:09:55.461: DEBUG/TEST(769): isAvailable: it_IT = -2
09-22 18:09:55.510: DEBUG/TEST(769): isAvailable: ja_JP = -2
09-22 18:09:55.510: DEBUG/TEST(769): isAvailable: ja = -2
09-22 18:09:55.510: DEBUG/TEST(769): isAvailable: ko_KR = -2
09-22 18:09:55.510: DEBUG/TEST(769): isAvailable: ko = -2
09-22 18:09:55.510: DEBUG/TEST(769): isAvailable: zh_CN = -2
09-22 18:09:55.510: DEBUG/TEST(769): isAvailable: zh_CN = -2
09-22 18:09:55.510: DEBUG/TEST(769): isAvailable: zh_TW = -2
09-22 18:09:55.510: DEBUG/TEST(769): isAvailable: zh_TW = -2
09-22 18:09:55.510: DEBUG/TEST(769): isAvailable: en_GB = -2
09-22 18:09:55.510: DEBUG/TEST(769): isAvailable: en_US = -2
09-22 18:09:55.510: DEBUG/TEST(769): ret of setLang: -2
09-22 18:09:55.510: DEBUG/TEST(769): result = -1
09-22 18:09:56.418: DEBUG/TEST(769): status = 0

"-2"というのはTTSのfinal int定数でLANG_NOT_SUPPORTEDの意です。
"-1"もエラーです。
なんと英語ですら喋ってくれません。


これはどうしたことか、実機がいるのだろうかと思いつつ情報を探し始めました。
すると割とすぐに以下の情報に辿りつきました。

Google グループ


まぁ、原因はこれだろうと思いました。
SDKを再びダウンロードし、エミュレータやらを終了してSDKを取り替え再度実行します。
しかし結果は変わりません。


ここで私は軽いパニックに陥りました。
なぜSDKも更新したのに動かないのか?
このSDKはまだ駄目なのか?であるとすればまともなSDKはどこにあるのか?


さすがにここでちょっと焦りました。上の投稿にはソースがありません。
しかし大抵のことなら本家の開発者向けMLに情報があるのは経験からわかります。
行ってみると一発で元の情報に当たりました。

Google グループ

Xavier Ducrohet  	
A last minute mix-up had us upload the wrong SDK packages :( 

The only difference is the lack of TTS language files and of the 
Accessibility apps (TalkBack, SoundBack and KickBack) 

If you don't care about this you can keep using your 1.6 SDK. If you 
care about these features, I've just uploaded the correct archives. 
We apologize for the inconvenience. 

なんとGoogleのXavierさん、リリース時にTTSのランゲージファイルを欠落してしまったようです。
既に新しいものと交換してあるとのこと。


# リリースバージョンを上げて欲しかったと思います。
# たった一行のセキュリティフィックスでもrの後ろの番号は上がったのですから。


わかりにくいもので別の人から突込まれていますね。

Hi, 
Could you please explain this a bit more - 
Does that mean that TTS won't work with the SDK files here - 
http://developer.android.com/sdk/1.6_r1/index.html 

Xavierさんが答えます。

Xavier Ducrohet  	

I've replaced the files at http://developer.android.com/sdk/1.6_r1/index.html 
(By the way, the size/checksum indicated on this page were always the 
one of the proper files, so if anyone had checked they should have 
realized the archives where wrong). 
To make sure you are running the system image that contains the TTS 
language packs: From Home, go to Settings, then Speech Synthesis. In 
there the first item 'listen to an example' should be enabled. click 
on it to be sure (it'll take a couple sec the first time) 

で、更新はちゃんとしたと。
確認方法は、ホーム画面からsettingを起動し、リストからSpeech Synthesisを選ぶ。その中で最初の項目のサンプルを聞くがenableされていること。押して確認してください。実行には数秒かかります。


で、ここで日本人には問題なのですが実はLocaleを日本語に設定してもサンプルを聞くボタンは有効です。
で、押してもエラーも何も表示されません。
実はDDMSのほうにエラーのログは出ています。
ですので、押しても鳴らない場合、それは非対応ロケールのせいです。


さて、これでSDKは大丈夫なことが理解できました。
駄目押しとして実行確認の投稿も来ています。

I downloaded 'newer' sdk and the code sample I provided previously now 
works OK, without alteration. 


これで私のプログラムに問題があることがはっきりした訳です。
で、後はしょうがないからGoogle様にひたすらお願いして検索した結果次の答に到達した訳です。

http://almondmendoza.com/2009/09/19/using-the-official-texttospeech-in-android-1-6-tutorial/

# 実はそのスレッドにも答があったのだけど、メール上のコードはつい読み飛ばしてしまいました。

要はインスタンスを取得して使うのは駄目で、OnInitListenerで呼ばれるonInitの中で実行しなければいけなかったのですね。
onInitの外で実行すると例外も出ずに実行結果が異なるという実に嫌な仕様です(--;

	public void onInit(int status) {
		Log.d("TEST", "status = " + status);
		
        Log.d("TEST", "Locale = " + tts.getLanguage());
        isAvailable(tts, Locale.CANADA);
        isAvailable(tts, Locale.CANADA_FRENCH);
        isAvailable(tts, Locale.CHINA);
        isAvailable(tts, Locale.CHINESE);
        isAvailable(tts, Locale.ENGLISH);
        isAvailable(tts, Locale.FRANCE);
        isAvailable(tts, Locale.FRENCH);
        isAvailable(tts, Locale.GERMAN);
        isAvailable(tts, Locale.GERMANY);
        isAvailable(tts, Locale.ITALIAN);
        isAvailable(tts, Locale.ITALY);
        isAvailable(tts, Locale.JAPAN);
        isAvailable(tts, Locale.JAPANESE);
        isAvailable(tts, Locale.KOREA);
        isAvailable(tts, Locale.KOREAN);
        isAvailable(tts, Locale.PRC);
        isAvailable(tts, Locale.SIMPLIFIED_CHINESE);
        isAvailable(tts, Locale.TAIWAN);
        isAvailable(tts, Locale.TRADITIONAL_CHINESE);
        isAvailable(tts, Locale.UK);
        isAvailable(tts, Locale.US);
        
        Log.d("TEST", "ret of setLang: " + tts.setLanguage(Locale.ENGLISH));
        Log.d("TEST", "result = " + tts.speak("Aoi sora shiroi kumo.", TextToSpeech.QUEUE_FLUSH, null));
	}

まぁ、わかればそれまでのことですのでLocaleを調査しました。
以下が実行結果です。

09-23 00:22:45.419: DEBUG/TEST(924): status = 0
09-23 00:22:45.419: DEBUG/TEST(924): Locale = eng_USA
09-23 00:22:45.429: DEBUG/TEST(924): isAvailable: en_CA = 0
09-23 00:22:45.438: DEBUG/TEST(924): isAvailable: fr_CA = 0
09-23 00:22:45.450: DEBUG/TEST(924): isAvailable: zh_CN = -2
09-23 00:22:45.450: DEBUG/TEST(924): isAvailable: zh = -2
09-23 00:22:45.460: DEBUG/TEST(924): isAvailable: en = 0
09-23 00:22:45.479: DEBUG/TEST(924): isAvailable: fr_FR = 1
09-23 00:22:45.479: DEBUG/TEST(924): isAvailable: fr = 0
09-23 00:22:45.489: DEBUG/TEST(924): isAvailable: de = 0
09-23 00:22:45.489: DEBUG/TEST(924): isAvailable: de_DE = 1
09-23 00:22:45.499: DEBUG/TEST(924): isAvailable: it = 0
09-23 00:22:45.509: DEBUG/TEST(924): isAvailable: it_IT = 1
09-23 00:22:45.519: DEBUG/TEST(924): isAvailable: ja_JP = -2
09-23 00:22:45.530: DEBUG/TEST(924): isAvailable: ja = -2
09-23 00:22:45.559: DEBUG/TEST(924): isAvailable: ko_KR = -2
09-23 00:22:45.559: DEBUG/TEST(924): isAvailable: ko = -2
09-23 00:22:45.559: DEBUG/TEST(924): isAvailable: zh_CN = -2
09-23 00:22:45.571: DEBUG/TEST(924): isAvailable: zh_CN = -2
09-23 00:22:45.589: DEBUG/TEST(924): isAvailable: zh_TW = -2
09-23 00:22:45.599: DEBUG/TEST(924): isAvailable: zh_TW = -2
09-23 00:22:45.609: DEBUG/TEST(924): isAvailable: en_GB = 1
09-23 00:22:45.620: DEBUG/TEST(924): isAvailable: en_US = 1
09-23 00:22:45.630: DEBUG/TEST(924): ret of setLang: 0
09-23 00:22:45.670: DEBUG/TEST(924): result = 0

そんな訳で現在SDK1.6にてサポートされている言語は以下の4つとなります。

  • 英語
  • フランス語
  • イタリア語
  • ドイツ語


残念ながら日本語を含むCJKの全ては全滅です。
でもこれはこれで味のある日本語を喋らせることができます。


そんな訳でお約束。ゆっくりしていってね

tts.speak("yuck lit shit it ne.", TextToSpeech.QUEUE_FLUSH, null);

まとめ

Android SDKの新機能は公式Blogで情報が出るまで待ちましょう。
今回はたまたまGoogleが既に外部ライブラリとして公開していたものが標準化されたために情報が早く外部から出たという幸運なだけだったようです。
たぶんandroid.git.kernel.orgからソースを落としてガンガン解析する気がないとつらいと思います。
公式ブログでは早速1.6のQuick Search Boxの情報が公開されたようです。
どうやらホーム画面のサーチwidgetに自分のプログラムを噛ませて独自のサーチを実行させられるようですね。
これでLiveFolderはいらないのかな?


TTSは面白いです。nullで誤魔化した部分に色々とパラメータを指定できるようですのでたぶん凝り始めると色々と面白いことができると思います。
やっぱりPCが喋ると心踊りますね。
色々と活用方法を考えたいと思います。
ではでは。