QT实战百度语音识别

前言

随着学习的深入,感觉愈发缺乏满足感。刚好看到微信语音转文字的功能,经网上查询,发现可以使用 QT + 百度语音识别技术 实现这一功能。当然,由于使用的 QT 和 百度语音识别,那么看不到一些具体的底层实现,但操作起来相对比较简单。俗话说:“没吃过猪肉,还没见过猪跑?”,我打算先看看别人已有的技术,搬过来跑一下,然后再进行深入学习,同时也可以复习一下 QT 相关知识。文章如有写错或者代码可优化,欢迎大家指正!

QT 采集麦克风 pcm 音频裸数据

基础知识

PCM(Pulse Code Modulation,脉冲编码调制)⾳频数据是未经压缩的⾳频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准数字⾳频数据。
描述PCM数据的6个参数:

  1. Sample Rate : 采样频率。8kHz(电话)、44.1kHz(CD)、48kHz(DVD)。
  2. Sample Size : 量化位数。通常该值为16-bit。
  3. Number of Channels : 通道个数。常⻅的⾳频有⽴体声(stereo)和单声道(mono)两种类型,⽴体声包含左声道和右声道。另外还有环绕⽴体声等其它不太常⽤的类型。
  4. Sign : 表示样本数据是否是有符号位,⽐如⽤⼀字节表示的样本数据,有符号的话表示范围为-128 ~127,⽆符号是0 ~ 255。有符号位16bits数据取值范围为-32768~32767。
  5. Byte Ordering : 字节序。字节序是little-endian还是big-endian。通常均为little-endian。
  6. Integer Or Floating Point : 整形或浮点型。⼤多数格式的PCM样本数据使⽤整形表示,⽽在⼀些对精度要求⾼的应⽤⽅⾯,使⽤浮点类型表示PCM样本数据(浮点数 float值域为 [-1.0, 1.0])。
环境配置

第一步: 新建一个QWidget项目
在这里插入图片描述

第二步: 项目名与存放路径自选(然后一直下一步)
第三步: 在.pro文件中添加模块

QT += multimedia

第四步: 新建一个C++ Class(因为采集麦克风只是一个小功能,我们还有其他的功能),名字可自取。这里类名我起的是 AudioCapture

代码

