凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Juliusを実行ファイルに組み込む

はじめに

過去のエントリでは Julius をサーバモードとして動かして結果を取ってきていました。今回からは JuliusLib を利用して直接 Julius を突っ込んでみました。

環境

  • Ubuntu 10.04
  • gcc version 4.6.1 20110617 (prerelease) (GCC)

Juliusの用意

最新版(2012/02/26現在:Julius-4.2.1)を以下のページからダウンロードしてきます。

次に model が含まれる以下の記述文法音声認識実行キットを以下のページからダウンロードしてきます。

そして次のように配置&ファイルを作成します。

<DIR> juliuslib_test
 ├ <DIR> julius-4.2.1
 ├ <DIR> model (grammar-kit内のものを移動)
 ├ <DIR> gram
 │ └ kaden.*(記述文法ファイル、過去のエントリで使ったもの)
 ├ hmm_mono.jconf(grammar-kit内のものを移動)
 ├ Makefile(後述)
 └ test.cpp(後述)

ここまでのものは github にも上げてあります(後のエントリによって色々付け足されているかもしれませんが…)
hecomi/JuliuslibTest · GitHub
次に Julius をコンパイルします。

$ cd julius-4.2.1
$ ./configure --with-mictype=alsa
$ make

これで Julius の準備は完了です。

プログラム

Juliusにくっついているサンプルコード(Julius: julius-simple/julius-simple.c ソースファイル)を参考にします。ちょっと長いので、音声認識→結果表示に必要な部分だけ抽出してみました。

test.cpp
#include <iostream>
#include <julius/juliuslib.h>

int main(int argc, char* argv[])
{
	// Jconf: configuration parameters
	// load configurations from command arguments
	Jconf *jconf = j_config_load_args_new(argc, argv);
	if (jconf == NULL) {
		std::cout << "Error @ j_config_load_args_new" << std::endl;
		return -1;
	}

	// Recog: Top level instance for the whole recognition process
	// create recognition instance according to the jconf
	Recog *recog = j_create_instance_from_jconf(jconf);
	if (recog == NULL) {
		std::cout << "Error @ j_create_instance_from_jconf" << std::endl;
		return -1;
	}

	// Regster callback
	callback_add(recog, CALLBACK_EVENT_SPEECH_READY, [](Recog *recog, void*) {
		std::cout << "<<< PLEASE SPEAK! >>>" << std::endl;
	}, NULL);

	callback_add(recog, CALLBACK_EVENT_SPEECH_START, [](Recog *recog, void*) {
		std::cout << "...SPEECH START..." << std::endl;
	}, NULL);

	callback_add(recog, CALLBACK_RESULT, [](Recog *recog, void*) {
		for (const RecogProcess *r = recog->process_list; r; r = r->next) {
			WORD_INFO *winfo = r->lm->winfo;
			for (int n = 0; n < r->result.sentnum; ++n) {
				Sentence *s   = &(r->result.sent[n]);
				WORD_ID *seq = s->word;
				int seqnum   = s->word_num;
				for (int i = 0; i < seqnum; ++i) {
					std::cout << winfo->woutput[seq[i]];
				}
			}
		}
	}, NULL);

	// Initialize audio input
	if (j_adin_init(recog) == FALSE) {
		return -1;
	}

	// output system information to log
	j_recog_info(recog);

	// Open input stream and recognize
	switch (j_open_stream(recog, NULL)) {
		case  0: break; // success
		case -1: std::cout << "Error in input stream" << std::endl; return -1;
		case -2: std::cout << "Failed to begin input stream" << std::endl; return -1;
	}

	// Recognition loop
	int ret = j_recognize_stream(recog);
	if (ret == -1) return -1;

	// exit
	j_close_stream(recog);
	j_recog_free(recog);

	return 0;
}
Makefile

julius-simple.c 付属のものをちょっといじりました。

LIBSENT=./julius-4.2.1/libsent
LIBJULIUS=./julius-4.2.1/libjulius

