Qt|QWebSocket与Web进行通讯,实时接收语音流

实现功能主要思路:在网页端进行语音输入,PC机可以实时接收并播放语音流。

此时,Qt程序做客户端,Web端做服务器,使用QWebSocket进行通讯,实时播放接收的语音流。

功能实现

想要实现该功能,需要完成以下两大部分。

第一部分:QWebSocket通讯实现。(简单

第二部分:语音流实时播放功能。(稍微有点难度

接下来对于该功能实现进行具体的讲解。

1:建立通讯

1.1:创建QWebSocket通讯

添加头文件

#include <QWebSocketServer>
#include <QWebSocket>

声明WebSocket对象并响应消息

m_pWebClient = new QWebSocket;
connect(m_pWebClient, &QWebSocket::connected, this, &QWebSocketManager::MsgRecievd_Server_Connected);
connect(m_pWebClient, &QWebSocket::disconnected, this, &QWebSocketManager::MsgRecievd_Server_Disconnected);
connect(m_pWebClient, &QWebSocket::textMessageReceived, this, &QWebSocketManager::MsgRecievd_Server_TextMessageReceived);
connect(m_pWebClient, &QWebSocket::binaryMessageReceived, this, &QWebSocketManager::MsgRecievd_Server_BinaryMessageReceived);

分别响应了:连接、断开、接收字符串内容、接收二进制内容

1.2:建立心跳包

一说到通讯,首先想到的应该是心跳包机制。在与Web通讯也是如此,为了防止掉线,程序中也需要设定一个心跳包机制。

为了保证心跳包有连接,但不频繁发送,可以采用在无数据发送时,采用3秒~10秒之间发送一条。

使用方法:QTimer进行心跳包发送。

在程序使用过程中,不需要精确发送时间,只要在指定时间范围内(3s~10s)发送就可以了。

定义时间更新变量

DWORD m_dwReciveTime;//接收到WebSocket消息的时间

每次接收到web服务端发送数据时,实时更新接收时间。

void QWebSocketManager::MsgRecievd_Server_TextMessageReceived(const QString &message)
{qDebug() << QStringLiteral("接收内容:") << message;m_dwReciveTime = GetTickCount(); //更新接收时间
}
void QWebSocketManager::MsgRecievd_Server_BinaryMessageReceived(const QByteArray &message)
{qDebug() << QStringLiteral("接收内容:") << message;m_dwReciveTime = GetTickCount(); //更新接收时间
}

在项目中重写了两个接收消息,所以都需要实时更新接收时间。

此时需要开启定时器,假设每间隔3秒访问一次,定时器核心代码,如下:

DWORD dwCalc = GetTickCount() - pThis->m_dwReciveTime; //时间差 = 最新时间 - 模拟人上传数据时间
if (dwCalc < g_nWebSocektHeartTime)
{//时间差 < 最小心跳包
}
else if ((dwCalc > g_nWebSocektHeartTime) && (dwCalc < g_nWebSocketLostConnectTime))
{//发送心跳包协议
}
else if(dwCalc > g_nWebSocketLostConnectTime)
{qDebug() << QStringLiteral("连接超时!");
}

注意:这是我在通讯过程中进行了一点点小小优化,大家也可以采用哟~

每次触发定时器时,并没有直接发送心跳包,而是当间隔超过10秒后代表断开连接了。

1.3:接收web端音频流

在1.1中实现了QWebSocket的两个消息数据接收:textMessageReceived、binaryMessageReceived

具体使用哪个消息,需要对应服务端是如何发送的,一般而言,音频流采用二进制流的方式比较安全。

接收语音流数据,实例代码如下:

void QWebSocketManager::MsgRecievd_Server_BinaryMessageReceived(const QByteArray &message)
{//qDebug() << QStringLiteral("MsgRecievd_Server_BinaryMessageReceived,内容:") << message;
}

接收到音频流以后,该如何进行播放呢?

接下来就需要进行第二步重要功能:语音流实时播放功能

2:语音流实时播放功能

在这里我用的是:QAudioOutput类,使用该类方便操作。

2.1:初始化输出音频参数

QAudioFormat audio_out_format; 
//设置录音的格式
audio_out_format.setSampleRate(8000); //采样率
audio_out_format.setChannelCount(1); //通道数
audio_out_format.setSampleSize(16);
audio_out_format.setCodec("audio/pcm"); //编码格式
audio_out_format.setByteOrder(QAudioFormat::LittleEndian); //样本是小端字节顺序
audio_out_format.setSampleType(QAudioFormat::SignedInt); //样本类型QAudioDeviceInfo  info(QAudioDeviceInfo::defaultOutputDevice());m_pAudioOutput = new QAudioOutput(audio_out_format);
m_pStreamOutput = m_pAudioOutput->start();
m_nPeriodSize = m_pAudioOutput->periodSize();

代码分析:

录音的格式要与服务端输入的音频流格式才能保证客户端接收到清晰完整的音频流。

此时,需要注意的是最后一行代码:m_nPeriodSize = m_pAudioOutput->periodSize();

这是实现播放音频流的核心之一!

2.2:播放接收的音频流

针对这部分实现方式,我经历了以下几个步骤,已踩坑,希望对大家有用!

简单有问题的实现方式
void QWebSocketManager::MsgRecievd_Server_BinaryMessageReceived(const QByteArray &message)
{//qDebug() << QStringLiteral("MsgRecievd_Server_BinaryMessageReceived,内容:") << message;m_dwReciveTime = GetTickCount(); //更新接收时间m_pStreamOutput->write(array); //播放音频流
}

接收到音频流就直接播放。使用这种方法会发现,音频是可以播放,但是叽里呱啦的,每次智能听到说话的第一个字,其余的全都听不到了。

此时,你会怀疑是不是服务端传入的音频流不正确呢?因为客户端可以播放声音。如果你沿着这条路走,那你就错了。

原因:之所以只能听到说话的第一个字是因为,频繁地接收数据,上一次接收的音频流还未播放完毕就立刻播放下一条音频流,所以会出现这种问题了。

那么,该如何解决这种问题呢?

在这里就用到了初始化时我所说的核心代码了。

m_nPeriodSize 是每次播放一条完整音频格式的大小,服务端传入的数据大小我们无法控制,但是可以在播放时,每次取m_nPeriodSize 大小的数据进行播放,就能保证数据的完整性。

那么,如何知道上一次播放的音频流已经完成了呢?

使用m_pAudioOutput->bytesFree(),循环进行判断,只有当释放的缓存数小于m_nPeriodSize 才能够继续播放音频流

下面为大家展示有效地实现方法。

复杂有效的实现方式
void QWebSocketManager::MsgRecievd_Server_BinaryMessageReceived(const QByteArray &message)
{//qDebug() << QStringLiteral("MsgRecievd_Server_BinaryMessageReceived,内容:") << message;m_dwReciveTime = GetTickCount(); //更新接收时间{std::lock_guard<std::mutex> lck(m_mutexPcm);  //C11用法m_ArrayAudio.append(message);}if (m_bRunningAudio == false){m_bRunningAudio = true; //开启数据处理线程m_threadAudio = std::thread(&QWebSocketManager::ThreadProcessingPCMData, this, this);}
}

代码解析:

当接收到第一条音频数据时,开启线程,将音频播放处理放到线程中进行判断,只有把上一次播放的音频缓存释放完成后,才能够从缓存m_ArrayAudio中获取m_nPeriodSize大小的数据

线程实现代码,如下:

unsigned int QWebSocketManager::ThreadProcessingPCMData(void* pParam)
{QWebSocketManager* pThis = reinterpret_cast<QWebSocketManager*>(pParam);while (pThis->m_bRunningAudio == true){//只有满足一个完整包数据时,才需要处理if (pThis->m_ArrayAudio.count() >= m_nPeriodSize){if (m_pAudioOutput->bytesFree() < m_nPeriodSize){Sleep(5);continue; //当前音频释放大小 < 固定大小时,不处理}std::lock_guard<std::mutex> lck(m_mutexPcm);  //C11用法QByteArray array = pThis->m_ArrayAudio.mid(0, m_nPeriodSize);pThis->m_pStreamOutput->write(array);pThis->m_ArrayAudio.remove(0, m_nPeriodSize);qDebug() << QStringLiteral("处理一次完整的音频,此时剩余大小 = ") << pThis->m_ArrayAudio.count();}else{Sleep(1000);}}return 0;
}

以上就是核心的实现流程了,如果需要查看原始代码的,请看下面链接

Qt中使用QWebSocket与Web进行通讯,实时语音通话

我是糯诺诺米团,一名C++开发程序媛~

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

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

相关文章

ElasticSearch地理空间数据写入

目录 ElasticSearch地理空间数据写入思路介绍实现(geo_point)数据处理创建点的mappings使用Java将数据写入ES配置maven依赖项目配置ES数据写入查询数据实现(geo_shape)数据处理创建geo_shape的mappings使用Java将数据写入ES数据写入查询数据ElasticSearch地理空间数据写入 申明…

欧拉函数的求解

欧拉函数的定义 欧拉函数的性质 性质1是性质2的特殊情况 性质1的理解&#xff1a;一个数a是质数&#xff0c;前面的数b与a的gcd一定是1 性质2的理解&#xff1a;1,2,…p,p1,p2…2p,…3p…p^k 其中以np结尾的序列重复了p^&#xff08;k-1&#xff09;次&#xff0c;每一次的循环…

HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)

系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现&#xff08;一&#xff09; HarmonyOS Next 系列之验证码输入组件实现&#xff08;二&#xff09; HarmonyOS Next 系列之底部标签栏TabBar实现&#xff08;三&#xff09; HarmonyOS Next 系列之HTTP请求封装和Token…

vue播放flv格式的直播流

在ios无法播放&#xff0c;安卓可以 安装 npm install flv.js --save页面 <template><div><videoref"videoElement"style"width: 100%; height: 100%"autoplayplaysinlinemuted></video></div> </template><scr…

【设计模式深度剖析】【11】【行为型】【解释器模式】| 以算术表达式求值为例加深理解

&#x1f448;️上一篇:状态模式 设计模式-专栏&#x1f448;️ 文章目录 解释器模式定义英文原话直译 解释器模式中的角色1. 抽象表达式&#xff08;AbstractExpression&#xff09;2. 终端表达式&#xff08;TerminalExpression&#xff09;3. 非终端表达式&#xff08;Non…

6.S081的Lab学习——Lab7: Multithreading

文章目录 前言一、Uthread: switching between threads (moderate)提示&#xff1a;解析 二、Using threads (moderate)解析&#xff1a; 三、Barrier (moderate)解析&#xff1a; 总结 前言 一个本硕双非的小菜鸡&#xff0c;备战24年秋招。打算尝试6.S081&#xff0c;将它的…

OpenCV--波滤器(二)

高通滤波器 代码和笔记 代码和笔记 import cv2 import numpy as np""" 高通滤波器--锐化图像&#xff0c;增强图像中的高频成分&#xff0c; 如边缘(边缘检测&#xff1a;旨在找出图像中亮度变化显著的区域&#xff0c;大幅度地减少了数据量&#xff0c;并剔除…

[Linux] 文件系统

UNIX操作系统将文件组织成一个有层次的树形结构&#xff1a; 标准目录&#xff1a; 根目录&#xff1a; /tmp目录 主目录&#xff1a; 这就是主目录 一般与系统有关的信息都存放在etc目录下 注意&#xff1a; /etc/passwd存放的是用户账户信息&#xff0c;不是密码信息&#xf…

ubuntu换镜像源方法

查看ubuntu的版本&#xff0c;不同的版本对应的不同的镜像源 cat /etc/issue Ubuntu 18.04.6 LTS \n \l 先备份一个&#xff0c;防止更改错误 cobol cp /etc/apt/sources.list /etc/apt/sources.list.backup 先进入清华源,搜索ubuntu&#xff0c;点击问号 点进来可以看到可以…

VMware RedHat虚拟机磁盘扩容(添加磁盘和扩展磁盘)

前言 自己的电脑上配一个虚拟机还是很有必要的&#xff0c;用起来比双系统方便一点&#xff0c;之前搞了100g的ubuntu没用到&#xff0c;后面重装redhat觉得随便搞个20g就够用了&#xff0c;后面用到之后就遇到磁盘不够用的情况&#xff0c;只能说情况允许的话&#xff0c;磁盘…

FPGA生成图像Modelsim仿真生成BMP图片保存显示

FPGA生成图像模板&#xff0c;产生RBG格式棋盘格。利用Modelsim仿真&#xff0c;将FPGA生成的图片转化成BMP图片保存并显示。 一&#xff1a;仿真代码 timescale 1ns/1ps module design_1_wrapper_top (); reg clk_100MHz; reg clk_300MHz; wire [31:0]clk_rate; …

为微信小程序项目添加eslint

背景 在使用vscode开发微信小程序的过程中&#xff0c;修改js的时候发现没有报错提示&#xff0c;让我很不习惯&#xff0c;所以想为微信小程序项目添加eslint配置 编码实战 为微信小程序配置ESLint可以遵循以下步骤&#xff1a; 安装ESLint及其相关插件 首先&#xff0c;…

spring和Mybatis的逆向工程

文章目录 十二、注解开发1、注解方式单表的增删改查的操作 十三、逆向工程13.1、创建逆向工程的步骤⑴添加依赖⑵配置MyBatis的核心配置文件⑶创建逆向工程的配置文件,该文件文件名必须是&#xff1a;generatorConfig.xml 13.2测试 十四、分页插件14.1、使用步骤14.2分页插件的…

基于IDEA的Maven(坐标信息介绍和编写)

这篇博客来学习和分析一下&#xff1a; " pom.xml " 所生成的最基本的信息。 之前的博客中讲到&#xff0c;学 Maven 就是学 " pom.xml " 的配置。后面也会围绕这个文件进行学习。 目录 一、分析 pom.xml 文件 &#xff08;1&#xff09;分析的 "p…

2024 年最新 Python 基于火山引擎豆包大模型搭建 QQ 机器人详细教程(更新中)

豆包大模型概述 火山引擎官网&#xff1a;https://www.volcengine.com/ 字节跳动推出的自研大模型。通过字节跳动内部50业务场景实践验证&#xff0c;每日千亿级tokens大使用量持续打磨&#xff0c;提供多模态能力&#xff0c;以优质模型效果为企业打造丰富的业务体验。 模型…

代码随想录算法训练营刷题复习6 :贪心算法2/2

贪心算法 贪心算法这些题中&#xff0c;关于区间的问题可以进行小总结&#xff0c;今天有事耽搁了还没来得及写 860. 柠檬水找零 406. 根据身高重建队列 452. 用最少数量的箭引爆气球 435. 无重叠区间 按照区间的左界对区间进行升序排序&#xff08;如遇到相同的值&#xf…

数据库 | SQL语言和关系代数汇总篇(*^▽^*)

双重/两个 not exists 嵌套查询 简单分析_两个not exists-CSDN博客 最明白的 not exists 双层嵌套讲解 SQL 查询语句_not exist 嵌套-CSDN博客 1. 答案&#xff1a; 2. 答案&#xff1a; 3. 4. 5. 6. 7. 8. 这个SQL查询是从两个表&#xff08;假设是SPJ和P&#xff09;中检…

智能合约新项目 链上智能合约前端H5源码 智能合约区块链 以太坊前端调用智能合约

智能合约新项目 链上智能合约前端H5源码 智能合约区块链 以太坊前端调用智能合约 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89402192 更多资源下载&#xff1a;关注我。

.NET+Python量化【1】——环境部署和个人资金账户信息查询

前言&#xff1a;量化资料很少&#xff0c;.NET更少。那我就来开个先河吧~ 以下是使用QMT进行量化开发的环境部署和基础信息获取有关操作。 1、首先自己申请券商的QMT权限&#xff0c;此步骤省略。 2、登陆QMT&#xff0c;选择极简模式&#xff0c;或者独立交易模式之类的。会进…

阿里员工:“大厂病”导致有些人不太正常

阿里巴巴对“大公司病”开刀 阿里巴巴宣布对“大公司病”开刀&#xff0c;终于有高层意识到这个问题的严重性&#xff0c;并决心解决。所谓“大公司病”&#xff0c;不仅体现在规模臃肿、部门繁多、层次复杂&#xff0c;还包括中层管理人员的不作为。这些人处在上传下达的位置&…