接入语音云平台

接入语音云平台

Hntea bio photo By Hntea

现在能够实时对语音端点进行检测并自动将语音信号写入文件,也可以通过音频块的方式接收包含语音信号的音频帧,那么接下来便是如何处理这些音频数据以达到识别的效果。最简单直接的方式就是接入第三方平台,不过这种方式对网络过于依赖。虽然讯飞有提供离线的语音识别,可需要付费…想要更好的用户体验,那必须掏腰包,毕竟专业支持到位。该博客主要记录第三方语音云服务器接入的设计过程,并给出具体的解决方法。

总体框架

考虑到不同的云服务平台接入方式不同,当对于HTTP请求方面大致相同,不同的就是接入参数。那么需要接入不同的平台,又要做到对软件尽量少的改动,最好的办法就是使用配置文件来处理。那么大概的框架就可以这么处理:

  1. 利用一个全局配置文件保存各种云服务平台的接入参数
  2. 统一编写一个文件参数解析器,可用 C++/Python 封装
  3. 对不同的云平台设计接入类,每个类的初始化参数由文件解析器提供

虽然过程只有三步,但每一步都需要花点心思。这里面的问题是:

  • 配置文件用什么格式写

这个问题在没有仔细想之前,觉得用普通文件也能实现。但对于实用性和易用性来说,这有点说不过去,随便用一个文件记录相关信息很简单,但如果没有固定格式声明,那么管理起来也很麻烦。那么有没有一种比较有效的文件来当配置文件使用,必须呀,就像 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接入和百度语音识别差不多,关键是参数配好就好。还有目前免费的感觉还是很傻…期待接下来会更加聪明。