项目简介
- 使用C++编写一个智能AI对话和语音命令执行的语音管理工具
- 借助图灵机器人和百度语音识别和合成等第三方平台和第三方工具
- 可执行Linux下相关指令,可本身添加想要执行的指令
项目技术点
- C++ STL
- http第三方库
- 图灵机器人
- 百度语音识别和语音合成
- Linux系统/网络编程
- 各类第三方库和第三方工具的安装与使用
项目框架
开始编码
我按照程序流程来介绍这个项目,包括要提早准备的各类第三方库等。(注:介绍的顺序并非真实编码顺序)linux
-
使用录音工具对用户的话进行录音
使用arecord进行语音录制,arecord系统默认自带,能够直接输入arecord看看你的平台有没有安装,若是没有,就会告诉你“bash: XXX: command not found...”,网上查阅进行安装便可。根据百度语音识别技术文档知录音时采用何种格式。ios
# arecord -t wav -c 1 -r 16000 -d 5 -f S16_LE demo.wav // -t: 设置文件类型,咱们采用wav格式 // -c: 设置通道号 // -r: 设置频率 // -d: 设置持续时间,单位为秒 // -f: 设置采样格式.格式包括:S8 U8 S16_LE(S16_LE: little endian signed 16 bits) // S16_BE U16_LE U16_BE S24_LE S24_BE U24_LE U24_BE S32_LE S32_BE // U32_LE U32_BE FLOAT_LE FLOAT_BE FLOAT64_LE FLOAT64_BE // IEC958_SUBFRAME_LE IEC958_SUBFRAME_BE MU_LAW A_LAW IMA_ADPCM MPEG GSM //咱们为什么这样采用,主要是由于百度语音识别的要求,具体能够参照百度语音识别文档
-
百度语音识别
咱们须要作的是将录好的音频发送给百度语音识别,而后获取语音识别的结果。git
1.那么首先咱们须要在代码中实现录音功能,也就是须要在代码中实现执行Linux指令的功能,咱们使用popen函数来实现这点。github
//执行linux命令 156 bool Exec(string command,bool is_print) 157 { 158 FILE* fp=popen(command.c_str(),"r"); 159 if(fp==nullptr) 160 { 161 cout<<"popen erroe"<<endl; 162 return false; 163 } 164 if(is_print) 165 { 166 char c; 167 size_t s=0; 168 while((s=fread(&c,1,1,fp)>0)) 169 { 170 cout<<c; 171 } 172 } 173 pclose(fp); 174 return true; 175 }
2.接着咱们须要与百度语音识别平台通讯,阅读官方文档,根据要求安装各类第三方库,因为是在Linux系统开发,我在更新cmake时发现根本下不动,而后在网上查缘由,各类解决方案都尝试了,包括更换yum源,更换ipv4的地址为ipv6的等,折腾了将近两三个小时也没解决,最后把wifi网络换成了手机流量就行了。。。编程
3.接着根据官方文档,咱们建立client来与其通讯。json
class yuzi 92 { 93 private: 94 turing tr; 95 aip::Speech *client; 96 string appid="21527483"; 97 string apikey="szxkTzGtRhGIGl2MMoE84qIw"; 98 string secretKey="hZppPdvzecr8WSPSBfh9gPxP6VgB6lqO";
4.根据语音识别接口说明,咱们编写语音识别的函数。须要注意的是:因为返回的是jason串,所以咱们须要对结果进行反序列化。刚开始我并无学过jason串,所以还学习了如何进行jason的序列化与反序列化。api
//语音识别 190 string ASR(aip::Speech* client) 191 { 192 string asr_file=VOICE_PATH; 193 asr_file+="/"; 194 asr_file+=SPEECH_ASR; 195 196 map<string,string> options; 197 string file_content; 198 aip::get_file_content(asr_file.c_str(),&file_content); 199 Json::Value root=client->recognize(file_content,"wav",16000,options); 200 return RecognizePickup(root); 201 }
//反序列化 178 string RecognizePickup(Json::Value& root) 179 { 180 int err_no=root["err_no"].asInt(); 181 if(err_no!=0) 182 { 183 cout<<root["err_msg"]<<":"<<err_no<<endl; 184 return "unknown"; 185 } 186 return root["result"][0].asString(); 187 }
-
判断是否为命令
想要使这个程序能够执行Linux指令,咱们须要建立一个命令文件,其中存放中文命令(key)及Linux命令(value)这样的kv键值对,而后在程序中遍历这个文件,将其存放在map中,这样就能判断一条语句是否为命令而且经过以前的Exec函数执行它。当时在运行这段代码时,发现说出了对应的Key值,可是依旧没有执行相关命令,发现是由于百度语音识别返回的语句结尾带有“。”,所以将key+="。"解决了问题。bash
代码以下:网络
//加载配置文件 110 void LoadCommandEtc() 111 { 112 LOG(Normal,"命令开始执行"); 113 string name=CMD_ETC; 114 ifstream in(name); 115 if(!in.is_open()) 116 { 117 LOG(Warning,"Load command etc error"); 118 exit(1); 119 } 120 char line[SIZE]; 121 string sep=": "; 122 while(in.getline(line,sizeof(line))) 123 { 124 string str=line; 125 size_t pos=str.find(sep); 126 if(pos==string::npos) 127 { 128 LOG(Warning,"command etc format error"); 129 break; 130 } 131 string key=str.substr(0,pos); 132 string value=str.substr(pos+sep.size()); 133 key+="。";//语音识别可以成功 134 record_set.insert({key,value}); 135 } 136 in.close(); 137 LOG(Normal,"命令执行成功"); 138 }
//判断是不是命令 229 bool IsCommand(string& message) 230 { 231 return record_set.find(message)==record_set.end()? false:true; 232 }
void run() 235 { 236 while(1) 237 { 238 LOG(Normal,"开始录音:"); 239 fflush(stdout); 240 if(Exec(record,false)) 241 { 242 LOG(Normal,"识别中..."); 243 fflush(stdout); 244 string message =ASR(client); 245 cout<<endl; 246 LOG(Normal,message); 247 if(IsCommand(message)) 248 { 249 //是命令 250 LOG(Normal,"运行一个指令"); 251 Exec(record_set[message],true); 252 continue; 253 }
-
不是命令,将信息发送给图灵机器人
经过阅读图灵机器人api接入文档,咱们构建turning类。咱们直接使用百度语音SDK自带的http客户端对图灵机器人平台发送消息。因为文档要求post请求,因此咱们使用post接口。固然因为发送的数据都为jason串,因此咱们须要进行序列化与反序列化。app
代码以下:
class turing 22 { 23 private: 24 string apiKey="4235c17e20a34e3eb3cad76b5f0d0097"; 25 string userId="1"; 26 string url="http://openapi.tuling123.com/openapi/api/v2"; 27 aip::HttpClient client; 28 public: 29 turing() 30 { 31 32 } 33 34 //反序列化 35 string ResponsePickup(string& str) 36 { 37 JSONCPP_STRING errs; 38 Json::Value root; 39 Json::CharReaderBuilder rb; 40 std::unique_ptr<Json::CharReader> const jsonReader(rb.newCharReader()); 41 bool res=jsonReader->parse(str.data(),str.data()+str.size(),&root,&errs); 42 if(!res || !errs.empty()) 43 { 44 LOG(Warning,"jsoncpp parse error"); 45 return errs; 46 } 47 Json::Value results = root["results"]; 48 Json::Value values = results[0]["values"]; return values["text"].asString(); 50 } 51 52 //序列化 53 string chat(string message) 54 { 55 Json::Value root; 56 root["reqType"]=0; 57 Json::Value word; 58 word["text"]=message; 59 Json::Value text; 60 text["inputText"]=word; 61 root["perception"]=text; 62 Json::Value user; 63 user["apiKey"]=apiKey; 64 user["userId"]=userId; 65 root["userInfo"]=user; 66 67 Json::StreamWriterBuilder wb; 68 std::ostringstream os; 69 70 std::unique_ptr<Json::StreamWriter> jsonWriter(wb.newStreamWriter()); 71 jsonWriter->write(root,&os); 72 string body=os.str();//有了json串 73 74 //发送http请求 75 string response; 76 int code= client.post(url,nullptr,body,nullptr,&response); 77 if(code!=CURLcode::CURLE_OK) 78 { 79 LOG(Warning,"http 请求错误!"); 80 return ""; 81 } return ResponsePickup(response); 83 } 84 85 ~turing() 86 { 87 88 } 89 };
-
返回的信息发送给百度语音合成
语音合成与语音识别相似,第三方库并无什么区别,一样直接使用自带的client就行,根据官方文档给的接口说明咱们照瓢画葫。须要注意的是,必定要先领取语音识别以及语音合成的免费额度,我刚开始就是由于没有领取致使一直返回错误码,查错误码文档也一直没找到那个错误码对应的错误信息,最后终于发现是由于没领取免费额度。
代码以下:
//语音合成 204 void TTL(aip::Speech *client,string &str) 205 { 206 ofstream ofile; 207 string ttl=VOICE_PATH; 208 ttl+="/"; 209 ttl+=SPEECH_TTL; 210 ofile.open(ttl.c_str(),ios::out | ios::binary); 211 string file_ret; 212 map<string,string> options; 213 options["spd"]="6"; 214 options["per"]="4"; 215 216 Json::Value result =client->text2audio(str,options,file_ret); 217 if(!file_ret.empty()) 218 { 219 ofile<<file_ret; 220 } 221 else 222 { 223 cout<<result.toStyledString()<<endl; 224 } 225 ofile.close(); 226 }
-
播放返回的录音
咱们经过Exec函数播放对应的录音便可,至此整个程序就完成了,如下为效果演示,固然因为没法录制视频,因此没法展示语音效果。
总结
总的来讲,这个项目代码并不复杂,主要在于学会各个工具以及第三方平台的使用,是一个很好玩的项目。