项目:智能语音对话机器人

技术点:

C++STL
● http第三方库
● 图灵机器人
百度语音识别和语音识别
● Linux系统/网络编程
● 各种第三方库和第三方工具的安装与使用

项目简介

  • 使用C++编写一个智能AI对话和语音命令执行的语音管理工具,其中可执行命令支持配置。
  • 主要采用C++进程编写,当程序启动后,加载配置文件,启动录音功能进行录音,调用百度语音识别平台对所录音文件进行识别;再对所识别到的文字进行判断,是命令还是普通对话,命令则执行系统命令执行模块;普通对话则交给图灵机器人进行交流,所返回的文字交给百度语音合成平台,启动播放功能,则成功进行对话。

准备工作

  • 使用图灵机器人实现对话,需要注册图灵机器人,拥有一个属于自己的机器人
  • 使用百度语音识别和语音合成,同样也需要注册,在之后的使用中,我们选择的技术文档为C++SDK,需要下载百度C++语音识别SDK
  • 安装jsoncpp、 安装openssl、安装libcurl(需要支持https)
  • 还要准备录音和播放工具,在我的Centos 7上有系统自带的录音工具arecord,安装vlc/cvlc播放器

编码过程

● 先建立项目目录,创建工程文件,引入百度语音识别SDK

#mkdir Boyfriend 
#ls build.sh

  • 构建项目的shell脚本,简单实现 command.etc
  • 语音命令配置文件 Boyfriend.cc
  • 源文件 Boyfriend.hpp
  • 核心代码 Makefile speech
  • 百度语音识别SDK temp_file
  • 保存临时语音文件目录

核心代码

