OpenJTalkでwavファイルを作らずに直接再生する

OpenJTalk (http://open-jtalk.sourceforge.net/) はオープンソースの日本語TTSです。

OpenJTalkに含まれるアプリケーション(open_jtalk)は合成音声をwavファイルとして出力するのですが、
この方法だとアプリケーションに組み込んで使用するには向きません。
(10/30 追記:WindowsではHTS engineに再生機能が組み込まれていてそのまま鳴るのですね。)
そこで、アプリ組み込みのステップとして
合成音声をファイルに出力せずに直接サウンドバイスから鳴らすアプリを書いてみました。

ところが、OpenJTalkで使用している音声合成エンジンであるHTSのAPIでは
ファイルに出力する以外のデータ取得方法がないようなので、
HTSにAPIを追加するパッチを当てて使用しています。

アプリのコードおよびHTSのパッチはこちらに置いてあります。
https://github.com/penkoba/open_jtalk-app
コンパイル手順、使い方はREADMEをご覧ください。

アプリケーションはALSAを叩いているので、Linuxで動作します。
そして、モノラル音が再生できる必要があります。
今時はPulseAudioやESDなどのオーディオフレームワークが入ってるのであまり気にしなくていいと思いますが、
ハードウェアを直接叩いている場合はモノラル再生に対応できない場合があります。

解説

モチベーションは、すでに書いたとおりOpenJTalkを自分のアプリケーションに組み込んでみたい、
そのためにはバッファに音声データをもらって直接音を鳴らしたいというところです。

まずOpenJTalkのアプリケーションのソースコードを見てみると、
OpenJTalkでやっているのは主に入力テキストの解析という前処理で、
音声合成本体はHTSというエンジンを使っているようです。
wavファイルへの出力もHTSのAPI(HTS_Engine_save_riff())が行っています。

ところが、合成データの出力APIはこの他に HTS_Engine_save_generated_speech() という
これまたファイル出力API(こちらはwavでなくraw PCMを吐き出す)しか見当たりません。

ということは、アプリ内のバッファに受け取るにはHTSに手を入れる必要がありそうです。

幸い、HTS_Engine_save_generated_speech() の実装を見てみると非常に単純で
HTS_GStreamSet_get_speech()という関数で1サンプルずつ取得してファイルに吐き出す処理をしているだけです。(すごい無駄なんですけど...)
これを参考に、バッファへの出力APIを追加することにします。

以下の2つのAPIを実装しました。

  • HTS_Engine_get_generated_speech_size(): 生成した音声のサンプル数
  • HTS_Engine_get_generated_speech(): 生成した音声をバッファに取得
/* HTS_Engine_get_generated_speech_size: obtain generated speech size */
unsigned int HTS_Engine_get_generated_speech_size(HTS_Engine * engine) 
{
   return HTS_GStreamSet_get_total_nsample(&engine->gss);
}

/* HTS_Engine_get_generated_speech: obtain generated speech */
void HTS_Engine_get_generated_speech(HTS_Engine * engine, short * buf)
{
   int i;  
   HTS_GStreamSet *gss = &engine->gss;

   for (i = 0; i < HTS_GStreamSet_get_total_nsample(gss); i++) {
      buf[i] = HTS_GStreamSet_get_speech(gss, i);
   }
}

これを使って、アプリケーション側はこんな感じで音声を取得します。

pcm_len = HTS_Engine_get_generated_speech_size(&app->engine);
app->pcm = malloc(pcm_len * sizeof(short));
HTS_Engine_get_generated_speech(&app->engine, app->pcm);

取得した音声の出力は手持ちのサウンドライブラリを使いました。
こんな感じです。

play_write(app->play_h, app->pcm, pcm_len * sizeof(short));

これで、ファイルを経由せずに合成音声を直接出力するアプリケーションが出来上がりました。

ところで、合成音声のクオリティがイマイチなのですがそのあたりのチューニングノウハウをお持ちの方がいたらご連絡ください。