第11章 プラグイン

目次

動作環境
使用方法
プログラミング例
例1:認識結果出力プラグイン
例2:音声入力プラグイン
例3:認識開始・終了の検知プラグイン
例4:オプションを拡張する
プラグインの仕様
音声入力プラグイン
音声後処理プラグイン
特徴量入力プラグイン
特徴量後処理プラグイン
ガウス分布計算プラグイン
結果取得プラグイン
その他のプラグイン関数
複合プラグインについて
制限

Julius-4.1 以降では,プラグインにより Julius に様々な機能や処理を追加することができる.プラグインは,関数を定義する共有オブジェクト(あるいはDLL)ファイルであり,Julius実行時に動的に組み込まれる.音声入力デバイスを追加するプラグインや,入力信号の後処理プラグイン結果出力のプラグインなどが作成できる.本章ではプラグインの動作環境,使用方法,および作成方法について,例を交えながら概要を述べる.

なお,サンプルのプラグインのソースコードが Julius のアーカイブの plugins 以下にある.各関数の具体的な仕様をソース上にコメントとして詳細に記述してあるので,各関数の詳細についてはそちらを参照されたい.

動作環境

dlopen()関数をサポートする環境,および Windows にて動作する.dlopen() は Linux, Solaris など主要なOSでサポートされている.

使用方法

Julius用プラグインのファイル拡張子は ".jpi" である.プラグインを使用するには,オプション "-plugindir dirlist" を指定する. dirlistには,使用したい .jpi ファイルの置いてあるディレクトリを指定する(そのディレクトリにあるすべての jpi ファイルが読み込まれる).なお,ディレクトリが複数ある場合, dirlistにコロンで区切って並べて指定することができる.

なお,音声入力プラグインや特徴量入力プラグインなどは,他の起動時オプション ("-input" 等) を拡張する.このため,この "-plugindir" オプションはできるだけ最初のほうで指定するのがよい.

プログラミング例

C 言語によるプラグインの簡単なプログラム例,およびコンパイルの方法を以下に示す.

例1:認識結果出力プラグイン

この最も簡単なプラグインは,認識が完了するたびに一位の認識候補の文字列を標準出力に出力する.また, 認識失敗や入力棄却の場合は [failed] と出力する.

例 11.1. test1.c: a simple output plugin

#include <stdio.h>
#include <string.h>
int
get_plugin_info(int opcode, char *buf, int buflen)
{
  switch(opcode) {
  case 0:
    strncpy(buf, "simple output plugin", buflen);
    break;
  }
  return 0;
}
void
result_best_str(char *result_str)
{
  if (result_str == NULL) {
    printf("\t[failed]\n");
  } else {
    printf("\t[%s]\n", result_str);
  }
}


ここでは2つの関数を外部参照可能なように定義している. get_plugin_info は,このプラグインに関する情報を返す関数である.これは,プラグインごとに必ず定義する必要がある. result_best_str が処理の本体である.この名前の関数がプラグイン内で定義されていると,Julius は,認識結果が得られるたびにこの関数を呼びだす.result_best_str に対しては,引数として認識結果の文字列(認識失敗時,入力棄却時は NULL)が与えられる.

Julius用プラグインファイル (.jpi) の実体は,共有オブジェクトである. 例えば gcc では,以下のようにコンパイルすることができる.

% gcc -shared -o test.jpi test.c

使用するには,前述のように,Julius 起動時にオプション -plugindir でそのプラグインの置いてあるディレクトリを指定する.例えばカレントディレクトリにある場合は以下のようにする. -C ... 以降は通常の起動と同じである.

% julius -plugindir . -C ...

上記を実行すれば,認識結果が出るたびに上記のresult_best_str が呼ばれ,認識結果が出力されることが確かめられる.

例2:音声入力プラグイン

2つ目の例として,音声入力を拡張するプラグインを解説する. 音声入力プラグインで定義するのは以下の関数である.

  • get_plugin_info(): プラグインの情報を返す

  • adin_get_optname(): "-input" オプション用文字列を返す

  • adin_get_configuration(): このプラグインのプロパティを返す

  • adin_standby(): 音声入力デバイスを準備する

  • adin_open(): 音声入力デバイスを開く

  • adin_read(): デバイスから音声データを読み込む

  • adin_close(): 音声入力デバイスを閉じる