audiocapture.h
#ifndef AUDIOCAPTURE_H
#define AUDIOCAPTURE_H#include <QObject>
#include <QAudioInput>
#include <QFile>
#include <QMessageBox>class AudioCapture : public QObject
{Q_OBJECT
public:explicit AudioCapture(QObject *parent = nullptr);void startCapture(QString filename);    //开始录音,文件名由调用者传入void stopCapture();                     //结束录音~AudioCapture();                        //析构函数,释放相关资源
signals:
private:QAudioInput *pAudioInput;               //录音对象QFile       *pFile;                     //存取文件
};#endif // AUDIOCAPTURE_H
audiocapture.cpp
#include "audiocapture.h"AudioCapture::AudioCapture(QObject *parent) : QObject(parent)
{//初始化pAudioInput = nullptr;pFile = nullptr;
}//开始录音
void AudioCapture::startCapture(QString filename)
{//打开默认的音频输入设备QAudioDeviceInfo audioDeviceInfo = QAudioDeviceInfo::defaultInputDevice();//判断本地是否有录音设备if(audioDeviceInfo.isNull() == false){/* 创建文件并打开 */pFile = new QFile;pFile->setFileName(filename);pFile->open(QIODevice::WriteOnly | QIODevice::Truncate);// 设置音频文件格式QAudioFormat format;// 设置采样频率,常见的有16000、44100、48000format.setSampleRate(16000);// 设置通道数,单声道、双声道、5.1声道format.setChannelCount(1);// 设置每次采样得到的样本数据位值,8位、16位format.setSampleSize(16);// 设置编码方法format.setCodec("audio/pcm");// 判断当前设备设置是否支持该音频格式if(audioDeviceInfo.isFormatSupported(format) == NULL){format = audioDeviceInfo.nearestFormat(format);}// 创建录音对象pAudioInput = new QAudioInput(format, this);// 开始录音pAudioInput->start(pFile);}else{// 没有录音设备QMessageBox::information(NULL, tr("Record"), tr("Current No Record Device"));}
}void AudioCapture::stopCapture()
{if(pAudioInput != NULL){// 停止录音pAudioInput->stop();}if(pFile != NULL){// 关闭文件pFile->close();delete pFile;pFile = nullptr;}
}AudioCapture::~AudioCapture()
{//释放资源if(pAudioInput != nullptr){delete pAudioInput;pAudioInput = nullptr;}if(pFile != nullptr){delete pFile;pFile = nullptr;}
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include "audiocapture.h"QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_startPtn_clicked();     // 点击Start按钮后触发的槽函数void on_stopPtn_clicked();      // 点击Stop按钮后触发的槽函数private:Ui::Widget *ui;                 //操作界面上的相关控件AudioCapture myAudioCapture;    //录音功能封装对象
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);ui->startPtn->setEnabled(true);     //Start按钮初始化可用ui->stopPtn->setEnabled(false);     //Stop按钮初始化不可用
}Widget::~Widget()
{delete ui;
}//点击Start按钮后触发的槽函数
void Widget::on_startPtn_clicked()
{QString filepath = ui->filepath->text();    //获取用户输入地址/* 判断用户是否输入地址 */if(filepath == ""){QMessageBox::information(NULL, "information", "Please input the filepath to save!");return;}/* 点击Start后禁用Start,开放Stop按钮 */ui->startPtn->setEnabled(false);ui->stopPtn->setEnabled(true);myAudioCapture.startCapture(filepath);  //开始录音
}//点击Stop按钮后触发的槽函数
void Widget::on_stopPtn_clicked()
{/* 点击Stop后禁用Stop,开放Start按钮 */ui->startPtn->setEnabled(true);ui->stopPtn->setEnabled(false);myAudioCapture.stopCapture();           //结束录音
}
运行时界面UI(仅供测试,不够美观)

在这里插入图片描述

播放 pcm 数据

由于vlc播放器无法直接播放pcm音频裸数据,这里使用ffplay来播放(也可使用代码播放)

ffplay -f s16le -ar 16000 -ac 1 -i D:\\1.pcm

这里的参数设置需与代码中的设置一样,否则音效不对。

使用 QAudioOutput 来播放 pcm 音频数据

主要代码

//设置音频输出格式
QAudioFormat fmt;//设置采样率
fmt.setSampleRate(44100);//设置采样位数
fmt.setSampleSize(16);//设置声道数
fmt.setChannelCount(1);//设置解码方式
fmt.setCodec("audio/pcm");// 设定字节序,以小端模式播放音频文件
fmt.setByteOrder(QAudioFormat::LittleEndian);// 设定采样类型。根据采样位数来设定。
fmt.setSampleType(QAudioFormat::UnSignedInt);// 创建QAudioOutput对象并初始化
QAudioOutput *out = new QAudioOutput(fmt);// 调用start函数后,返回QIODevice对象的地址
QIODevice *io = out->start();//获取设备播放一个周期所需要的字节数
int size = out->periodSize();//创建缓冲区
char *buf = new char[size];//以二进制只读方式打开pcm文件
FILE *fp = fopen("d:/1.pcm", "rb");//判断是否读到末尾
while(!feof(fp))
{//判断空闲空间是否小于一个周期的大小,如果是则说明CPU处理速度太快,得等一等。if(out->bytesFree() < size){QThread::msleep(1);continue;}int len = fread(buf, 1, size, fp);//判断是否成功读入if(len <= 0){break;}//这里相当于写入到电脑声卡的缓冲区,接下来的工作由声卡完成,与我们无关io->write(buf, len);
}fclose(fp);    //关闭文件//资源释放
if(NULL != buf)
{delete buf;buf = NULL;
}if(NULL != out)
{delete out;out = NULL;
}
语音识别

百度智能云网址: https://cloud.baidu.com/product/speech.html?track=cf3e1b9d08c41e54e7f0ace5828291cce549454e8c470208

第一步: 点击右上角控制台,并完成登录
在这里插入图片描述