LInux语音AI相关代码

  #Boyfriend.hpp
  //1、创建相关类,包含相关头文件 
   #pragma once
   #include 
   #include 
   #include 
   #include 
   #include 
   #include 
   #include 
   #include 
   #include 
   #include 
   #include 
   #include 
   #include "base/http.h"
   #include "speech.h"
  
   //#define TTS_PATH "temp_file/tts.mp3"
   #define TTS_PATH "temp_file/tts.wav"
   #define ASR_PATH "temp_file/asr.wav"
   #define CMD_ETC "command.etc"
   #define LOG "log.txt"
  
  class Util{
      private:
          static pthread_t id;
      public:
         static bool Exec(std::string command, bool is_print)
          {
            if(!is_print){
                  command += ">/dev/null 2>&1";
                  }
             FILE *fp = popen(command.c_str(), "r");
             if(nullptr == fp){
                  std::cerr << "popen exec \'" << command << "\' Error" << std::endl;
                  return false;
             }
                  if(is_print){
                          char ch;
                          while(fread(&ch,1,1,fp) >0){
                                  fwrite(&ch,1,1,stdout);
                          }
                  }
                  pclose(fp);
                  return true;
          }
          static void *ThreadRun(void *arg)
          {
             const char *tips = (char*)arg;
             int i = 0;
             char bar[103] = {0};
             const char *lable = "|/-\\";
             for(; i<=50;i++){
                  printf("%s[%-51s][%d%%][%c]\r",tips,bar,i*2,lable[i%4]);
                  fflush(stdout);
                  bar[i] = '=';
                  bar[i+1] = '>';
                  //bar[i+2] = 0;
                  usleep(49000*2);
             }
             printf("\n");
          }
          static void PrintStart(std::string tips)
          {
             pthread_create(&id,NULL,ThreadRun,(void*)tips.c_str());
          }
          static void PrintEnd()
          {
             pthread_cancel(id);
          }
  };
  pthread_t Util::id;
  //2、采用图灵机器人,进行智能对话 
  class Robot{
     private:
        std::string url;
        std::string api_key;
        std::string user_id;
        aip::HttpClient client;
      private:
             bool IsCodeLegal(int code)
            {
                  bool result = false;
                  switch(code){
                      case 5000:
                      case 6000:
                          break;
                      case 10004:
                          result = true;
                          break;
                      default:
                          result = true;
                          break;
                   }
                  return result;
            }
              std::string MessageToJson(std::string &message)
             {
                 Json::Value root;
                 Json::StreamWriterBuilder wb;
                 std::ostringstream ss;
 
                 Json::Value item_;
                 item_["text"] = message ;
 
                 Json::Value item;
                 item["inputText"] = item_ ;
 
                 root["reqType"] = 0;//text
                 root["perception"] = item;
 
                 item.clear();
                 item["apiKey"] = api_key;
                 item["userId"] = user_id;
                 root["userInfo"] = item;
 
                 std::unique_ptr<Json::StreamWriter> sw(wb.newStreamWriter());
                 sw->write(root,&ss);
                 std::string json_string = ss.str();
                 //std::cout<<"debug: "<
                 return json_string;
            }
             std::string RequestTL(std::string &request_json)
             {
               std::string response;
               int status_code = client.post(url,nullptr,request_json,nullptr,&response);
               if (status_code != CURLcode::CURLE_OK) {
                     std::cerr << "post error" << std::endl;
                     return "";
              }
                 return response;
             }
              std::string JsonToEchoMessage(std::string &str)
              {
                 //std::cout <<"JsonToEchoMessage:"<< str << std::endl;
                 JSONCPP_STRING errs;
                 Json::Value root;
                 Json::CharReaderBuilder rb;
                 std::unique_ptr<Json::CharReader> const cr(rb.newCharReader());
                 bool res = cr->parse(str.data(),str.data()+str.size(), &root,&errs);
                 if(!res || !errs.empty()) {
                         std::cerr << "parse error!"<<std::endl;
                         return "";
                 }
                 int code = root["intent"]["code"].asInt();
                 if(!IsCodeLegal(code))
                 {
                         std::cerr<< "response code error!" <<std::endl;
                         return "";
                 }
                 Json::Value item = root["results"][0];
                 std::string msg = item["values"]["text"].asString();
                 return msg;
              }
 
     public:
        Robot(std::string id = "1")
        {
            this->url = "http://openapi.tuling123.com/openapi/api/v2";
            this->api_key = "b7fa07e47cbe46a191b8190cc004ed60";
            this->user_id = id;
         }
        std::string Talk(std::string message)
        {
           std::string json = MessageToJson(message);
           std::string responce = RequestTL(json);
           std::string echo_message = JsonToEchoMessage(responce);
           return echo_message ;
        }
 
        ~Robot()
        {
 
        }
 };
 //3、语音识别Speech Recognition
 class SpeechRec{
    private:
         //填写appid,填写Api Key,填写Secret Key.
         std::string app_id;
         std::string api_key;
         std::string secret_key;
         aip::Speech *client;
    private:
            bool IsCodeLegal(int code)
            {
                  bool result = false;
                  switch(code){
                      case 0:
                          result=true;
                          break;
                      default:
                          break;
                   }
                  return result;
            }
    public:
         SpeechRec()
         {
             app_id = "16943059";
             api_key = "q9vkbqwjwKjvoPgt69NTt36F";
             secret_key = "8lbil7MTruiGKHQdSsIT1WB97rX4ncsl";
             client = new aip::Speech(app_id, api_key, secret_key);
         }
  //4、语音识别Automatic Speech Recognition 
         bool ASR(std::string path,std::string &out)
         {
             std::map<std::string,std::string> options;
             options["dev_pid"] = "1536";
             std::string file_content;
             aip::get_file_content(ASR_PATH,&file_content);
             Json::Value result = client->recognize(file_content,"wav",16000,options);
             //std::cout << "debug: " << result.toStyledString() << std::endl;
             int code = result["err_no"].asInt();
             if(!IsCodeLegal(code)){
                    std::cerr << "recognize error" << std::endl;
                    return false;
                 }
             out = result["result"][0].asString();
             return true;
         }
  //5、语音合成Text To Speech
         bool TTS(std::string message)
         {
            bool ret;
            std::ofstream ofile;
            std::string ret_file;
            std::map<std::string,std::string> options;
            options["spd"]="5"; //speech speed 0~15
            options["pit"]="7"; //0-15
            options["vol"]="15"; //0-15
            options["per"]="110"; //1 0 3 4 度博文=106 度小童=110 度小萌=111 度米朵=103 度小娇=5
            options["aue"]="6";
            ofile.open(TTS_PATH,std::ios::out|std::ios::binary);
            Json::Value result=client->text2audio(message,options,ret_file);
             if(!ret_file.empty()){
                 ofile << ret_file;
                 ofile.close();
                 ret = true;
            }
            else{
                 std::cerr <<result.toStyledString() << std::endl;
                 ret = false;
            }
            ofile.close();
            return ret;
         }
         ~SpeechRec()
         {
         }
 };
 //6、核心代码-服务器&&系统相关 
 class Boyfriend{
    private:
         Robot rt;
         SpeechRec sr;
         std::unordered_map<std::string,std::string> commands;
    private:
         bool Record()
         {
            //std::cout << "debug:" << "Record ..." << std::endl;
            Util::PrintStart("录音中");
            std::string command = "arecord -t wav -c 1 -r 16000 -d 5 -f S16_LE ";
            command += ASR_PATH;
            bool ret = Util::Exec(command,false);
            //std::cout << "debug:Recoed ... done" <
            Util::PrintEnd();
            return ret;
         }
         bool Play()
         {
            std::string command = "cvlc --play-and-exit ";
            command += TTS_PATH;
            return Util::Exec(command,false);
         }
    public:
      Boyfriend()
      {
 
       };
       bool LoadEtc()
      {
         std::ifstream in(CMD_ETC);
         if(!in.is_open()){
                 std::cerr <<"open error" <<std::endl;
                 return false;
                 }
                 std::string sep = ":";
                 char line[256];
                while(in.getline(line,sizeof(line))){
                         std::string str = line;
                         std::size_t pos = str.find(sep);
                         if(std::string::npos == pos){
                                 std::cerr << "not find :" << std::endl;
                                 continue;
                         }
                         std::string k = str.substr(0,pos);
                         std::string v = str.substr(pos+sep.size());
                         k+="。";
                         commands.insert(std::make_pair(k,v));
                         //commands.insert({k,v});
                 }
                 std::cerr <<"Load command erc done ... success"<< std::endl;
                 in.close();
                 return true;
      }
      bool IsCommand(std::string message,std::string &cmd)
      {
         auto iter = commands.find(message);
         if(iter ==commands.end()){
                 return false;
         }
         cmd = iter->second;
      }
       void Run()
      {
 #ifdef _LOG_
         int fd = open(LOG,O_WRONLY|O_CREAT,0644);
         if(fd<0){
           return 1;
         }
         dup2(fd,2);
         //int fd = open("/dev/null",O_WRONLY);
 #endif
         volatile bool quit = false;
         while(!quit){
            if(this->Record()){
                 std::string message;
                 if(sr.ASR(ASR_PATH,message)){
                    //1.command
                    std::string cmd="";
                    if(IsCommand(message,cmd)){
                         std::cout << "[Boyfriend@localhost]$ "<<cmd <<std::endl;
                         Util::Exec(cmd,true);
                         continue;
                         }
                    std::cout<<"我# "<<message <<std::endl;
                    if(message =="你走吧。"){
                         std::string quit_message = "那我就走了,不要太想我哦!";
                         std::cout<<"Boyfriend# " << quit_message<< std::endl;
                          if(sr.TTS(quit_message)){
                                  this->Play();
                                 }
                         exit(0);
                         }
                    //2.Tuling
                    // std::cout<<"我# "<
                    std::string echo = rt.Talk(message);
                    std::cout<<"Boyfriend# " << echo<< std::endl;
                    if(sr.TTS(echo)){
                         this->Play();
                    }
                 }
         //      else{
         //         std::cerr<<"Recognize error..." << std::endl;
         //      }
            }
            else{
                 std::cerr << "Record error..." << std::endl;
         }
           // sleep(2);
         }
 #ifdef _LOG_
         close(fd);
 #endif
       }
      ~Boyfriend()
      {
       }
 };
 