adin_get_optname() は,このプラグインを使用して音声入力を行う際に Julius の起動時オプション -input で指定すべき文字列を指定する.

adin_get_configuration()は,この音声入力プラグイン使用時にJulius がどう音声入力をハンドルすべきかを返す.リアルタイム認識を行うか,無音区間検出を行うか,音声入力部をスレッド化すべきかなどの情報を返す.

adin_standby() は,起動時に一回呼ばれるので, デバイスのチェックなどをここで行える. adin_open()でデバイスを開き, adin_read()でバッファにあるデータを読み込み, adin_close()でデバイスを閉じる.

ヘッダ "plugin_defs.h" は julius のアーカイブの plugins ディレクトリ以下にあり, booleanなどいくつかの必要な定義が含まれている.音声入力プラグイン用のいくつかの関数で,返り値や引数に用いているのでインクルードする必要がある.

Linux で OSS API を使用してマイクから音声入力を行うプラグインの例を以下に示す. [10]

例 11.2. test2.c: OSS A/D-in plugin

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/soundcard.h>
#include "plugin_defs.h"

static int audio_fd;        /* audio file descpritor */
static int freq;            /* given sampling frequency */

/* return plugin information */
int
get_plugin_info(int opcode, char *buf, int buflen)
{
  switch(opcode) {
  case 0:
    strncpy(buf, "OSS adin plugin", buflen);
    break;
  }
  return 0;
}

/* return argument string for "-input" */
void
adin_get_optname(char *buf, int buflen)
{
  strncpy(buf, "myadin", buflen);
}

/* return property of this adin input */
int
adin_get_configuration(int opcode)
{
  switch(opcode) {
  case 0:	/* enable real-time processing of 1st pass by default? */
    return 1;   /* yes */
  case 1:       /* enable frontend voice detection by level/zc by default? */
    return 1;   /* yes */
  case 2:       /* input module threading is needed or not, if supported? */
    return 1;   /* yes, needed*/
  }
}

/* standby, will be called once at startup */
boolean
adin_standby(int sfreq, void *dummy)
{
  freq = sfreq;  /* just store required sampling frequency to local */
  return TRUE;
}

/* open device */
boolean
adin_open(char *pathname)
{
  int fmt;
  int stereo;
  int ret;
  int s;

  if ((audio_fd = open(pathname ? pathname : "/dev/dsp", O_RDONLY)) == -1) {
    printf("Error: cannot open %s\n", pathname ? pathname : "/dev/dsp");
    return FALSE;
  }
  fmt = AFMT_S16_LE;               /* 16bit signed (little endian) */
  if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &fmt) == -1) {
    printf("Error: failed set format to 16bit signed\n");
    return FALSE;
  }
  stereo = 0;			/* mono */
  ret = ioctl(audio_fd, SNDCTL_DSP_STEREO, &stereo);
  if (ret == -1 || stereo != 0) {
    stereo = 1;
    ret = ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &stereo);
    if (ret == -1 || stereo != 1) {
      printf("Error: failed to set monoral channel\n");
      return FALSE;
    }
  }
  s = freq;
  if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &s) == -1) {
    printf("Erorr: failed to set sample rate to %dHz\n", freq);
    return FALSE;
  }

  return(TRUE);
}

/* read samples */
int
adin_read(SP16 *buf, int sampnum)
{
  audio_buf_info info;
  int size, cnt;

  /* get sample num that can be read without blocking */
  if (ioctl(audio_fd, SNDCTL_DSP_GETISPACE, &info) == -1) {
    printf("Error: adin_oss: failed to get number of samples in the buffer\n");
    return(ADIN_ERROR);
  }
  /* get them as much as possible */
  size = sampnum * sizeof(SP16);
  if (size > info.bytes) size = info.bytes;
  cnt = read(audio_fd, buf, size);
  if ( cnt < 0 ) {
    printf("Error: adin_oss: failed to read samples\n");
    return (ADIN_ERROR);
  }
  cnt /= sizeof(short);
  return(cnt);
}