第二步: 点击右上角三条杠,然后选中语音技术
在这里插入图片描述

第三步: 概览中点击免费尝鲜
在这里插入图片描述

第四步: 选中短语音识别-普通话,然后左下角点击0元领取(这里我已经领过了,所以没有这个选项了)
在这里插入图片描述

第五步: 点击应用列表,然后创建应用,应用名称随意,应用归属选择个人即可,然后添加一些描述,创建即可(这里我昨天实验时创建过一个了)
在这里插入图片描述

第六步: 复制 APIKey 和 SecretKey
在这里插入图片描述

整个语音识别的逻辑分析

第一步: QT中使用QAudioInput进行麦克风采集pcm音频数据
第二步: 通过http的post方式将音频数据提交给百度后台进行语音识别
第三步: 百度返回识别后的数据,将数据显示到文本框中。

语音识别

网络请求主要代码

bool httppost::postMsg(QString url, QMap<QString, QString> headerdata, QByteArray requestData, QByteArray &replyData)
{//发送请求的对象QNetworkAccessManager manager;//请求对象QNetworkRequest request;request.setUrl(url);//设置请求参数QMapIterator<QString, QString> it(headerdata);while(it.hasNext()){it.next();request.setRawHeader(it.key().toLatin1(),it.value().toLatin1());}QNetworkReply *reply = manager.post(request, requestData);QEventLoop loop;//一旦服务器返回,reply会发出信号connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);loop.exec();//死循环,reply发出信号,结束循环//判断是否响应成功if(reply != nullptr && reply->error() == QNetworkReply::NoError){replyData = reply->readAll();qDebug() << replyData;return true;}else{qDebug() << "请求失败";return false;}
}

百度的接口相关设置

//获取access_token相关
const QString baiduTokenUrl = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%1&client_secret=%2&";
const QString client_id = 刚才复制的APIKey;
const QString client_secret = 刚才复制的SecretKey;//普通话测试
const QString baiduSpeechurl = "http://vop.baidu.com/server_api?dev_pid=1537&cuid=%1&token=%2";

语音识别主要代码

