目录
准备工作
最近学了QT,就想着做一下项目练练手。所以,我想了对语音聊天机器人比较有兴趣,就打算写一个语音聊天机器人。上网查了一圈,发现青云客聊天机器人是免费且不限次数的,比图灵机器人一天100次更好用点,语音识别就是基于百度语音API识别的,还有百度语音合成。
青云客:青云客智能聊天机器人API
百度智能云:百度智能云-云智一体深入产业
青云客API可以直接获取,目前不用登陆注册,直接在首页最下面就可以看到需要的API接口信息;
百度智能云需要注册登录,在语音技术可以找到需要的语音识别和语音合成API接口,对新用户好像有免费的次数可以领取,足够自己玩了。
效果展示
自己做的页面效果,很粗糙(请忽视)。
额,这个怎么说呢,可能是识别不够精确,也可能是我普通发不够标准吧(流汗),所以我的设置没有是录完音就直接发送给青云客机器人,而是转换成文本放在了发送框,以便修改内容。
代码展示
.pro文件需要加上 network 和 multimedia
这是语音按钮按下的代码:可能是会有延迟,所以每次按下按钮就开始讲话的话,总是不能够识别,所以最好等待一下再开始。
下面的starttaudio()是自己封装的开始录音的函数,需要获取录音设备,将录好的音频存入文件中。
void Form::on_yuyinbtn_pressed()
{
/*
按下录音时,应等待一秒,并清晰说出内容,否则不容易识别
*/
ui->yuyinbtn->setText("录音中...");
ui->yuyinbtn->setStyleSheet("color:rbg(122,125,25);");
startaudio("my.pcm");
}
void Form::startaudio(QString filename)
{
//获取默认的录音设备;
QAudioDeviceInfo device = QAudioDeviceInfo::defaultInputDevice();
if(device.isNull()){//录音设备不存在
QMessageBox::warning(this, "QAudioDevice", "录音设备不存在");
}
else{
// 音频编码要求、
QAudioFormat my_format;
// 设置采样频率
my_format.setSampleRate(16000);
// 设置声道数
my_format.setChannelCount(1);
// 设置位深
my_format.setSampleSize(16);
// 设置编码
my_format.setCodec("audio/pcm");
// 判断设备是否支持该格式,如果不支持则获取最近的格式
if(!device.isFormatSupported(my_format)){
my_format = device.nearestFormat(my_format);
}
my_file ->setFileName(filename);
my_file->open(QIODevice::ReadWrite);
// 创建录音对象 ;
my_audio = new QAudioInput(my_format, this);
my_audio->start(my_file);//把音频数据保存在文件里;
}
}
语音按钮松开时,结束录音,关闭文件。
void Form::stopaudio()
{
my_audio->stop();
my_file->close();
delete my_file;
my_file = NULL;
}
按钮松开后,开始获取百度语音的access_token;其中client_id和client_secret都是在百度上获取的唯一标识码,直接复制就好了。另,百度推荐的是post方式,我这里直接就用get,最好还是用post比较好。
void Form::on_yuyinbtn_released()
{
// 停止录音
stopaudio();
ui->yuyinbtn->setText("");
//获取Access_token
QString tokenurl = "https://aip.baidubce.com/oauth/2.0/token?";
QByteArray url =QString("client_id=%1&client_secret=%2&grant_type=client_credentials")
.arg(client_id)
.arg(client_secret).toLatin1();
QNetworkRequest requestdata;
requestdata.setUrl(QUrl(tokenurl+url));
http = new QNetworkAccessManager(this);
connect(http,SIGNAL(finished(QNetworkReply*)),this,SLOT(httpgetjson(QNetworkReply*)));
http->get(requestdata);
}
/*获取百度access_token*/
void Form::httpgetjson(QNetworkReply *reply)
{
// qDebug()<<"reply1"<<reply;
QByteArray byte = reply->readAll();
QByteArray bytearry = QString(byte).toUtf8();
QJsonDocument objdocument = QJsonDocument::fromJson(bytearry);
if(objdocument.isObject()){
QJsonObject json = objdocument.object();
if(json.contains("access_token")){
QJsonValue jsonval = json.value("access_token");
access_token = jsonval.toString();
//qDebug()<<"access_token"<<access_token;
}
}
}
接下来是语音识别的代码,其中json参数是按照百度技术文档填写的,有些必填项,有些是选填,看自己选择就好啦,附上百度语音识别的参数图:
//打开音频文件
my_file = new QFile(this);
my_file->setFileName("my.pcm");
my_file->open(QIODevice::ReadWrite);
if(my_file->isOpen() == false){
qDebug()<<"音频文件打开失败";
delete my_file;
return;
}
//语音识别
QString baiduspeechUrl = "http://vop.baidu.com/server_api";
QNetworkRequest reques;
reques.setUrl(QUrl(baiduspeechUrl));
//设置头部
reques.setRawHeader("Content-Type", "application/json");
QByteArray file = my_file->readAll();
QString speak = file.toBase64();
my_file->close();
QString macaddress = gethostMACaddress();//获取本机mac地址
//配置上传的json参数
QJsonObject json;
json["format"] = "pcm";
json["rate"] = 16000;
json["dev_pid"] = 1537;
json["channel"] = 1;
json["token"] = access_token;
json["cuid"] = macaddress;
json["len"] = file.size(); //语音文字真是的byte长度
json["speech"] = speak;
manager = new QNetworkAccessManager(this);
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(sendspeech(QNetworkReply*)));
QByteArray data = QJsonDocument(json).toJson();
manager->post(reques,data);
其实这部分json格式是可以和上面获取access_token合并为一个函数的,我当时是因为是想要测试一下,所以就改成两个了,后面也因为懒就没有改回去了。
/*获取语音识别文本*/
void Form::sendspeech(QNetworkReply *reply)
{
// qDebug()<<"reply"<<reply;
QByteArray byte = reply->readAll();
QByteArray bytearry = QString(byte).toUtf8();
QJsonDocument objdocument = QJsonDocument::fromJson(bytearry);
//返回有错误信息
if(objdocument.isObject()){
QJsonObject json = objdocument.object();
if(json.contains("err_no")){
QJsonValue value = json.take("err_no");
if(value.isDouble()){
int ver = value.toVariant().toInt();
if(3301 == ver){
QMessageBox::warning(this,"错误","识别失败,请重新录音","确认");
}
}
}
//返回文本结果
if(json.contains("result")){
QString text = json["result"].toArray()[0].toString();
//qDebug()<<"text"<<text;
ui->sendmsg->setText(text);
}
}
}
文本方式发送青云客机器人,获取对话内容(可以直接get):
/*文本方式将对话发送给青云客机器人*/
void Form::on_sendbtn_clicked()
{
//发送文本数据给青云客机器人
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
QNetworkRequest request;
request.setUrl(QUrl("http://api.qingyunke.com/api.php?key=free&appid=0&msg="+ui->sendmsg->toPlainText()));
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(getrubotchat(QNetworkReply*)));
manager->get(request);
//获取本地时间
QTime time = QTime::currentTime();
//hh:mm:ss ap 设置时间格式
QString times = time.toString("hh:mm:ss ap");
//文本中显示发送的时间
ui->secvmsg->append(times);
ui->secvmsg->append("我:"+ui->sendmsg->toPlainText());
}
/*获取机器人对话内容,显示在文本框中*/
void Form::getrubotchat(QNetworkReply *reply)
{
//获得机器人聊天内容
QString recvdata = reply->readAll();
QString getrubotjson;
QJsonParseError Error;
QJsonDocument json = QJsonDocument::fromJson(recvdata.toUtf8(),&Error);
//判断是否出错
if(Error.error == QJsonParseError::NoError){
if(json.isObject()){
//获得文档对象内容
QJsonObject jsonobj = json.object();
if(jsonobj.contains("content")){
//取得text文本
QJsonValue jsontext = jsonobj.take("content");
if(jsontext.isString()){
getrubotjson = jsontext.toVariant().toString();
getrubotjson.replace("br","\n");
//qDebug()<<"getrubot"<<getrubotjson;
//这个text是全局的,为了将机器人的文本对话内容语音合成出来
text = getrubotjson;
//qDebug()<<"内容:"<<text;
}
}
}
}
reply->deleteLater();
//也是显示接受的本地时间
QTime time = QTime::currentTime();
QString times = time.toString("hh:mm:ss ap");
ui->secvmsg->append(times);
ui->secvmsg->append("菲菲:"+getrubotjson);
playvoice();
ui->sendmsg->clear();
}
这是通过百度语音合成直接语音播放文本内容的,也是直接根据百度技术文档填写参数就好啦。
/*播放聊天文本音频*/
void Form::playvoice()
{
//百度语音合成body参数
QString url = "https://tsn.baidu.com/text2audio?";
QUrlQuery query;
query.addQueryItem("tex", QUrl::toPercentEncoding(text));
query.addQueryItem("tok", QUrl::toPercentEncoding(access_token));
QString mac = gethostMACaddress().replace(":", "");
query.addQueryItem("cuid", mac);
query.addQueryItem("ctp", "1");
query.addQueryItem("lan", "zh");
query.addQueryItem("vol", "7");
query.addQueryItem("per", "0");
query.addQueryItem("aue","3");
url.append(QString(query.toString()));
player = new QMediaPlayer(this);
player->setMedia(QUrl(url));
//qDebug()<< player->error();
player->play();
}
也可以直接使用QTextToSpeech来本地合成语音,更简单。只需要在.pro文件中加上texttospeech
和头文件#include<QTextToSpeech>,就可以直接传入文本内容并play()播放了,里面也有很多音量,音速和音色等的设置,这就需要自己查阅了,我就直接new播放了。
QTextToSpeech *myspeech= new QTextToSpeech();
myspeech->say(text);
补充一个上面获取本机mac地址:
/*获取本机mac地址*/
QString Form::gethostMACaddress()
{
//获取所有网络接口列表
QList<QNetworkInterface>nets = QNetworkInterface::allInterfaces();
int cnet = nets.count();
QString MACaddress;
for(int i= 0;i<cnet;i++){
//如果此网络接口被激活并且正在运行也不是回环地址,并且是IPV4地址,则就是需要的MAC地址
if(nets[i].flags().testFlag(QNetworkInterface::IsUp)
&& nets[i].flags().testFlag(QNetworkInterface::IsRunning)
&& !nets[i].flags().testFlag(QNetworkInterface::IsLoopBack)){
for(int j=0;j<nets[i].addressEntries().size();j++){
//该mac的IP地址不能是127.0.0.1,且需要ipv4地址
//QHostAddress::LocalHost:主机的本机地址相当于127.0.0.1
//protocol():返回网络层协议,QAbstractSocket::IPv4Protocol:枚举,是IPv4协议
if(nets[i].addressEntries().at(j).ip()!= QHostAddress::LocalHost
&& nets[i].addressEntries().at(j).ip().protocol() == QAbstractSocket::IPv4Protocol){
MACaddress = nets[i].hardwareAddress();
}
}
}
}
// qDebug()<<"mac"<<MACaddress;
return MACaddress;
}
完整文件
.h文件
#ifndef FORM_H
#define FORM_H
#include <QWidget>
#include <QMessageBox>
#include <QString>
#include <QAudioFormat> //存储音频信息
#include <QAudioDeviceInfo>//获取录音设备信息
#include <QAudioInput> //从音频外接设备获取音频
#include<QMediaPlayer> //音频播放
#include<QTextToSpeech> //本地语音合成
#include<QUrlQuery>
#include <QNetworkAccessManager>
#include<QNetworkReply>
#include<QNetworkRequest>
#include <QMetaEnum>
#include <QFile>
#include <QJsonDocument> //读写json文本
#include <QJsonObject> //json对象
#include <QJsonArray> //数组
#include <QJsonValue> //value值
#include<QTime> //获取本地时间
#include<QNetworkInterface> //获得主机网络接口列表
namespace Ui {
class Form;
}
class Form : public QWidget
{
Q_OBJECT
public:
explicit Form(QWidget *parent = 0);
~Form();
signals:
void backform();
private slots:
void on_yuyinbtn_pressed();
void on_yuyinbtn_released();
void startaudio(QString filename);
void stopaudio();
void httpgetjson(QNetworkReply*);
void sendspeech(QNetworkReply *);
void playvoice();
QString gethostMACaddress();
void on_sendbtn_clicked();
void getrubotchat(QNetworkReply*);
private:
Ui::Form *ui;
QTcpSocket*socket;
QAudioInput *my_audio;
QFile *my_file;
QNetworkAccessManager *http;
QNetworkAccessManager *manager;
//QMediaPlayer *player;
QTextToSpeech *myspeech;
QString access_token;
QString text;
//获取Access_token的准备,直接复制过来就好了
const QString client_id = " ";
const QString client_secret = " ";
};
#endif // FORM_H
完整.cpp文件
#include "form.h"
#include "ui_form.h"
/*构造函数里面都是一些样式设计,不是主要代码*/
Form::Form(QWidget *parent) :
QWidget(parent),
ui(new Ui::Form)
{
ui->setupUi(this);
this->setWindowTitle("语音聊天机器人");
//自定义背景
QPixmap pixmap2 = QPixmap("back.png").scaled(this->size());
QPalette palette2(this->palette());
palette2.setBrush(QPalette::Window,QBrush(pixmap2));
this->setPalette(palette2);
//聊天框样式设置
ui->secvmsg->setStyleSheet("backgroud-color:rgba(225,225,224,50%);border:5px solid #B362ca;color:#80126a;");
ui->secvmsg->setAlignment(Qt::AlignLeft);
ui->sendmsg->setStyleSheet("backgroud-color:rgba(225,225,224,50%);border:5px solid #B362ca;color:#80126a;");
ui->sendmsg->setAlignment(Qt::AlignLeft);
//设置接受框不会清空前一次内容
ui->secvmsg->insertPlainText("");
//设置按钮图标
QPixmap pixmap3 = QPixmap("yuyin.png").scaled(ui->yuyinbtn->size());
ui->yuyinbtn->setIcon(QIcon(pixmap3));
ui->yuyinbtn->setIconSize(QSize(ui->yuyinbtn->width(),ui->yuyinbtn->height()));
ui->yuyinbtn->setFlat(true);
ui->yuyinbtn->setStyleSheet("border:0px");
//设置按钮样式
ui->sendbtn->setStyleSheet(//鼠标正常样式
"QPushButton{"
"backgroud-color:#8221e9;"//按钮背景颜色
"border-style:outset;" //边框样式
"border-width:6px;" //边框样式
"border-radius:20px;" //边框圆角
"border-color:#B362ca90;" //边框颜色
"color:white;"
"}"
//鼠标悬停样式
"QPushButton:hover{"
"backgroud-color:#21e2e9;"
"border-color:#8221e9;"
"color:white;"
"}"
//鼠标点击样式
"QPushButton:pressed{"
"backgroud-color:#B362ca99;"
"border-color:#B362ca;"
"border-style:inset;"
"color:#B362ca90;"
"}");
my_file = new QFile;
}
Form::~Form()
{
delete ui;
}
void Form::on_yuyinbtn_pressed()
{
/*
按下录音时,应等待一秒,并清晰说出内容,否则不容易识别
*/
ui->yuyinbtn->setText("录音中...");
ui->yuyinbtn->setStyleSheet("color:rbg(122,125,25);");
startaudio("my.pcm");
}
void Form::on_yuyinbtn_released()
{
// 停止录音
stopaudio();
ui->yuyinbtn->setText("");
//获取Access_token
QString tokenurl = "https://aip.baidubce.com/oauth/2.0/token?";
QByteArray url =QString("client_id=%1&client_secret=%2&grant_type=client_credentials")
.arg(client_id)
.arg(client_secret).toLatin1();
QNetworkRequest requestdata;
requestdata.setUrl(QUrl(tokenurl+url));
http = new QNetworkAccessManager(this);
connect(http,SIGNAL(finished(QNetworkReply*)),this,SLOT(httpgetjson(QNetworkReply*)));
http->get(requestdata);
//打开音频文件
my_file = new QFile(this);
my_file->setFileName("my.pcm");
my_file->open(QIODevice::ReadWrite);
if(my_file->isOpen() == false){
qDebug()<<"音频文件打开失败";
delete my_file;
return;
}
//语音识别
QString baiduspeechUrl = "http://vop.baidu.com/server_api";
QNetworkRequest reques;
reques.setUrl(QUrl(baiduspeechUrl));
//设置头部
reques.setRawHeader("Content-Type", "application/json");
QByteArray file = my_file->readAll();
QString speak = file.toBase64();
my_file->close();
QString macaddress = gethostMACaddress();//获取本机mac地址
//配置上传的json参数
QJsonObject json;
json["format"] = "pcm";
json["rate"] = 16000;
json["dev_pid"] = 1537;
json["channel"] = 1;
json["token"] = access_token;
json["cuid"] = macaddress;
json["len"] = file.size(); //语音文字真是的byte长度
json["speech"] = speak;
manager = new QNetworkAccessManager(this);
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(sendspeech(QNetworkReply*)));
QByteArray data = QJsonDocument(json).toJson();
manager->post(reques,data);
}
void Form::startaudio(QString filename)
{
//获取默认的录音设备;
QAudioDeviceInfo device = QAudioDeviceInfo::defaultInputDevice();
if(device.isNull()){//录音设备不存在
QMessageBox::warning(this, "QAudioDevice", "录音设备不存在");
}
else{
// 音频编码要求、
QAudioFormat my_format;
// 设置采样频率
my_format.setSampleRate(16000);
// 设置声道数
my_format.setChannelCount(1);
// 设置位深
my_format.setSampleSize(16);
// 设置编码
my_format.setCodec("audio/pcm");
// 判断设备是否支持该格式,如果不支持则获取最近的格式
if(!device.isFormatSupported(my_format)){
my_format = device.nearestFormat(my_format);
}
my_file ->setFileName(filename);
my_file->open(QIODevice::ReadWrite);
// 创建录音对象 ;
my_audio = new QAudioInput(my_format, this);
my_audio->start(my_file);//把音频数据保存在文件里;
}
}
void Form::stopaudio()
{
my_audio->stop();
my_file->close();
delete my_file;
my_file = NULL;
}
/*获取百度access_token*/
void Form::httpgetjson(QNetworkReply *reply)
{
// qDebug()<<"reply1"<<reply;
QByteArray byte = reply->readAll();
QByteArray bytearry = QString(byte).toUtf8();
QJsonDocument objdocument = QJsonDocument::fromJson(bytearry);
if(objdocument.isObject()){
QJsonObject json = objdocument.object();
if(json.contains("access_token")){
QJsonValue jsonval = json.value("access_token");
access_token = jsonval.toString();
//qDebug()<<"access_token"<<access_token;
}
}
}
/*获取语音识别文本*/
void Form::sendspeech(QNetworkReply *reply)
{
// qDebug()<<"reply"<<reply;
QByteArray byte = reply->readAll();
QByteArray bytearry = QString(byte).toUtf8();
QJsonDocument objdocument = QJsonDocument::fromJson(bytearry);
//返回有错误信息
if(objdocument.isObject()){
QJsonObject json = objdocument.object();
if(json.contains("err_no")){
QJsonValue value = json.take("err_no");
if(value.isDouble()){
int ver = value.toVariant().toInt();
if(3301 == ver){
QMessageBox::warning(this,"错误","识别失败,请重新录音","确认");
}
}
}
//返回文本结果
if(json.contains("result")){
QString text = json["result"].toArray()[0].toString();
//qDebug()<<"text"<<text;
ui->sendmsg->setText(text);
}
}
}
/*播放聊天文本音频*/
void Form::playvoice()
{
// //百度语音合成body参数
// QString url = "https://tsn.baidu.com/text2audio?";
// QUrlQuery query;
// query.addQueryItem("tex", QUrl::toPercentEncoding(text));
// query.addQueryItem("tok", QUrl::toPercentEncoding(access_token));
// QString mac = gethostMACaddress().replace(":", "");
// query.addQueryItem("cuid", mac);
// query.addQueryItem("ctp", "1");
// query.addQueryItem("lan", "zh");
// query.addQueryItem("vol", "7");
// query.addQueryItem("per", "0");
// query.addQueryItem("aue","3");
// url.append(QString(query.toString()));
// player = new QMediaPlayer(this);
// player->setMedia(QUrl(url));
// //qDebug()<< player->error();
// player->play();
myspeech = new QTextToSpeech();
myspeech->say(text);
}
/*获取本机mac地址*/
QString Form::gethostMACaddress()
{
//获取所有网络接口列表
QList<QNetworkInterface>nets = QNetworkInterface::allInterfaces();
int cnet = nets.count();
QString MACaddress;
for(int i= 0;i<cnet;i++){
//如果此网络接口被激活并且正在运行也不是回环地址,并且是IPV4地址,则就是需要的MAC地址
if(nets[i].flags().testFlag(QNetworkInterface::IsUp)
&& nets[i].flags().testFlag(QNetworkInterface::IsRunning)
&& !nets[i].flags().testFlag(QNetworkInterface::IsLoopBack)){
for(int j=0;j<nets[i].addressEntries().size();j++){
//该mac的IP地址不能是127.0.0.1,且需要ipv4地址
//QHostAddress::LocalHost:主机的本机地址相当于127.0.0.1
//protocol():返回网络层协议,QAbstractSocket::IPv4Protocol:枚举,是IPv4协议
if(nets[i].addressEntries().at(j).ip()!= QHostAddress::LocalHost
&& nets[i].addressEntries().at(j).ip().protocol() == QAbstractSocket::IPv4Protocol){
MACaddress = nets[i].hardwareAddress();
}
}
}
}
// qDebug()<<"mac"<<MACaddress;
return MACaddress;
}
/*文本方式将对话发送给青云客机器人*/
void Form::on_sendbtn_clicked()
{
//发送文本数据给机器人
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
QNetworkRequest request;
//青云客API,免注册免登录,可以不限次数且免费 yyds(反应有点慢,可以接受)
request.setUrl(QUrl("http://api.qingyunke.com/api.php?key=free&appid=0&msg="+ui->sendmsg->toPlainText()));
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(getrubotchat(QNetworkReply*)));
manager->get(request);
//获取本地时间
QTime time = QTime::currentTime();
//hh:mm:ss ap 时间格式
QString times = time.toString("hh:mm:ss ap");
ui->secvmsg->append(times);
ui->secvmsg->append("我:"+ui->sendmsg->toPlainText());
}
/*获取机器人对话内容,显示在文本框中*/
void Form::getrubotchat(QNetworkReply *reply)
{
//槽函数,获得机器人聊天内容
QString recvdata = reply->readAll();
QString getrubotjson;
QJsonParseError Error;
QJsonDocument json = QJsonDocument::fromJson(recvdata.toUtf8(),&Error);
//判断是否出错
if(Error.error == QJsonParseError::NoError){
if(json.isObject()){
//获得文档对象内容
QJsonObject jsonobj = json.object();
if(jsonobj.contains("content")){
//取得text文本
QJsonValue jsontext = jsonobj.take("content");
if(jsontext.isString()){
getrubotjson = jsontext.toVariant().toString();
getrubotjson.replace("br","\n");
//qDebug()<<"getrubot"<<getrubotjson;
text = getrubotjson;
qDebug()<<"内容:"<<text;
}
}
}
}
reply->deleteLater();
QTime time = QTime::currentTime();
QString times = time.toString("hh:mm:ss ap");
ui->secvmsg->append(times);
ui->secvmsg->append("菲菲:"+getrubotjson);
playvoice();
ui->sendmsg->clear();
}
参考文章: