实现功能
我实现的AI管理工具功能有执行系统命令和智能聊天。程序启动后,开始录音。录音成功后使用百度语音识别平台进行识别,判断是命令还是普通对话。如果是命令,进入系统命令执行模块,执行完毕,看到执行结果,一次交互完成。如果是对话信息,推送到图灵机器人,图灵机器人会进行智能对话,得到图灵机器人响应的文本,调用百度语音合成,在本地合成语音,然后程序启动播放器,播放语音信息,完成对话信息。支持语音退出。
准备工作
1.由于开发环境的需求,gcc的版本至少要在6.x以上,需要准备高版本的gcc
# sudo yum install centos-release-scl
# sudo yum install devtoolset-8
# scl enable devtoolset-8 bash
2.图灵机器人
图灵机器人能进行智能对话。接入图灵机器人,依赖的是http协议,请求和相应的数据都是json格式。http本次不写,直接使用第三方库httplib。json进行数据序列号和反序列化功能需要我们完成,选择一个开源的C++库:jsoncpp。
3.百度语音识别
百度语音识别可以识别、合成语音。使用百度语音识别,首先,在官方网站下载C++ SDK压缩包,将下载的aip-cpp-sdk-version.zip解压, 其中文件为包含实现代码的头文件。然后,安装依赖库libcurl(需要支持https) openssl jsoncpp(>1.6.2版本,0.x版本将不被支持,本次使用的是1.8.3版本)。编译工程时添加 C++ 11 支持 (gcc/clang 添加编译参数 -std=c++11), 添加第三方库链接参数 lcurl, lcrypto, ljsoncpp。最后,在源码中include speech.h,引入压缩包中的头文件以使用aip命名空间下的类和方法。
4.准备Centos 7录音和播放工具
采用arecord进行语音录制,使用vlc/cvlc进行播放。
arecord是系统默认自带的,直接使用就好。vlc/cvlc播放器需要安装。
什么是json
json是一种传递对象的语法,对象可以是name/value对,数组和其他对象。
语法规则:
json 的语法规则十分简单:
数组(Array)用方括号(“[]”)表示。
对象(Object)用大括号(”{}”)表示。
名称/值对(name/value)组合成数组和对象。
名称(name)置于双引号中,值(value)有字符串、数值、布尔值、null、对象和数组。
并列的数据之间用逗号(“,”)分隔
输入参数
{
"reqType":0,
"perception": {
"inputText": {
"text": "你好吗"
},
"userInfo": {
"apiKey": "",
"userId": ""
}
}
项目模块
主要分四个大的模块:工具类、图灵机器人交互式类、访问百度语音识别类、完成核心逻辑类。
class Util{ //tool
private:
static pthread_t id;
public:
//在Linux中执行指定命令,采用popen。
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* label = "|/-\\";
for(; i <= 50; i++){
printf("%s[%-51s][%d%%][%c]\r",tips, bar, i*2, label[i%4]);
fflush(stdout);
bar[i] = '=';
bar[i+1] = '>';
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;
采用图灵机器人,进行智能对话,机器人最重要的功能:message转成json、请求图灵、拿回请求的消息
class Robot{
private:
std::string url;
std::string api_key;
std::string user_id;
//使用百度语音识别speech自带的Http Client发起相关请求
aip::HttpClient client;
private:
bool IsCodeLegal(int code)
{
bool result = false;
switch(code){
case 5000:
case 6000:
case 4000:
case 4001:
case 4002:
case 4003:
case 4005:
case 4007:
case 4100:
case 4200:
case 4300:
case 4400:
case 4500:
case 4600:
case 4602:
case 7002:
case 8008:
case 0:
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["repType"] = 0;
root["perception"] = item;
item.clear();
item["apiKey"] = api_key;
item["userId"] = user_id;
root["userInfo"] = item;
std::unique_ptr sw(wb.newStreamWriter());
sw->write(root, &ss);
std::string json_string = ss.str();
// std::cout << "debug: " << json_string << std::endl;
return json_string;
}
//请求图灵
std::string RequestTL(std::string &request_json)
{
std::string response;
int status_code = this->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:"< 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 << "reponse code error" << std::endl;
return "";
}
Json::Value item = root["result"][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 = "4c7e0cfef1c74e41969b8476bf6fab7c";
this->user_id = id;
}
std::string Talk(std::string message)
{
std::string json = MessageToJson(message);
std::string response = RequestTL(json);
std::string echo_message = JsonToEchoMessage(response);
return echo_message;
}
~Robot()
{
}
};
访问百度语音识别类
class SpeechRec{
private:
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 = "16945671";
api_key = "D3uRSK2xh7Ur5de5F4DLvRb7";
secret_key = "wAiZYUnbnLmgs2GbGXSdGLx7yIDpuzlx";
client = new aip::Speech(app_id, api_key, secret_key);
}
//语音识别Automatic Speech Recognition
bool ASR(std::string path, std::string &out)
{
std::map 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::cout << "Recognize error" << std::endl;
return false;
}
out = result["result"][0].asString();
return true;
}
//语音合成Text To Speech
bool TTS(std::string message)
{
bool ret;
std::ofstream ofile;
std::string ret_file;
std::map options;
options["spd"] = "5";
options["pit"] = "5";
options["vol"] = "5";
options["per"] = "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;
ret = true;
}
else{
std::cerr << result.toStyledString() << std::endl;
ret = false;
}
ofile.close();
return ret;
}
~SpeechRec()
{
}
};
完成核心逻辑类
class Jarvis{
private:
Robot rt;
SpeechRec sr;
std::unordered_map commands;
private:
bool Record()
{
Util::PrintStart("录音中: ");
//std::cout << "debug: " << "Record..." << std::endl;
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: Record... done" << std::endl;
return ret;
}
bool play()
{
std::string command = "cvlc --play-and-exit ";
command += TTS_PATH;
return Util::Exec(command, false);
}
public:
Jarvis()
{
}
//加载命令执行配置文件
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));
}
std::cerr << "Load command etc 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);
dup2(fd, 2);
#endif
volatile bool quit = false;
while(!quit){
if(Record()){
std::string message;
if(sr.ASR(ASR_PATH, message)){
//1.command
std::string cmd="";
if(IsCommand(message, cmd)){
std::cout << "[Jarvis@localhost]$ " << cmd << std::endl;
Util::Exec(cmd, true);
continue;
}
//2.tuling
std::cout << "我# " << message << std::endl;
if(message == "你走吧。"){
std::string quit_message = "那我走了!";
std::cout << "Jarvis# " << quit_message << std::endl;
if(sr.TTS(quit_message)){
play();
}
exit(0);
}
std::string echo = rt.Talk(message);
std::cout << "Jarvis# " << echo << std::endl;
if(sr.TTS(echo)){
play();
}
}
}
else{
std::cerr << "Record error..." << std::endl;
}
}
#ifdef _LOG_
close(fd);
#endif
}
~Jarvis()
{
}
调用逻辑:
#include "Jarvis.hpp"
using namespace std;
int main()
{
Jarvis *js = new Jarvis();
if(!js->LoadEtc()){
return 1;
}
js->Run();
return 0;
}
makefile文件
CC=g++
bin=Jarvis
src=Jarvis.cc
INCLUDE=-Ispeech
LIB=-ljsoncpp -lcurl -lcrypto -lpthread
FLAGS=-D_LOG_
$(bin):$(src)
$(CC) -o $@ $^ $(INCLUDE) $(LIB)
.PHONY:clean
clean:
rm -f $(bin)