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格式…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
