Tech Sketch Bucket of Technical Chips by TIS Inc.

日本語連続音声認識エンジン"Julius"をAndroidで動作させる 3

Pocket

日本語連続音声認識エンジン"Julius"をAndroidで動作させるの連載3回目、最終回をお届けします。

第一回第二回 で、Juliusを用いたAndroidでオフライン音声認識を行うアプリのJNIライブラリが生成できました。最終回の今回は、JNIを利用するJava側の実装について解説します。動作イメージは以下のようになります。

JuliusForAndroid.png

今回も少し?濃いですが、出てくるのはJavaコードだけなので大丈夫!タブンね?

では、始めましょう!

AndroidでJulius用に音声を録音する 

Androidで音声を録音するためには、 MediaRecorder を用いる方法と AudioRecord を用いる方法の二種類が存在します。マイクから入力された音声を録音してファイル出力するだけならば、高レベルなAPIを提供するMediaRecorderを用いるほうが簡単です。出力ファイルのopen/closeやマイクから入力されたバイトデータのことなど、細かいことを気にしなくても使えますので。

ただし今回のアプリでは、まさにその「細かいこと」を気にしなければなりません。なぜなら、Juliusが理解できる音声ファイルは形式が厳密に定められた下記の2種類しかなく、そのサンプリングレートもコンフィグファイルで明示的に指定しなければならないからです。

-- The Juliusbook より--
サポートするファイル形式
 読み込み可能なファイル形式は,デフォルトでは以下のとおり.
  .wavファイル:Microsoft WAVE形式 WAVファイル(16bit, 無圧縮PCM, monoral のみ)
  ヘッダ無しRAWファイル:データ形式は signed short (16bit),Big Endian, monoral

そこで今回は、細かい条件を設定できるAudioRecordを用い、16bit・monoral・BigEndianのヘッダ無しRAWファイルとして音声ファイルを生成します。

なお MediaRecorder でも、MediaRecorder.OutputFormatとMediaRecorder.AudioEncoderをに"DEFAULT"を指定することで無圧縮PCMのwave形式ファイルを生成できるようです。ただこの使い方は公式にはUndocumentedのため、今回は利用を見送りました。

JuliusActivity

では、具体的なソースコードに入りましょう。今回は全ての処理をActivityに押し込めてしまいました。

onCreate

onCreateでは、AudioRecordの初期化とイベントリスナーの設定を行ないます。

まずAudioRecordを初期化します。
AudioRecord.getMinBufferSizeを用い、最小限のバッファサイズを計算します。ただし最小限ギリギリだとスムースな録音ができないようなので、適当に2倍の大きさを確保することにしています。
そのバッファサイズを使い、「16bit monoral サンプリングレート22050Hz」でAudioRecordを初期化します。ここで指定するサンプリングレートは、Juliusのコンフィグファイルに設定したサンプリングレートと一致させてください。また特定のサンプリングレートしか使えないデバイスもありますので、デバイスに合わせて適切に変更してください。

次にラジオボタンを選択したときのイベントリスナーを設定します。
選択されたラジオボタンが変更されたタイミングで、AsyncTaskであるJuliusInitializerがバックグラウンドで実行されます。詳細は後述します。

最後に、ボタンをタップしたときのイベントリスナーを設定します。
最下部のボタンをタップすると、onClickListenerというView.OnClickListener()を実装した匿名クラスのインスタンスに処理が委譲されます。この匿名クラスは、音声の録音と認識エンジンの呼び出しを担当しています。これも詳細は後述します。

JuliusInitializer

次に、JuliusInitializerを確認しましょう。この内部クラスでは、適切なコンフィグファイルでJuliusの初期化が行われます。

AsyncTaskの使い方は、 AsyncTaskのJavadoc などを参照してください。
肝になるのは、第二回で説明した「1. Julius初期化する」JNIメソッドinitJulius()を呼び出す部分です。Juliusのコンフィグファイルや音響モデル・言語モデルなどは、事前にSDCardなどの外部ストレージに配置されている前提です。この外部ストレージのパスはデバイスごとに異なるため、Environment.getExternalStorageDirectory()を用いてAndroidOS上での外部ストレージの絶対パスを取得しています。

onClickListener

onClickListenerでは、AudioRecordを用いた音声の録音と、録音完了後(isRecordingの認識エンジンの呼び出しを行ないます。

ややこしそうに見えますが、実際に行なっている処理はシンプルです。

  1. 録音していないときにボタンをタップされた場合
    1. 録音中フラグをONにする
    2. AudioRecordに録音開始を指示する
    3. 別スレッドを生成し、録音している音声データをAudioRecordから取り出してファイルへ出力する
  2. 録音中にボタンをタップされた場合
    1. 録音中フラグをOFFにする(録音している音声データをファイルへ出力する処理が終了する)
    2. 出力された音声ファイルを引数に、Juliusの音声認識処理を起動する

ただし以下の部分が要注意です。

AudioRecordから取り出した音声データは、調べたところリトルエンディアンでした。Juliusのヘッダ無しRAWファイルはビッグエンディアンを想定していますので、上位ビットと下位ビットをSWAPする必要があります。実はJavaのShortクラスには、reverseBytes()というまさにそのためのメソッドが準備されています。このメソッドを使って、エンディアンを調整しましょう。
(私もこのアプリを書いていて、始めてこのメソッドの存在を知りました。。。)

JuliusRecognizer

Juliusに非同期で音声認識処理を行わせるJuliusRecognizerは、JuliusInitializerとよく似ています。onPreExecuteでのダイアログの表示、onPostExecuteでのダイアログ消去は全く同じですので、コア部分だけ示します。

「2. 入力された音声を認識する」JNIメソッドrecognizeを呼び出しているだけですね。

Juliusからのコールバック

最後に、音声認識後にJuliusからコールバックされるメソッドです。
第二回の「3. 音声認識の結果を得る」でも説明しましたが、Juliusの音声認識処理終了後にJNIから呼ばれるコールバックメソッドを定義します。
JNIコードにて"void callback(byte[])"というシグネチャを持つメソッドを呼び出すと決め打ちしていますので、引数や戻り値だけでなく、メソッド名も変更してはなりません。

処理内容は実にシンプルで、Juliusから戻ってきたバイト配列をShift_JISとみなして文字列化し、resultStrに詰め込んで画面に表示しているだけです。

まとめ

三回に渡って、JuliusをAndroid上で動かし、オフライン音声認識を行うアプリについて解説しました。いかがだったでしょうか。

第一回 JuliusをAndroid ARM系CPU用にクロスコンパイルする方法
第二回 JuliusライブラリとAndroid JavaアプリをつなぐJNIの実装
第三回 Juliusを用いたAndroidでオフライン音声認識を行うアプリ

先のGoogle I/OにてAndroid4.1からオフライン音声認識が搭載されることが発表されたことからもわかるように、オフライン音声認識は大きな期待が寄せられている技術です。
(ただしGoogleオフライン音声認識は、またNexus 7のような「最初からAndroid4.1として作られているデバイス」でなければ動かない模様です。)

このアプリはサンプルの音響モデルと言語モデルを使っているため、一般的な言葉を認識させるには、Googleの大量データを背景にした認識精度にはかなわない面があります。ただしOSSで作られているこのアプリは、全てが手元にあります。特定の状況に特化した言語モデルや記述文法を準備すれば、その状況下では最善のアプリを作ることも可能となるでしょう。

完全なソースコードは、githubの Julius for Android にあります。ぜひforkして動作を確認し、自分だけのオフライン音声認識アプリを作ってみてください。

エンジニア採用中!私たちと一緒に働いてみませんか?