QT实战百度语音识别

news2025/1/13 9:48:30

前言

随着学习的深入,感觉愈发缺乏满足感。刚好看到微信语音转文字的功能,经网上查询,发现可以使用 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、48000
        format.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_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    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)
{
    //获取token
    QString 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));

    //把文件转化为QByteArray
    QFile 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();
            
            //判断是否包含key
            if(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.coloradmin.cn/o/1654184.html

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

相关文章

04-28 周日 FastAPI Post请求同时传递文件和普通参数

04-28 周日 FastAPI Post请求同时传递文件和普通参数 时间版本修改人描述04-28 周日V0.1宋全恒新建文档2024年5月6日14:20:05V1.0宋全恒完成文档的传递 简介 由于在重构FastBuild的时候&#xff0c;为了支持TLS是否启用&#xff0c;在接口中需要同时传递文件参数和其他参数&am…

SQL查询语句(三)范围查找关键字

在上一篇文章中&#xff0c;我们介绍了SQL语句中&#xff0c;逻辑关键字的作用&#xff0c;并举例演示了如何用逻辑关键字来组合WHERE子句。在文章的末尾我们提到了两个用于范围查找的关键字IN和BETWEEN。这两个关键字都可以与NOT关键字灵活组合&#xff0c;起到对字句结果取反…

【算法】滑动窗口——将x减到0的最小操作数

本节博客主要是讲的我解“将x减到0的最小操作数”这道题的思路历程&#xff0c;从最开始的想法到代码提交的详细记录&#xff0c;有需要借鉴即可。 目录 1.题目2.代码示例3.细节3.1left越界3.2特殊情况 4.总结 1.题目 题目链接&#xff1a;LINK 看题目意思是就是给你一个数X&…

Redis(主从复制搭建)

文章目录 1.主从复制示意图2.搭建一主多从1.搭建规划三台机器&#xff08;一主二从&#xff09;2.将两台从Redis服务都按照同样的方式配置&#xff08;可以理解为Redis初始化&#xff09;1.安装Redis1.yum安装gcc2.查看gcc版本3.将redis6.2.6上传到/opt目录下4.进入/opt目录下然…

VueReal将在Display Week上推出microLED创新技术

公司展示将microLED从晶圆转移到背板的“改变游戏规则”的平台 在2024年显示周&#xff08;5月12日至16日在圣何塞举行&#xff09;上&#xff0c;VueReal将展示其MicroSolid打印平台&#xff0c;并展示其在推动微LED显示器和其他微型半导体器件在智能手机显示器和AR/VR解决方案…

Linux进程间通信:system V共享内存

目录 一、什么是共享内存 1.1创建共享内存 1.2释放共享内存 1.2.1shmctl 1.2.2shmat 1.2.3 shmdt 二、共享内存的实现及使用 2.1ShmClient 2.2Shm_Server 2.3Fifo.hpp 2.4Comm.hpp 一、什么是共享内存 标准系统V也叫system V的本地通信方式一般有三种&#xff1a; …

论文阅读】 ICCV-2021-3D Local Convolutional Neural Networks for Gait Recognition

motivation :现有方法方法无法准确定位身体部位&#xff0c;不同的身体部位可以出现在同一个条纹(如手臂和躯干)&#xff0c;一个部分可以出现在不同帧(如手)的不同条纹上。其次&#xff0c;不同的身体部位具有不同的尺度&#xff0c;即使是不同帧中的同一部分也可以出现在不同…

2024041702-计算机操作系统 - 死锁

计算机操作系统 - 死锁 计算机操作系统 - 死锁 必要条件处理方法鸵鸟策略死锁检测与死锁恢复 1. 每种类型一个资源的死锁检测2. 每种类型多个资源的死锁检测3. 死锁恢复 死锁预防 1. 破坏互斥条件2. 破坏占有和等待条件3. 破坏不可抢占条件4. 破坏环路等待 死锁避免 1. 安全状态…

06-beanFactoryPostProcessor的执行

文章目录 invokeBeanFactoryPostProcessors(beanFactory)invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors())invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);invokeBeanFactoryPostProcessors(regularPostProc…

docker desktop实战部署oracle篇

1、前言 oracle数据库官方已提供现成的镜像&#xff0c;可以直接拿来部署了。 由于项目中需要使用oracle数据库的分表功能&#xff0c;之前安装的是standard版本&#xff0c;无奈只能重新安装。网上查了一番&#xff0c;使用的方法都比较传统老旧&#xff1a;下载安装包手动安…

Ps中 饱和度 和 自然饱和度 的区别?

1.饱和度&#xff08;Saturation&#xff09;&#xff1a;在Photoshop中&#xff0c;饱和度是一个全局性调整&#xff0c;它影响图像中所有颜色的鲜艳程度。当你增加饱和度时&#xff0c;所有的颜色都会变得更浓烈、更鲜艳&#xff1b;相反&#xff0c;减小饱和度会使图像整体变…

解决 git克隆拉取代码报SSL certificate problem错误

问题&#xff1a;拉取代码时报错&#xff0c;SSL证书问题:证书链中的自签名证书问题 解决&#xff1a;只需要关闭证书验证&#xff0c;执行下面代码即可&#xff1a; git config --global http.sslVerify "false" 再次拉取代码就可以了

怎样选择IT外包公司?需要注意什么?

随着网络化、数字化、智能化快速发展&#xff0c;一部分企业成立自己的IT部门&#xff0c;负责各个科室的网络安全&#xff0c;大部分企业把网络安全、数据安全&#xff0c;外包给专业的IT外包公司&#xff0c;既提升了办公效率&#xff0c;企业又能把主要精力放在发展核心业务…

(二刷)代码随想录第1天|704. 二分查找 27. 移除元素

704. 二分查找 704. 二分查找 - 力扣&#xff08;LeetCode&#xff09; 代码随想录 (programmercarl.com) 手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode&#xff1a;704. 二分查找_哔哩哔哩_bilibili 给定一个 n 个元素有序的&#xff08;升序&#xff09…

Unity如何使用adb工具安装APK

1、下载adb工具 SDK 平台工具版本说明 | Android Studio | Android Developers (google.cn) 2、配置环境变量 把platform-tools的路径添加进去就行 打开cmd&#xff0c;输入adb&#xff0c;即可查看版本信息 3、使用数据线连接设备&#xff0c;查看设备信息&#xff08;…

后教培时代的新东方,正在找寻更大的教育驱动力?

近段时间&#xff0c;K12教育主要上市公司的阶段性业绩皆已出炉。从具体数据来看&#xff0c;随着时间推移&#xff0c;教培机构的转型之路已愈走愈顺。 财报显示&#xff0c;2023年12月1日-2024年2月29日&#xff0c;好未来实现营收4.3亿美元&#xff0c;同比增长59.7%&#…

C++ | Leetcode C++题解之第60题排列序列

题目&#xff1a; 题解&#xff1a; class Solution { public:string getPermutation(int n, int k) {vector<int> factorial(n);factorial[0] 1;for (int i 1; i < n; i) {factorial[i] factorial[i - 1] * i;}--k;string ans;vector<int> valid(n 1, 1);…

小程序支付的款项流转与到账时间

商家做小程序&#xff0c;最关心的是客户通过小程序下单支付的钱&#xff0c;是怎么样的流转状态以及最终到哪里。因此&#xff0c;本文将详细解析款项最终流向何处以及多久能够到账。 一、小程序支付的款项流向 当用户在小程序内完成支付后&#xff0c;款项并不会直接到达商…

数据结构学习——线性表、顺序表

1.线性表 线性表 &#xff08; linear list &#xff09; 是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一…

多线程【阻塞队列】(生产者消费者模型代码实现)

阻塞队列 解耦合削峰填谷生产者消费者模型&#xff1a; 解耦合 削峰填谷 生产者消费者模型&#xff1a; 正常来说&#xff0c;wait通过notify唤醒&#xff0c;其他线程调用了take,在take的最后一步进行notify. package thread; class MyBlockingQueue{private String [] data…