/* close device */
boolean
adin_close()
{
  close(audio_fd);
  return TRUE;
}
/* end of program */

コンパイルは例1と同様に行う.plugin_defs.h を Julius の plugins ディレクトリからカレントにコピーしておくこと.

% gcc -shared -o test2.jpi test2.c

使用するには,このプラグインをロードした上で,起動時に "-input" でこの音声入力プラグインを選択する.この例では関数 adin_get_optname は "myadin" をバッファに格納して返しているので,以下のようにすればこの音声入力プラグインを使った音声入力が行える.

% julius -plugindir . -C ... -input myadin

例3:認識開始・終了の検知プラグイン

3つ目の例は,認識処理の開始・終了時に何らかの処理を挟み込むプラグインである. これは,Julius のコアライブラリである JuliusLib のコールバック機能を使うことで実現できる.

プログラム例を以下に示す.コールバック機能を使う場合,JuliusLib ライブラリとリンクする必要があるため,julius/juliuslib.hをインクルードしている.

例 11.3. test3.c: Start / stop notification plugin

#include <julius/juliuslib.h>
int
get_plugin_info(int opcode, char *buf, int buflen)
{
  switch(opcode) {
  case 0:
    strncpy(buf, "start/stop notify plugin", buflen);
    break;
  }
  return 0;
}
static void
func_begin(Recog *recog, void *dummy)
{
  printf("[BEGIN]\n");
}
static void
func_end(Recog *recog, void *dummy)
{
  printf("[END]\n");
}
int
startup(void *data)
{
  Recog *recog = data;
  callback_add(recog, CALLBACK_EVENT_RECOGNITION_BEGIN, func_begin, NULL);
  callback_add(recog, CALLBACK_EVENT_RECOGNITION_END, func_end, NULL);
  return 0;
}

これまでの例と同様に, get_plugin_info は必ず定義する必要がある. 2つのローカルな関数 func_beginfunc_endは,コールバックに登録する関数であり,認識処理中にそれぞれ音声認識処理の開始・終了のタイミングで呼び出される.

startup という名前の関数がプラグインにおいて定義・ エクスポートされているとき,その関数はJuliusの起動が完了した時点で1回だけ呼ばれる.呼び出しの際,引数としてエンジンインスタンス recogが与えられるので,ここではそのエンジンに対して上記のコールバックを登録している.

本プラグインは JuliusLib を使用している.JuliusLibを用いたコンパイルは以下のように行う(システムに Julius がインストールされている場合).

% gcc -shared -o test3.jpi `libjulius-config --cflags` `libsent-config --cflags` \
 test3.c `libjulius-config --libs` `libsent-config --libs`

システム上のものではなく,バイナリパッケージ内になるライブラリと直接リンクすることもできる.この場合は以下のようにする. なお,パッケージの展開ディレクトリを $JDIR とする.

% gcc -shared -o test3.jpi -I$JDIR/libjulius/include -I$JDIR/libsent/include \
 `$JDIR/bin/libjulius-config --cflags` `$JDIR/bin/libsent-config --cflags` \
 test3.c \
 -L$JDIR/libjulius `libjulius-config --libs` -L$JDIR/libsent `libsent-config --libs`

このようにして,JuliusLib のコールバック機能をプラグインから利用できる. コールバックの一覧や使い方,引数の説明については, JuliusLib の章を参照のこと. なお,例2で使用したヘッダ "plugin_defs.h" の内容は "julius/juliuslib.h" に含まれているので,JuliusLib を使用する際は "plugin_defs.h" はインクルードしなくてよい.

例4:オプションを拡張する

Julius 起動時に特定のプラグインに対してパラメータを与えたい場合, 以下の要領でプラグイン用の起動時オプションを追加することができる.前節の test3.c を,起動時オプション "-notify" をつけたときのみ有効にするよう拡張したプログラム test4.c を以下に示す.なお, test3.c からは,関数 opt_notify, initialize が追加され,startupが変更されている.

例 11.4. test4.c: extend test3.c by adding an option "-notify"

#include <julius/juliuslib.h>
static int notify_flag = 0;