● 调用逻辑

#Boyfriend.cc
   #include "Boyfriend.hpp"
  
   using namespace std;
   
   int main()
   {
      //Robot r;
      //string str;
      //volatile bool quit = false;
      //while(!quit){
      //  cout << "我# ";
      //  cin >> str;
      //  std::string s = r.Talk(str);
      //  cout << "Tom$ " << s << endl;
      //    }
      //  std::string s = r.Talk("你好吗?");
      //  cout << s << endl;
    Boyfriend *bf = new Boyfriend();
    if(!bf->LoadEtc()){
          return 1;
    }
  
    bf->Run();
    return 0;
  }

● command.etc命令配置文件

#command.etc
   查看当前目录:ls -l
   关闭防火墙:systemctl stop firewalld
   打开防火墙:systemctl start firewalld
   跑火车:sl
   黑客帝国:cmatrix -B -C blue
   查看黑名单:cat black_name.txt
   你给我闺蜜唱首歌:aplay 李森茂_-_生日快乐歌(吉他改编版).wav

● Makefile配置环境变量

#Makefile
   CC=g++
   #编译器
   bin=Boyfriend
   #生成的可执行文件
   src=Boyfriend.cc
   #依赖的源文件
   INCLUDE=-Ispeech/
   LIB=-ljsoncpp  -lcurl -lcrypto -lpthread
   
  $(bin):$(src)
   #生成bin,依赖的src
          $(CC) -o $@ $^ $(INCLUDE) $(LIB)
  #方法
  .PHONY:clean
  clean:
          rm -f $(bin)

