JuliusLibを使った音声認識の組み込みアプリケーション開発（for Mac）


#はじめに
以前，音声認識エンジンJuliusのデモの動かし方を書きましたので，
今回は実際に音声認識を用いたアプリケーションの開発を始めたいと思います．
以前の記事：https://qiita.com/sgr0416st/items/928fb2bde28e17eda274

また，とても分かりやすい記事があったのでこちらを大変参考に（ほぼ写し）させていただきました．
http://tips.hecomi.com/entry/20120226/1330260359









#ゴール
・自作のC++アプリケーションに音声認識を組み込む方法を理解する
・エンターを押したら停止するアプリケーションを作る





#事前準備
・ダウンロードがまだの方は以下からJulius,dictation-kitのダウンロード
https://ja.osdn.net/projects/julius/
（今回はjulius4.4.2.1，dictation-kit-v4.4を使用）

・Juliusのインストールがまだの方は以前の記事を参照
https://qiita.com/sgr0416st/items/928fb2bde28e17eda274




##開発
開発に当たって，まずディレクトリを整理します．
とりあえず作業ディレクトリを以下のようにしました．

> <DIR> (作業ディレクトリ)
　├ <DIR> libsent（Julius4.4.2.1に入ってるやつを持ってくる）
　├ <DIR> libjulius（Julius4.4.2.1に入ってるやつを持ってくる）
　├ <DIR> model （以下dictation-kitに入ってるやつを持ってくる）
　├ am-gmm.jconf  → GMM-HMM音響モデルを使う場合
　├ am-dnn.jconf  → DNN-HMM音響モデルを使う場合
　├ julius.dnnconf  → DNN-HMM音響モデルを使う場合
　└ main.jconf


次に，dictation-kit 内にある，JuliusLibを使ったサンプルコードを眺めます．
(http://quruli.ivory.ne.jp/document/julius_4.2/julius-simple_8c_source.html)

...が，長くて少々解読しづらいです．
そこで，少し整理されたコードを用意します．
参考：http://tips.hecomi.com/entry/20120226/1330260359

```cpp:sample.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:makefile
LIBSENT=./libsent
LIBJULIUS=./libjulius

CC=g++
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: sample

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

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

distclean:
	$(RM) *.o *.bak *~ core TAGS
	$(RM) sample
```

この２つのファイルを用意して、それぞれ作業ディレクトリに入れましょう。

その後、ターミナルで作業ディレクトリへ移動し、

```
make
./sample -C main.jconf -C am-dnn.jconf -dnnconf julius.dnnconf
```
と実行してみましょう。

これで、用意したsample.cppが実行されます。
あとは、このsample.cppを使いたい様に書き換えて、make して実行するだけです！

※追記
以下のようなエラーが出た方は、　Juliusのインストールをしていないまま「libjulius」「licsent」フォルダを持ってきてしまった方だと思います。

```
./libjulius/include/julius/julius.h:33:10: fatal error: 'julius/config.h' file
      not found
#include <julius/config.h>
         ^~~~~~~~~~~~~~~~~
1 error generated.
```
[このサイト](https://qiita.com/sgr0416st/items/928fb2bde28e17eda274)を参考にJuliusをインストールし、インストール済みの(makeをした)Juliusフォルダの中にある「libjulius」「licsent」をもう一度持ってきてみてください。

#コード解説
とりあえず動けば良いという方はこちらを読み飛ばしてください。
それではざっとコードを見てみましょう。

```
Jconf *jconf = j_config_load_args_new(argc, argv);
```
最初の方にあるこのコードは、コマンド引数からjconfを読み込みます。
jconfファイルは様々な設定をまとめたテキストファイルです。

```
Recog *recog = j_create_instance_from_jconf(jconf);
```
次にjconfファイルに従って，新たなエンジンインスタンスを起動・生成します。

```
callback_add(recog, CALLBACK_〇〇〇〇, [](Recog *recog, void*) {
        {処理内容}
    }, NULL);
```
ここで、必要なコールバック関数の登録をします。具体的には
・CALLBACK_EVENT_SPEECH_READY:発話検出ができるようになるタイミング
・CALLBACK_EVENT_SPEECH_START：発話を検出したタイミング
・CALLBACK_RESULT：認識が終わったタイミング
の３つでどの様な処理をするかそれぞれ決定しています。
つまり、認識結果を使いたい時は、
**CALLBACK_RESULT　のコールバック関数で処理をはさみましょう**。

その後初期化設定等をして、

```
 // Recognition loop
    int ret = j_recognize_stream(recog);
    if (ret == -1) return -1;
```
上の部分で認識ループをしています。
ちなみに、juliusはデフォルトではコールバックから復帰すると自動で再び認識を始めるようになっています。

キーを押して終了する１つの方法として、スレッドを使うことが考えられます。
例えば、上の部分を以下の様に書き換えてみてください。

```
// Recognition loop
boost::thread thr([&]() {
	int ret = j_recognize_stream(recog);
	if (ret == -1) return -1;
});

// Exit if Enter key pressed
std::string line;
getline(std::cin, line);

j_close_stream(recog);
thr.join();
j_recog_free(recog);
```

これでキーを押したら停止するようになりました！

##参考URL
Juliusを実行ファイルに組み込む
http://tips.hecomi.com/entry/20120226/1330260359