int
get_plugin_info(int opcode, char *buf, int buflen)
{
  switch(opcode) {
  case 0:
    strncpy(buf, "start/stop notify plugin with option -notify", buflen);
    break;
  }
  return 0;
}
static void
func_begin(Recog *recog, void *dummy)
{
  printf("[BEGIN]\n");
}
static void
func_end(Recog *recog, void *dummy)
{
  printf("[END]\n");
}

static boolean
opt_notify(Jconf *jconf, char *arg[], int argnum)
{
  notify_flag = 1;
  return TRUE;
}
int
initialize()
{
  j_add_option("-notify", 0, 0, "enable notify extension", opt_notify);
  return 0;
}

int
startup(void *data)
{
  Recog *recog = data;
  if (notify_flag == 1) {
    callback_add(recog, CALLBACK_EVENT_RECOGNITION_BEGIN, func_begin, NULL);
    callback_add(recog, CALLBACK_EVENT_RECOGNITION_END, func_end, NULL);
  }
  return 0;
}


関数 initialize が定義されている場合,それはプラグインが読み込まれた直後に実行される.initialize 関数内で呼び出しているj_add_option はJuliusLibの関数であり,Julius にオプションを追加するものである.ここではオプション "-notify" が指定されたとき opt_notify 関数を呼び出すよう登録している.このプログラムでは,オプションが指定されたかどうかをstaticなグローバル変数 notify_flag に保存しておき, startup 内でその値が 1 のときにのみコールバックを登録するようにしている.

このtest4.cをコンパイル後,test3.jpi を削除して以下を実行し,オプション "-notify" をつける場合とつけない場合でどうなるか,確かめるとよい.

% julius -plugindir . -C ...  -notify

プラグインの仕様

現時点の Julius において,定義できる関数はおおまかに以下の種類に分類できる.

  • 音声入力プラグイン

  • 音声後処理プラグイン

  • 特徴量入力プラグイン

  • 特徴量後処理プラグイン

  • ガウス分布計算プラグイン

  • 結果取得プラグイン

  • 初期化・処理開始時など汎用関数

以下にプラグインごとの概要および定義すべきエクスポート関数の一覧を示す. なお,関数の型や引数の意味など,各関数の具体的な仕様については,julius のソースアーカイブの plugin ディレクトリ以下にあるプラグインのサンプルプログラムを参照のこと.

音声入力プラグイン

音声入力プラグインは,Julius の音声入力デバイスを拡張する.あるデバイス用のプラグインを作成すれば,Juliusはそのデバイスから音声を直接取り込んで認識することができる.

このプラグインは,Julius のオプション -input を拡張する.-input につづけてプラグイン内の adin_get_optname 関数で返す文字列を指定することで, Julius はこのプラグインを音声入力ハンドラとして選択し,このプラグイン関数を使って音声の取り込みを行う.

音声入力プラグインで定義すべきエクスポート関数は以下の通り.

  - int get_plugin_info(int opcode, char *buf, int buflen)
  - int adin_get_optname(char *buf, int buflen)
  - int adin_get_configuration(int opcode)
  - boolean adin_standby(int sfreq, void *dummy)
  - boolean adin_open(char *pathname)
  - int adin_read(SP16 *buf, int sampnum)
  - boolean adin_close()
任意(省略可能):
  - boolean adin_terminate()
  - boolean adin_pause()
  - boolaen adin_resume()

音声後処理プラグイン

音声の後処理プラグインは,Julius が取り込んだ音声信号に対して後処理を行えるプラグインである.このプラグインは,音声信号を取り込んだ直後,特徴量抽出や認識処理などの処理を行う前に呼び出される.このプラグインを使うことで,例えば入力音声信号をモニタしたり,信号に対して認識処理の前段で何らかの処理を加えることができる.呼び出す対象としては,音声区間検出前を含むすべての音声入力か,あるいは振幅と零交差数によってトリガした音声入力区間のみかを選択できる.

音声の後処理プラグインで定義すべきエクスポート関数は以下の通り.

  - get_plugin_info()
  - adin_postprocess() または adin_postprocess_triggered()

特徴量入力プラグイン

特徴量入力プラグインは,特徴量入力を拡張するプラグインである.これを用いることで,Julius に対して外部から特徴量ベクトル列を与えることができる. 与えられた特徴量ベクトルはフレーム単位で逐次処理され,リアルタイム認識が可能である.

