项目简介:
Servant通过借助百度语音识别、语音合成对语音文字进行相互转换,通过分析文字选择下达Linux相关命令或者与图灵机器人进行交互实现与AI进行语音对话。
开发环境:
CentOS7 , g++ 7.3.1
应用技术
- 图灵机器人,
- 百度语音识别及语音合成,
- Linux系统/网络编程,
- C++ STL,
- http第三方库
项目执行流程:
开发步骤:
在整体流程中要把语音转换为文字,判断是否是指令,如果不是指令,就使用文字与图灵机器人进行对话,我决定先实现与图灵机器人进行文本交互,调试正确后,如果后续出现bug那么就可以缩小查找bug的范围。
1、与图灵机器人交互
首先在图灵机器人官网通过链接教程了解怎么使用此api,地址:https://www.kancloud.cn/turing/www-tuling123-com/718218
通过阅读文档,我知道了想要与图灵机器人进行交互,需要用到HTTP POST,要交互的数据是以JSON格式来进行传输。
要使用HTTP POST,就需要在程序中创建一个HTTP Client 并实现一个POST方法。
(这里写创建方法)
在我了解百度语音识别工具集时发现,在语音识别C++ SDK中包含一个http请求封装类,人家的更严谨一些,所以这里我就不自己写了,使用SDK中的Client以及POST方法。
这里注意到要使用这个SDK就得安装libcurl库,以支持不同协议的链接以及不同服务器之间的沟通。
解决了通信通道的问题,接下来就要解决传输格式问题,在上面API使用文档中说到,对图灵API发起请求的参数格式为json,所以想要进行请求就要把请求的数据转换成json格式。
仿照上面的示例格式,构建自己的json数据
string Chat(string message)
{
Json::Value root;
root["reqType"] = 0;
Json::Value text;
text["text"] = message;
Json::Value inputText;
inputText["inputText"] = text;
root["perception"] = inputText;
Json::Value user;
user["apiKey"] = apiKey;
user["userId"] = userId;
root["userInfo"] = user;
Json::StreamWriterBuilder wb;
std::ostringstream os;
std::unique_ptr<Json::StreamWriter> JsWriter(wb.newStreamWriter());
JsWriter->write(root, &os);
std::string body = os.str();
//保存服务器返反馈的数据
string response;
//对服务器进行POST请求
int code = client.post(url, nullptr, body, nullptr, &response);
百度语音识别SDK中的post方法:
经过一次请求,拿到了一个json字符串,要把它转换成json对象才方便解析。这就用到了parse()函数
。转换成json对象后进行解析,拿到需要的信息。这里我就是拿到图灵机器回复我的文字,然后打印出来即可。
//这里复习一下,str.data()与str.c_str()的区别:
//c_str() 返回一个指向正规C字符串的指针常量,该指针保证指向一个 size() + 1 长度的空间,而且最后一个字符肯定是 \0
//而 data() 返回的指针则保证指向一个size()长度的空间,不保证有没有null-terminate
jsonReader->parse(str.data(), str.data()+str.size(), &root, &errs);
Json::Value result = root["results"];
Json::Value value = result[0]["values"];
Json::Value text = value["text"];
现在完成了与图灵机器人进行交互,已经可以和AI进行文字交流了,但是想要做到和手机上的Siri和小爱那样语音交流,就需要用百度语音识别以及语音合成了。
首先要创建一个语音识别客户端,SDK中已经实现了,使用时只需要参照示例代码进行创建即可。
要进行语音识别,肯定得有一个语音文件嘛,所以在每次识别前,先打开录音程序进行录音,然后再把它交给语音识别来识别。
这里我想到可以用popen()函数来完成这一需求。让他来创建一个子进程来录音。
FILE * popen( const char * command,const char * type);
//popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。
//参数type可使用“r”代表读取,“w”代表写入。
//依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。
//随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中
完成录音后按照语音识别实例进行识别
识别后可以得到一个json数据,将他进行解析得到输入的语音转换成的文字。
为了不让他只会聊天,这里我加了一个执行指令的功能,就是转化出的文字如果是我配置的指令,那么就不发给图灵机器人了,直接执行指令。
为了满足这一功能,我在程序启动时先加载我的指令配置文件,由于指令一般都是一个字符串所以我用map<string,string>来存放命令以及对应指令。每次进行说话后,判断识别出来的内容是不是一个命令,如果是的话就调用popen()来执行对应的指令。如果不是,就将识别出的文字发给图灵机器人,与进行AI对话。
像前面的一样,这时候图灵机器人回复给我的数据经过解析,依然是个字符串,而不是语音,接下来用百度语音合成,将其转换成语音,然后再使用popen()播放语音。
(这里记录几个自己遇到的问题(纯怪自己傻🐷)
1、明明安装齐了依赖库,但编译时候出现链接时错误,原因是没在Makefile中添加第三方链接参数。
2、在对图灵机器人发送数据时候,总是出错,后来发现构建json串时候自己少写了一层。json:value的嵌套)
完整项目地址:Servant