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++开发程序媛~
相关文章:
Qt|QWebSocket与Web进行通讯,实时接收语音流
实现功能主要思路:在网页端进行语音输入,PC机可以实时接收并播放语音流。 此时,Qt程序做客户端,Web端做服务器,使用QWebSocket进行通讯,实时播放接收的语音流。 功能实现 想要实现该功能,需要…...
「51媒体」电视台媒体邀约采访报道怎么做?
传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 电视台作为地方主流媒体,对于新闻报道有着严格的选题标准和报道流程。如果您希望电视台对某个会议或活动进行报道,可以按这样的方法来做: 1.明确活动信…...
Python提取PDF文本和图片,以及提前PDF页面中指定矩形区域的文本
前言 从PDF中提取内容能帮助我们获取文件中的信息,以便进行进一步的分析和处理。此外,在遇到类似项目时,提取出来的文本或图片也能再次利用。要在Python中通过代码提取PDF文件中的文本和图片,可以使用 Spire.PDF for Python 这个…...
C#实现边缘锐化(图像处理)
在 C# 中进行图像的边缘锐化,可以通过卷积滤波器实现。边缘锐化的基本思想是通过卷积核(也称为滤波器或掩模)来增强图像中的边缘。我们可以使用一个简单的锐化核,例如: [ 0, -1, 0][-1, 5, -1][ 0, -1, 0]这个卷积核…...
ffmpeg windows系统详细教程
视频做预览时黑屏,但有声音问题解决方案。 需要将 .mp4编成H.264格式的.mp4 一般上传视频的站点,如YouTube、Vimeo 等,通常会在用户上传视频时自动对视频进行转码,以确保视频能够在各种设备和网络条件下流畅播放。这些网站通常…...
【单片机】MSP430G2553单片机 Could not find MSP-FET430UIF on specified COM port 解决方案
文章目录 MSP430G2553开发板基础知识解决办法如何实施解决办法4步骤一步骤二步骤三 MSP430G2553开发板基础知识 MSP430G2553开发板如下图,上半部分就是UIF程序下载调试区域的硬件。个人觉得MSP430G2553开发板的这个部分没有做好硬件设计,导致很多系统兼…...
每日一题——力扣104. 二叉树的最大深度(举一反三+思想解读+逐步优化)四千字好文
一个认为一切根源都是“自己不够强”的INTJ 个人主页:用哲学编程-CSDN博客专栏:每日一题——举一反三Python编程学习Python内置函数 目录 我的写法 代码功能 代码结构 时间复杂度分析 空间复杂度分析 总结 我要更强 优化方法:迭代&…...
wpf textbox 有焦点 导致后台更新 前台不跟着改变
这个问题可能是由于 WPF 的数据绑定机制导致的。当 TextBox 有焦点时,它会独立于数据绑定进行更新,这可能会导致前台界面不能及时反映后台数据的变化。 1.使用 UpdateSourceTrigger 属性: 在数据绑定时,将 UpdateSourceTrigger 属性设置为 PropertyChanged。这样当 TextBox 的…...
数字化物资管理系统的未来:RFID技术的创新应用
在信息化和智能化不断发展的背景下,物资管理系统的数字化转型已成为各行各业关注的焦点。RFID技术作为一种先进的物联网技术,通过全面数字化实现物资信息的实时追踪和高效管理,为企业的物资管理提供了强有力的支持。 首先,RFID技…...
【docker】常用指令-表格整理
以下列出的指令是Docker中常用的命令,但并不是全部。Docker的指令非常丰富,可以根据具体的需求和场景选择合适的指令。同时,每个指令都有很多选项和参数可以使用,可以通过 docker COMMAND --help 来获取更详细的信息。 一、容器命…...
洛谷——P2824 排序
题目来源:[HEOI2016/TJOI2016] 排序 - 洛谷https://www.luogu.com.cn/problem/P2824 问题思路 本文介绍一种二分答案的做法,时间复杂度为:(nm)*log(n)*log(n).本题存在nlog(n)的做法,然而其做法没有二分答案的做法通俗易懂. 默认读…...
echart在线图表demo下载直接运行
echart 全面的数据可视化图表解决方案 | 折线图、柱状图、饼图、散点图、水球图等各类图表展示 持续更新中 三色带下表题速度仪表盘 地图自定义图标 动态环形图饼状图 动态水波动圆形 多标题指针仪表盘 温度仪表盘带下标题 横向柱状图排名 环形饼状图 双折线趋势变化...
MLX5_SET_TO_ONES宏解析
看代码时,遇到一个非常复杂的宏MLX5_SET_TO_ONES,这个宏的主要作用是对特定的数据结构置位,宏的上下文如下: #define __mlx5_nullp(typ) ((struct mlx5_ifc_##typ##_bits *)0) #define __mlx5_bit_off(typ, fld) (offsetof(struc…...
SQL Server入门-SSMS简单使用(2008R2版)-1
环境: win10,SQL Server 2008 R2 参考: SQL Server 新建数据库 - 菜鸟教程 https://www.cainiaoya.com/sqlserver/sql-server-create-db.html 第 2 课:编写 Transact-SQL | Microsoft Learn https://learn.microsoft.com/zh-cn/…...
高考专业抉择探索计算机专业的未来展望及适合人群
身份:一位正在面临人生重要抉择的高考生,一位计算机行业从业者 正文: 随着2024年高考落幕,我与数百万高三学生一样,又将面临人生中的重要抉择:选择大学专业。对于许多学生来说,计算机科学…...
windows安装spark
在 Windows 上安装 Spark 并进行配置需要一些步骤,包括安装必要的软件和配置环境变量。以下是详细的步骤指南: 步骤一:安装 Java 下载和安装 Java Development Kit (JDK) 到 Oracle JDK 下载页面 或 OpenJDK 下载页面 下载适合你系统的 JDK。…...
【信息学奥赛】CSP-J/S初赛03 计算机网络与编程语言分类
第1节 计算机网络基础 1.1 网络的定义 所谓计算机网络,就是利用通信线路和设备,把分布在不同地理位置上的多台计算机连 接起来。计算机网络是现代通信技术与计算机技术相结合的产物。 网络中计算机与计算机之间的通信依靠协议进行。协议是计算机收、发…...
python20 函数的定及调用
函数的定及调用 函数是将一段实现功能的完整代码,使用函数名称进行封装,通过函数名称进行调用。以此达到一次编写,多次调用的目的 用 def 关键字来声明 函数 格式: def 函数名(参数列表):函数体[:return 返回值是可选的࿰…...
【Android WebView】WebView基础
一、简介 WebView是一个基于webkit引擎、展现web页面的控件。Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chrome。 二、重要类 以WebView类为基础,WebSettings、WebViewClient、WebChromeClient为辅助共同完成安卓段加…...
Python酷库之旅-第三方库openpyxl(03)
目录 一、 openpyxl库的由来 1、背景 2、起源 3、发展 4、特点 4-1、支持.xlsx格式 4-2、读写Excel文件 4-3、操作单元格 4-4、创建和修改工作表 4-5、样式设置 4-6、图表和公式 4-7、支持数字和日期格式 二、openpyxl库的优缺点 1、优点 1-1、支持现代Excel格式…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