CC=g++-4.6
CFLAGS=-g -O2 -std=c++0x

CPPFLAGS=-I$(LIBJULIUS)/include -I$(LIBSENT)/include  `$(LIBSENT)/libsent-config --cflags` `$(LIBJULIUS)/libjulius-config --cflags`
LDFLAGS= -L$(LIBJULIUS) `$(LIBJULIUS)/libjulius-config --libs` -L$(LIBSENT) `$(LIBSENT)/libsent-config --libs`

############################################################

all: test

test: test.cpp
	$(CC) $(CFLAGS) $(CPPFLAGS) -o test test.cpp $(LDFLAGS)

clean:
	$(RM) *.o *.bak *~ core TAGS

distclean:
	$(RM) *.o *.bak *~ core TAGS
	$(RM) test
コンパイルと実行
$ make
$ ./test -C hmm_mono.jconf -gram gram/kaden -input mic 
結果
(Juliusからの出力)
<<< PLEASE SPEAK! >>>
...SPEECH START...
<s>電気をつけて<s>
<<< PLEASE SPEAK! >>>
(以下繰り返し)

解説

まず、Jconf と Recog を作ります。

Jconf *jconf = j_config_load_args_new(argc, argv);
Recog *recog = j_create_instance_from_jconf(jconf);

Jconf にはコマンドラインから与えられた設定パラメータが格納されます(Julius: 構造体 Jconf)。
Recog は Jconf を元にして認識全体にわたって使われるインスタンスとなります(Julius: 構造体 Recog)。
次に、コールバックを登録します。

callback_add(recog, CALLBACK_EVENT_SPEECH_READY, [](Recog *recog, void*) {
	std::cout << "<<< PLEASE SPEAK! >>>" << std::endl;
}, NULL);

callback_add(recog, CALLBACK_EVENT_SPEECH_START, [](Recog *recog, void*) {
	std::cout << "...SPEECH START..." << std::endl;
}, NULL);

callback_add(recog, CALLBACK_RESULT, [](Recog *recog, void*) {
	for (const RecogProcess *r = recog->process_list; r; r = r->next) {
		WORD_INFO *winfo = r->lm->winfo;
		for (int n = 0; n < r->result.sentnum; ++n) {
			Sentence *s   = &(r->result.sent[n]);
			WORD_ID *seq = s->word;
			int seqnum   = s->word_num;
			for (int i = 0; i < seqnum; ++i) {
				std::cout << winfo->woutput[seq[i]];
			}
		}
	}
}, NULL);

ここでは、音声認識待機状態に入った時、発話が始まった時、音声認識結果が帰ってきた時の3つを登録しています。他に使えるコールバックに関してはJulius: libjulius/include/julius/callback.hを参考にして下さい。
CALLBACK_RESULT内のデータの解析に関しては、もう少し色々試した後、別途紹介エントリを書きたいと思います。
(2012/02/28 書きました Juliusで認識結果の取り出し方を調べてみた - 凹みTips
そしてストリームを開きます。

switch (j_open_stream(recog, NULL)) {
	case  0: break; // success
	case -1: std::cout << "Error in input stream" << std::endl; return -1;
	case -2: std::cout << "Failed to begin input stream" << std::endl; return -1;
}

参考:Julius: Basic API
で、j_recognize_stream で開いたストリームを繰り返し解析します。

int ret = j_recognize_stream(recog);
if (ret == -1) return -1;

参考:Julius: Basic API
終わった後は後片付けをして終了します。

j_close_stream(recog);
j_recog_free(recog);

ざっくりこんな感じです。

今後の展望

コールバックを使えばサーバモードの時には出来なかった、コマンド発話(hogehoge) → 音声によるトークバック → キャンセル(あっ、やっぱりmogemoge)→ mogemoge 実行といった、MMDAgent みたいな挙動も出来ると思います。がんがります。
【MMDAgent】初音ミクとおしゃべりできるソフトをつくってみた ‐ ニコニコ動画:GINZA