【项目】——智能AI语音管家tamako

项目简介

  • 使用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函数播放对应的录音便可,至此整个程序就完成了,如下为效果演示,固然因为没法录制视频,因此没法展示语音效果。

 

总结

总的来讲,这个项目代码并不复杂,主要在于学会各个工具以及第三方平台的使用,是一个很好玩的项目。

项目源码

https://github.com/hu1277141113/C-study

相关文章
相关标签/搜索