现在能够实时对语音端点进行检测并自动将语音信号写入文件,也可以通过音频块的方式接收包含语音信号的音频帧,那么接下来便是如何处理这些音频数据以达到识别的效果。最简单直接的方式就是接入第三方平台,不过这种方式对网络过于依赖。虽然讯飞有提供离线的语音识别,可需要付费…想要更好的用户体验,那必须掏腰包,毕竟专业支持到位。该博客主要记录第三方语音云服务器接入的设计过程,并给出具体的解决方法。
总体框架
考虑到不同的云服务平台接入方式不同,当对于HTTP请求方面大致相同,不同的就是接入参数。那么需要接入不同的平台,又要做到对软件尽量少的改动,最好的办法就是使用配置文件来处理。那么大概的框架就可以这么处理:
- 利用一个全局配置文件保存各种云服务平台的接入参数
- 统一编写一个文件参数解析器,可用 C++/Python 封装
- 对不同的云平台设计接入类,每个类的初始化参数由文件解析器提供
虽然过程只有三步,但每一步都需要花点心思。这里面的问题是:
- 配置文件用什么格式写
这个问题在没有仔细想之前,觉得用普通文件也能实现。但对于实用性和易用性来说,这有点说不过去,随便用一个文件记录相关信息很简单,但如果没有固定格式声明,那么管理起来也很麻烦。那么有没有一种比较有效的文件来当配置文件使用,必须呀,就像 Linux 内核配置文件一样…不过也不用这么像它这么选项。我们需要的就是简单的键值对文件即可,那么考虑到 C++/Python 的支持,由于开源的JSON 库很稳定,安装也比较简单,用起来也方便。所以配置文件就用 JSON 格式。
对与 JSON 库安装可以查阅:C++ Json 库安装与使用
接入设计
对于百度语音接入,使用Python封装比较简单,网上有很多教程,这里不过多说明。现在主要以讯飞API的 C++ 封装来说明。根据总体的设计方案,总共需要三步,配置文件选项如何搭配呢?这还得看看讯飞API提供的说明文档。按照文档说明,对于在线识别和离线识别有不同的参数配置,还有最基本的帐号信息。那么基于分类管理的方法,此次设计的是离线参数集和在线参数集合,在配置文件中显示如下:
{
"speechServer" : {
"xunfei" : {
"loginParams" : "appid = ???, work_dir = /home/xxx/.SpeechSystem/xf-source/",
"onlineasr":{
"userWordsFilePath" : "/home/xxx/.SpeechSystem/xf-source/userwords.txt",
"params" : "sub = iat, domain = iat, language = zh_cn, accent = mandarin, sample_rate = 16000, result_type = plain, result_encoding = utf8"
},
"localasr":{
"engine_type" : "local",
"sample_rate" : 16000,
"result_type" : "xml",
"result_encoding" : "UTF-8",
"asr_res_path" : "fo|/home/xxx/.SpeechSystem/xf-source/asr-local-model/asr/common.jet",
"grm_build_path" : "/home/xxx/.SpeechSystem/xf-source/asr-local-model/asr/GrmBuilld",
"bnf_file" : "/home/xxx/.SpeechSystem/xf-source/call.bnf",
"asr_denoise" : 1,
"language" : "zh_cn",
"accent" : "mandarin",
"vad_enable" : 1,
"vad_bos" : 1000,
"vad_eos" : 500
}
},
}
}
有了配置文件的选项集,接下来比较好办,就是使用C++类对API进行封装。当具体要留下多少个接口?根据开始提到的语音数据保存方式,可以使用两个函数行为来进行描述。为了规范类接口,可以设计一个 ASR 基类,并且该类只有两种运行接口,其它基于该基类的派生类继承该接口需要重写实现过程,这样就能规范了。本次对语音识别设计了离线类和在线类两个类。
ASR 基类接口:
class ASR {
public:
ASR();
virtual ~ASR();
/*
* 参数说明:
* input:输入文件
* result:存放识别结果
* */
virtual void runasr(std::string input,std::string& result) = 0;
/*
* 参数说明:
* input:输入音频块
* result:存放识别结果
* end:结束标志
* */
virtual void runasr(std::vector<int16_t>& input,std::string& result,bool end) = 0;
protected:
std::string _result;
};
对于讯飞语音识别,一切的内部操作都视为私有行为,不暴露接口。如在线识别类具体实现如下:
class XFonlineasr :public ASR{
public:
XFonlineasr();
XFonlineasr(const XfOnlineASR& params,const XfBasic& lg);
virtual ~XFonlineasr();
/*
* 上传用户词汇表
* */
void uploadUserWords(const XfOnlineASR& asrparams);
void runasr(std::string file,std::string& result);
void runasr(std::vector<int16_t>& input,std::string& result,bool end);
private:
/**登录* */
void login(const XfBasic& params);
/**合成启动会话参数* */
void composeBeginParams(const XfOnlineASR& asrparams);
/** 获取会话语句柄 * */
void getSid();
/** 会话复位* */
void reset();
/** 等待语音识别结束* */
void waitAsrComplete(std::string& result);
XfAsrState _state; //离线命令识别状态
string _begin_params; //第一个会话参数
string _sid; //识别会话语句柄
};
对象构造时给定初始化参数,该参数可以通过一个文件解析器获得相关参数配置的具体内容,所以在使用时还需要声明一个配置文件解析器来初始化对象,也就是解析器和识别器要配对使用。具体使用如下所示:
int main(int argc, char **argv)
{
// 依照配置文件配置服务
std::string configfile = “config_file_path”;
// 文件解析类
Hntea::ConfigResolver parser(configfile);
//初始化讯飞对象
Hntea::XFonlineasr online =
Hntea::XFonlineasr(parser.getXfOnlineParams(),parser.getXfBasic());
//识别
online.runasr("音频文件",“结果存放地址”);
return 0;
}
使用就这么简单。对源码有兴趣可以直接戳 源码传送门
现在能使用语音识别了,只是能使用,但要如何更加智能的实现交互,还需要花很多心思,对与讯飞的离线命令此识别,它使用了一种网络配置文件,若该文件设计的好,那交互起来也会更智能。若是针对不同的内容的命令词识别,是否能够声明不同的识别对象并绑定不同的bnf文件?这样就能够同时支持多网络了。有兴趣可以尝试以下。
对于自然语义理解,这里就不在多说,图灵提供的API接入和百度语音识别差不多,关键是参数配好就好。还有目前免费的感觉还是很傻…期待接下来会更加聪明。