Pocketsphinx API 核心理念
Pocketsphinx API 被设计是为了减轻编写语音识别功能应用程序。
- 由于使用抽象类,所以在源代码和二进制文件兼容方面,更能保持稳定。
- 因为它完全可重入,所以在同一进程中拥有多个编码器也不会出现问题。
- 在运行时,新的语言模型的接口(在sphinxbase)支持线性多模型插值。
- 它能大幅度减少代码量而且能明显减少内存消耗。
相关文档见:http://cmusphinx.sourceforge.net/api/pocketsphinx/
基本用法(hello world)
你需要知道几个关键的点如何使用应用程序接口(API):
- 命令行通过外部<cmd_ln.h>来解析;
- 一切都需要一个ps_decoder_t*作为第一个参数。
为了说明新的接口,我们将通过一个简单的“hello world”的例子。这个例子为unix源文件和汇编程序。我们将创建一个叫hello_ps.c的C源文件。我们使用如下命令来编译:
gcc -o hello_ps hello_ps.c \
-DMODELDIR=\"`pkg-config --variable=modeldir pocketsphinx`\"
`pkg-config --cflags --libs pocketsphinx sphinxbase`
请注意,编译错误在这里意味着你没有仔细阅读教程和不遵循上述安装指南。例如pocketsphinx需要通过pkg-config系统来正确安装。为了检查pocketsphinx正确安装与否,只要在命令行运行 pkg-config --cflags --libs pocketsphinx sphinxbase 就可以从输出结果看出。
-I/usr/local/include/sphinxbase -I/usr/local/include/pocketsphinx
-L/usr/local/lib -lpocketsphinx -lsphinxbase –lsphinxad
pkg-config --variable=modeldir pocketsphinx
显示结果输出:/usr/local/share/pocketsphinx/model
补充:gcc -o recog recog.c \-DMODELDIR=\"/usr/local/share/pocketsphinx/model\" -I/usr/local/include/sphinxbase -I/usr/local/include/pocketsphinx -L/usr/local/lib -lpocketsphinx -lsphinxbase -lsphinxad
初始化
首先我们需要做的是创建一个配置对象,由于某种原因叫作cmd_ln_t。我们将按如下程序这样做:
#include <pocketsphinx.h>
int main(int argc, char *argv[])
{
ps_decoder_t *ps;
cmd_ln_t *config;
config = cmd_ln_init(NULL, ps_args(), TRUE,
"-hmm",MODELDIR "/hmm/en_US/ hub4wsj_sc_8k",
"-lm", MODELDIR "/lm/en/turtle.DMP",
"-dict", MODELDIR "/lm/en/turtle.dic",
NULL);
if (config == NULL)
return 1;
return 0;
}
该cmd_ln_init()功能采用可变数目的空结束NULL的字符串参数。第一个参数是之前要更新的cmd_ln_t *。第二参数是一个数组参数定义—通过调用ps_args()来进行设置。第三个参数是一个标志,告诉参数解析器将是“严格”的—如果这是真的,然后重复的参数或未知参数会导致解析失败。
该MODELDIR宏定义是在GCC命令行中使用pkg-config从PocketSphinx配置中获得modeldir变量。在windows系统中,你可以简单的增加一个预处理宏定义到代码中,像这样:
#define MODELDIR "c:/sphinx/model"
(无论你的模型安装在哪个目录下),现在,为了初始化解码器,使用ps_init如下:
ps = ps_init(config);
if (ps == NULL)
return 1;
解码文件流
现在现场音频输入受到一些特定平台的影响,这将限制我们自已去解码音频文件。“turtle”语言模式识别的一个非常简单的“robot control”语言,其中包括一些诸如“go forward ten meters”的短语。事实上,一个音频文件包含在PocketSphinx源代码的句子中。你可以在test/data/goforward.raw中找到。把它复制到当前目录。如果你想创建自己的版本,需要一个单声道,小端,无头16-bit PCM有符号,采样率为16KHZ的音频文件。
为了这样做,首先我们打开文件:
FILE *fh;
fh = fopen("goforward.raw", "rb");
if (fh == NULL) {
perror("Failed to open goforward.raw");
return 1;
}
然后使用ps_decode_raw()进行解码:
rv = ps_decode_raw(ps, fh, "goforward", -1);
if (rv < 0)
return 1;
现在,为了假设,我们可以使用ps_get_hyp():
char const *hyp, *uttid;
int rv;
int32 score;
hyp = ps_get_hyp(ps, &score, &uttid);
if (hyp == NULL)
return 1;
printf("Recognized: %s\n", hyp);
从内存中解码音频数据
现在我们将再次解码相同的文件,但是使用API从内存块中解码音频数据。在这种情况下,首先我们需要使用ps_start_utt()开始说话:
fseek(fh, 0, SEEK_SET);
rv = ps_start_utt(ps, "goforward");
if (rv < 0)
return 1;
我们将每次从文件中读取512大小的样本,使用ps_process_raw()把它们放到解码器中:
int16 buf[512];
while (!feof(fh)) {
size_t nsamp;
nsamp = fread(buf, 2, 512, fh);
rv = ps_process_raw(ps, buf, nsamp, FALSE, FALSE);
}
然后我们需要使用ps_end_utt()去标记说话的结尾处:
rv = ps_end_utt(ps);
if (rv < 0)
return 1;
以相同精确的方式运行来检索假设的字符串:
hyp = ps_get_hyp(ps, &score, &uttid);
if (hyp == NULL)
return 1;
printf("Recognized: %s\n", hyp);
清理
使用ps_free()对对象进行清理,返回使用ps_init()。你不应该释放配置对象。
代码清单
1 #include <pocketsphinx.h> 2 3 int main(int argc, char *argv[]) 4 { 5 ps_decoder_t *ps; 6 cmd_ln_t *config; 7 FILE *fh; 8 char const *hyp, *uttid; 9 int16 buf[512]; 10 int rv; 11 int32 score; 12 13 config = cmd_ln_init(NULL, ps_args(), TRUE, 14 "-hmm", MODELDIR "/hmm/en_US/hub4wsj_sc_8k", 15 "-lm", MODELDIR "/lm/en/turtle.DMP", 16 "-dict", MODELDIR "/lm/en/turtle.dic", 17 NULL); 18 if (config == NULL) 19 return 1; 20 ps = ps_init(config); 21 if (ps == NULL) 22 return 1; 23 24 fh = fopen("goforward.raw", "rb"); 25 if (fh == NULL) { 26 perror("Failed to open goforward.raw"); 27 return 1; 28 } 29 30 rv = ps_decode_raw(ps, fh, "goforward", -1); 31 if (rv < 0) 32 return 1; 33 hyp = ps_get_hyp(ps, &score, &uttid); 34 if (hyp == NULL) 35 return 1; 36 printf("Recognized: %s\n", hyp); 37 38 fseek(fh, 0, SEEK_SET); 39 rv = ps_start_utt(ps, "goforward"); 40 if (rv < 0) 41 return 1; 42 while (!feof(fh)) { 43 size_t nsamp; 44 nsamp = fread(buf, 2, 512, fh); 45 rv = ps_process_raw(ps, buf, nsamp, FALSE, FALSE); 46 } 47 rv = ps_end_utt(ps); 48 if (rv < 0) 49 return 1; 50 hyp = ps_get_hyp(ps, &score, &uttid); 51 if (hyp == NULL) 52 return 1; 53 printf("Recognized: %s\n", hyp); 54 55 fclose(fh); 56 ps_free(ps); 57 return 0; 58 }
高级用法
对于使用更复杂的老API,有一些明显的不同地点:
- 得到部分和全部假设不再有单独的功能;
- 词语切分是通过访问迭代器而不是返回的数组或列表;
- 语言模型的转换是通过外部(<ngram_model.h>)。
首先这些都是简单的。在这之前,你必须使用uttproc_partial_result()得到部分结果(例如在uttproc_end_utt()前),使用uttproc_result()得到全部结果。现在,ps_get_hyp()能工作在两种方式下。
对于字的分割,API提供了一个遍历所有字序列的迭代器对象。这个迭代器对象是一个抽象类,这个抽象类为一些访问器提供获取每个字的时间点,分数和(最有趣的)后验概率。
最后,语言模型切换是完全不同的。解码器总是与一个语言模型集对象联系在一起。切换语言模型是通过以下:
- 得到一个句柄然后给语言模型集对象:ps_get_lmset()
- 选择一个新的语言模型:ngram_model_set_select()
- 告诉解码器语言模型集已更新:ps_update_lmset()
翻译于2011年9月21日
参考官网:http://cmusphinx.sourceforge.net/wiki/tuturialpocketsphinx