このプラグインはオプション -input を拡張する. -input につづけて fvin_get_optname で返される文字列を指定することで, Julius はこのプラグインを特徴量の入力ハンドラとして用いて認識を行う.

特徴量入力プラグインで定義すべきエクスポート関数は以下の通り.

  - get_plugin_info()
  - fvin_get_optname()
  - fvin_get_configuration()
  - fvin_standby()
  - fvin_open()
  - fvin_read()
  - fvin_close()
任意(省略可能):
  - fvin_terminate()
  - fvin_pause()
  - fvin_resume()

なお,特徴量入力プラグインは,特徴量が一種類の場合のみ動作する. 複数音響モデルを使用した認識で,各モデルが2つ以上の異なる特徴量を用いている場合,特徴量入力プラグインは正しく動作しない.

特徴量後処理プラグイン

特徴量後処理プラグインは,Julius が抽出した(あるいは外部から与えられた) 特徴量ベクトルに対して後処理を行えるプラグインである.このプラグインは, 入力フレームごとに,特徴量を算出した後認識を行う直前に呼ばれる. このプラグインを使うことで,特徴量ベクトルを外部へ取り出したり, 認識前に加工を加えることができる.

特徴量後処理プラグインで定義すべきエクスポート関数は以下の通り.

  - get_plugin_info()
  - fvin_postprocess()

なお,特徴量後処理プラグインは,特徴量が一種類の場合のみ動作する. 複数音響モデルを使用した認識で,各モデルが2つ以上の異なる特徴量を用いている場合,このプラグインは正しく動作しない.

ガウス分布計算プラグイン

ガウス分布計算プラグインは,音響モデルの出力確率(尤度)計算を行うプラグインである.使用時,この関数は認識処理においてデコーダ内部関数の代わりに呼ばれる.これを使うことで,Julius の音響尤度計算部分を任意のものに差し替えることができる.呼び出し時にはガウス分布集合と入力ベクトルが与えられるので,各ガウス分布における入力ベクトルの出力確率をそれぞれ計算して格納して返すよう作成する.

このプラグインはオプション -gprune を拡張する.これに calcmix_get_optname で返される文字列を指定することで, Julius はこのプラグインをガウス分布計算モジュールとして用いる.

このガウス分布計算プラグインで定義すべきエクスポート関数は以下の通り.

  - get_plugin_info()
  - calcmix_get_optname()
  - calcmix()
  - calcmix_free()
  - calcmix_init()

結果取得プラグイン

以下の関数は,認識結果の取得に使えるプラグイン関数である.入力の認識が終わるたびに呼び出され,入力に対する認識結果(最も確率の高い候補)の文字列が渡される.複数モデル認識の場合は,全ての認識処理での結果の中で最もスコアの高いものが渡される.

  - result_best_str()

このプラグインに渡される認識結果はテキスト(単語列)のみである. 時間のアラインメントや競合候補などの詳細な結果を取得するには, コールバック関数を使用する.

その他のプラグイン関数

以下の関数は,定義しておけばそれぞれのタイミングで呼び出される汎用関数である.プラグインの初期化やコールバックの設定などに用いることができる.

  - initialize()
  - startup()

複合プラグインについて

一つのプラグインに異なる種類のプラグイン関数を書くことで,単一のプラグインに複数の機能を持たせることができる.例えば,音声入力プラグインの関数と結果取得プラグインの関数の両方を定義すれば,入力と出力を一度に拡張するプラグインを作成することができる.

制限

特徴量に関するプラグインは,複数の種類の特徴量を用いた同時認識の場合, 動作しないことがある.音響モデルが1つであるか,または複数の音響モデルが同一の特徴量を使う場合,特徴量に関するプラグインは動作する.しかし, 複数の音響モデルがそれぞれ異なる特徴量を使用する条件では正しく動作しない.

Julius のプラグイン機能全体を無効化したい場合は,コンパイル時の configure オプションで--disable-plugin を指定する.



[10] OSS API での入力は Julius にすでに実装されている. ここに示すのは等価なプラグイン版である.