代码实现

智能语音对话AI

难点

jsoncpp的使用方法讲解

  • 什么是json
  • 有什么特点
  • 为什么要用它
  • StreamWriterBuilder、StreamWriter、CharReaderBuilder、CharReader、write函数、parse函数与Json:Value的使用
//序列化 
#include  
#include 
#include  
#include 
#include  
int main() 
{    
Json::Value root;    
Json::StreamWriterBuilder wb;     
std::ostringstream os; 
root["Name"] = "zhangsan";     
root["Age"] = 26;     
root["Lang"] = "c++"; 
std::unique_ptr<Json::StreamWriter>
jsonWriter(wb.newStreamWriter());     
jsonWriter->write(root, &os);     
std::string
s = os.str(); 
std::cout << s << std::endl; 
return 0; 
} 
#打印序列化结果
{
 "Age" : 26,
 "Lang" : "c++",
 "Name" : "zhangsan"
}

总结:json还支持嵌套,如json里面包含“json”,甚至json还包含”json数组“等,但是目前jsoncpp我们以简单使用为主。

总结&项目扩展
  • 整体来讲,项目的难度不大,核心点在于对各个工具,平台的熟悉程度
  • 我从这个项目中吸收的东西是: 学会使用第三方平台,第三方工具,就能完成很有意思的功能

扩展:

  • 将该项目移植到windows中, 语音控制windows上各个软件的打开和关闭,岂不酷炫
  • 能否移植到手机端呢?如找一个js页面,在服务器上部署服务,使用手机远程控制服务器(笔记本)
  • 可以在树莓派上,安装一些第三方驱动或者组件(如,灯),语音控制灯的开启和关闭灯,再调研一下百度的其他语音或者文本功能做这类项目,就应该玩起来。

你可能感兴趣的:(项目,AI,语音识别,C++/Linux项目)