QString speechrecognition::speechIdentify(QString filename)
{//获取tokenQString tokenUrl = QString(baiduTokenUrl).arg(client_id).arg(client_secret);QMap<QString, QString> headers;headers.insert(QString("Content-Type"), QString("audio/pcm;rate=16000"));QByteArray requestdata;     //发送的内容QByteArray replydata;       //服务器返回的内容httppost httputil;          //封装的网络请求类bool success = httputil.postMsg(tokenUrl, headers, requestdata, replydata);//判断是否请求成功if(success){QString key = "access_token";//获取到access_token(通过json数据格式解析)accessToken = getJsonvalue(replydata, key);qDebug() << "----------------" << endl;qDebug() << accessToken << endl;}else    return "";//语言识别QString baiduSpeech = QString(baiduSpeechurl.arg("LAPTOP-71LN9B3Q").arg(accessToken));//把文件转化为QByteArrayQFile file;file.setFileName(filename);file.open(QIODevice::ReadOnly);requestdata = file.readAll();file.close();replydata.clear();//再次发起http请求bool result = httputil.postMsg(baiduSpeech, headers,requestdata,replydata);//判断是否请求成功if(result == true){QString key = "result";QString text = getJsonvalue(replydata,key);     //获取识别后的文字return text;}else{QMessageBox::warning(NULL, "识别提示", "识别失败");return "";}
}QString speechrecognition::getJsonvalue(QByteArray ba, QString key)
{QJsonParseError parseError;QJsonDocument jsonDocument = QJsonDocument::fromJson(ba, &parseError);if(parseError.error == QJsonParseError::NoError){if(jsonDocument.isObject()){//jsonDocument转化成json对象QJsonObject jsonObj = jsonDocument.object();//判断是否包含keyif(jsonObj.contains(key)){QJsonValue jsonVal = jsonObj.value(key);if(jsonVal.isString())  //字符串{return jsonVal.toString();}else if(jsonVal.isArray())  //数组{QJsonArray arr = jsonVal.toArray();QJsonValue jv = arr.at(0);return jv.toString();}}}}return "";
}
效果展示

当点击start按钮后,语音描述"很高兴和大家一起学习音视频"
点击stop后,文本框内显示文字"很高兴和大家一起学习音视频"
在这里插入图片描述

过程中的一些坑(个人遇到的主要的坑)
  1. 音频采样参数必须一致,否则百度只会识别出嗯嗯嗯等一系列奇怪的词语
  2. 注意采样频率目前百度语音识别支持16000,使用其他的如44100/48000等会报错
  3. 注意.pro文件中要添加network模块,否则根本发送不出去,也就是说百度根本收不到,奇怪的是QT没给我直接报错,虽然没有提示,但也还是一点一点写了,结果发现根本没发送出去。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/8506.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VALSE 2024 Workshop报告分享┆探索短视频生成与编辑的前沿技术

2024年视觉与学习青年学者研讨会&#xff08;VALSE 2024&#xff09;于5月5日到7日在重庆悦来国际会议中心举行。本公众号将全方位地对会议的热点进行报道&#xff0c;方便广大读者跟踪和了解人工智能的前沿理论和技术。欢迎广大读者对文章进行关注、阅读和转发。文章是对报告人…

Python高级编程-DJango1

Python高级编程 灵感并不是在逻辑思考的延长线上产生 而是在破除逻辑或常识的地方才有灵感 目录 Python高级编程 1.python学习之前的准备 ​编辑 2.DJango 开发网站 3.创建项目 4.&#xff44;&#xff4a;&#xff41;&#xff4e;&#xff47;项目结构介绍 &#xff11;&…

Linux-信号保存

1. 概念 进程执行信号的处理动作&#xff0c;称为 信号递达&#xff08;Delivery&#xff09; 信号从产生到递达之间的状态&#xff0c;称为 信号未决&#xff08;Pending&#xff09; 进程可以选择 阻塞&#xff08;Block&#xff09;某个信号 过程&#xff1a; 信号产生 ——…

linux上go项目打包与部署

1.第一步把项目打包 1.确保本地goland的操作系统为linux go env找到GOOS如果为window就修改为Linux 修改命令为 go env -w GOOSlinux2.打包 在项目根目录下输入 go build main.go然后项目根目录下会出现一个mian的二进制文件 3.上传包 将 main 程序包放到服务的目录下&…

鸿蒙开发接口Ability框架:【 (ServiceExtensionAbility)】

ServiceExtensionAbility ServiceExtensionAbility模块提供ServiceExtension服务扩展相关接口的能力。 说明&#xff1a; 本模块首批接口从API version 9开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 本模块接口仅可在Stage模型下使用。 导入…

【栈】Leetcode 1047. 删除字符串中的所有相邻重复项

题目讲解 1047. 删除字符串中的所有相邻重复项 算法讲解 使用栈这个数据结构&#xff0c;每一次入栈的时候观察此时的字符和当前栈顶字符是否相等&#xff0c;如相等&#xff1a;栈顶出栈&#xff1b;不相等&#xff1a;入栈 class Solution { public:string removeDuplica…

Linux-进程管理类命令实训

实训1&#xff1a;进程查看&#xff0c;终止&#xff0c;挂起及暂停等操作 1.使用ps命令显示所有用户的进程 2.在后台使用cat命令。查看进程cat&#xff0c;并杀死进程 3.使用top命令只显示某一用户的进程。 4.执行命令cat&#xff0c;把Ctrlz挂起进程&#xff0c;输入jobs命令…

「网络流 24 题」最小路径覆盖 【最小路径覆盖】

「网络流 24 题」最小路径覆盖 思路 具体可以看 这篇博客 对于有向无环图&#xff0c;我们只需要将假装将点裂成左点和右点&#xff08;实际没有裂开&#xff09;&#xff0c;然后连边&#xff1b; 在上面跑二分图最大匹配后&#xff0c;剩下没有匹配的左点就是终点&#xff…

python与java用途区别有哪些

区别&#xff1a; 1.Python比Java简单&#xff0c;学习成本低&#xff0c;开发效率高。 2.Java运行效率高于Python&#xff0c;尤其是纯Python开发的程序&#xff0c;效率极低。 3.Java相关资料多&#xff0c;尤其是中文资料。 4.Java版本比较稳定&#xff0c;Python2和3不…

【第14章】spring-mvc之ajax

文章目录 前言一、准备二、单个值1.前端2.后端3. 结果 三、对象1.前端2.后端3. 结果 四、JSON对象1.前端2.后端3. 结果 五、JSON数组1.前端2.后端3. 结果 总结 前言 AJAX&#xff08;Asynchronous JavaScript and XML&#xff09;是一种用于创建快速动态网页的技术&#xff0c…

学习和“劳动”相关的谚语,柯桥俄语培训

1. Бог труды́ лю́бит. 天道酬勤。 2. В ми́ре нет тру́дных дел, ну́жно лишь усе́рдие. 世上无难事,只怕有心人。 3. У́тро вечера мудренее. 一日之计在于晨。 4. Что посе́ешь,…

cesium 雷达遮罩(电弧球效果)

cesium 雷达遮罩(电弧球效果) 以下为源码直接复制可用 1、实现思路 通过修改“material”材质来实现轨迹球效果 2、代码示例 2.1 index.html <!DOCTYPE html> <html lang="en"><head><!

【初阶数据结构】栈

目录 栈的概念及结构栈的实现栈的结构栈的初始化栈的销毁入栈出栈取栈顶元素判断栈是否为空取栈中元素个数代码测试 完整代码Stack.hStack.ctest.c 栈的概念及结构 栈&#xff1a;是一种特殊的线性表&#xff0c;它只允许在固定的一端进行插入和删除元素的操作。   栈顶&…

[MDK] 介绍STM32使用C和C++混合编程的方法

目录 [MDK] 介绍STM32使用C和C混合编程的方法前言业务场景步骤1基础工程步骤2写代码步骤3添加cpp文件步骤4配置与编译上机现象后记 [MDK] 介绍STM32使用C和C混合编程的方法 前言 搞单片机编程大多数还是使用MDK编程&#xff0c;自己对MDK这个软件也比较熟悉&#xff0c;在网络…

【通信】电子科协通信专题

数字通信 最直观的通信方式-基带通信 问题&#xff1a;①无限大的带宽②天线体积

java回调机制

目录 一、简介二、示例2.1 同步回调2.2 异步回调2.3 二者区别 三、应用场景 一、简介 在Java中&#xff0c;回调是一种常见的编程模式&#xff0c;它允许一个对象将某个方法作为参数传递给另一个对象&#xff0c;以便在适当的时候调用该方法。 以类A调用类B方法为例: 在类A中…

KMP + Compose 跨平台 Android IOS 实战入门

KMP&#xff08;Kotlin Multiplatform&#xff09;是一种面向移动端开发的跨平台框架&#xff0c;使用 Kotlin 语言编写&#xff0c;可实现在 Android 和 iOS 平台上共享代码和逻辑。通过 KMP 框架&#xff0c;我们可以编写一次代码&#xff0c;然后在不同的平台上进行部署和运…

python能够干什么?

python有哪些用途&#xff1f; Python是一种高级编程语言&#xff0c;它被广泛用于各种不同的领域。以下是Python的一些常见用途&#xff1a; 网络应用开发&#xff1a;Python可以用于编写Web应用程序、API、爬虫、网络服务器等。数据科学和机器学习&#xff1a;Python拥有许…

深究muduo网络库的Buffer类!!!

最近在学习了muduo库的Buffer类&#xff0c;因为这个编程思想&#xff0c;今后在各个需要缓冲区的项目编程中都可以用到&#xff0c;所以今天来总结一下&#xff01; Buffer的数据结构 muduo的Buffer的定义如下&#xff0c;其内部是 一个 std::vector&#xff0c;且还存在两个…

Shell编程之条件语句

条件测试 文件测试与整数测试 字符串测试与逻辑测试 if语句 if单分支语句 if双分支语句 if多分支语句 case分支语句 条件测试操作 条件测试操作 1 条件判断 test命令测试表达式是否成立&#xff0c;若成立返回0.否则返回其它数值。 格式 1 test 条件表达式 格式 2 …