当前位置: 首页 > news >正文

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进行通讯,实时接收语音流

实现功能主要思路&#xff1a;在网页端进行语音输入&#xff0c;PC机可以实时接收并播放语音流。 此时&#xff0c;Qt程序做客户端&#xff0c;Web端做服务器&#xff0c;使用QWebSocket进行通讯&#xff0c;实时播放接收的语音流。 功能实现 想要实现该功能&#xff0c;需要…...

「51媒体」电视台媒体邀约采访报道怎么做?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 电视台作为地方主流媒体&#xff0c;对于新闻报道有着严格的选题标准和报道流程。如果您希望电视台对某个会议或活动进行报道&#xff0c;可以按这样的方法来做&#xff1a; 1.明确活动信…...

Python提取PDF文本和图片,以及提前PDF页面中指定矩形区域的文本

前言 从PDF中提取内容能帮助我们获取文件中的信息&#xff0c;以便进行进一步的分析和处理。此外&#xff0c;在遇到类似项目时&#xff0c;提取出来的文本或图片也能再次利用。要在Python中通过代码提取PDF文件中的文本和图片&#xff0c;可以使用 Spire.PDF for Python 这个…...

C#实现边缘锐化(图像处理)

在 C# 中进行图像的边缘锐化&#xff0c;可以通过卷积滤波器实现。边缘锐化的基本思想是通过卷积核&#xff08;也称为滤波器或掩模&#xff09;来增强图像中的边缘。我们可以使用一个简单的锐化核&#xff0c;例如&#xff1a; [ 0, -1, 0][-1, 5, -1][ 0, -1, 0]这个卷积核…...

ffmpeg windows系统详细教程

视频做预览时黑屏&#xff0c;但有声音问题解决方案。 需要将 .mp4编成H.264格式的.mp4 一般上传视频的站点&#xff0c;如YouTube、Vimeo 等&#xff0c;通常会在用户上传视频时自动对视频进行转码&#xff0c;以确保视频能够在各种设备和网络条件下流畅播放。这些网站通常…...

【单片机】MSP430G2553单片机 Could not find MSP-FET430UIF on specified COM port 解决方案

文章目录 MSP430G2553开发板基础知识解决办法如何实施解决办法4步骤一步骤二步骤三 MSP430G2553开发板基础知识 MSP430G2553开发板如下图&#xff0c;上半部分就是UIF程序下载调试区域的硬件。个人觉得MSP430G2553开发板的这个部分没有做好硬件设计&#xff0c;导致很多系统兼…...

每日一题——力扣104. 二叉树的最大深度(举一反三+思想解读+逐步优化)四千字好文

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 目录 我的写法 代码功能 代码结构 时间复杂度分析 空间复杂度分析 总结 我要更强 优化方法&#xff1a;迭代&…...

wpf textbox 有焦点 导致后台更新 前台不跟着改变

这个问题可能是由于 WPF 的数据绑定机制导致的。当 TextBox 有焦点时,它会独立于数据绑定进行更新,这可能会导致前台界面不能及时反映后台数据的变化。 1.使用 UpdateSourceTrigger 属性: 在数据绑定时,将 UpdateSourceTrigger 属性设置为 PropertyChanged。这样当 TextBox 的…...

数字化物资管理系统的未来:RFID技术的创新应用

在信息化和智能化不断发展的背景下&#xff0c;物资管理系统的数字化转型已成为各行各业关注的焦点。RFID技术作为一种先进的物联网技术&#xff0c;通过全面数字化实现物资信息的实时追踪和高效管理&#xff0c;为企业的物资管理提供了强有力的支持。 首先&#xff0c;RFID技…...

【docker】常用指令-表格整理

以下列出的指令是Docker中常用的命令&#xff0c;但并不是全部。Docker的指令非常丰富&#xff0c;可以根据具体的需求和场景选择合适的指令。同时&#xff0c;每个指令都有很多选项和参数可以使用&#xff0c;可以通过 docker COMMAND --help 来获取更详细的信息。 一、容器命…...

洛谷——P2824 排序

题目来源&#xff1a;[HEOI2016/TJOI2016] 排序 - 洛谷https://www.luogu.com.cn/problem/P2824 问题思路 本文介绍一种二分答案的做法&#xff0c;时间复杂度为&#xff1a;(nm)*log(n)*log(n).本题存在nlog(n)的做法&#xff0c;然而其做法没有二分答案的做法通俗易懂. 默认读…...

echart在线图表demo下载直接运行

echart 全面的数据可视化图表解决方案 | 折线图、柱状图、饼图、散点图、水球图等各类图表展示 持续更新中 三色带下表题速度仪表盘 地图自定义图标 动态环形图饼状图 动态水波动圆形 多标题指针仪表盘 温度仪表盘带下标题 横向柱状图排名 环形饼状图 双折线趋势变化...

MLX5_SET_TO_ONES宏解析

看代码时&#xff0c;遇到一个非常复杂的宏MLX5_SET_TO_ONES&#xff0c;这个宏的主要作用是对特定的数据结构置位&#xff0c;宏的上下文如下&#xff1a; #define __mlx5_nullp(typ) ((struct mlx5_ifc_##typ##_bits *)0) #define __mlx5_bit_off(typ, fld) (offsetof(struc…...

SQL Server入门-SSMS简单使用(2008R2版)-1

环境&#xff1a; win10&#xff0c;SQL Server 2008 R2 参考&#xff1a; SQL Server 新建数据库 - 菜鸟教程 https://www.cainiaoya.com/sqlserver/sql-server-create-db.html 第 2 课&#xff1a;编写 Transact-SQL | Microsoft Learn https://learn.microsoft.com/zh-cn/…...

高考专业抉择探索计算机专业的未来展望及适合人群

身份&#xff1a;一位正在面临人生重要抉择的高考生&#xff0c;一位计算机行业从业者  正文&#xff1a;  随着2024年高考落幕&#xff0c;我与数百万高三学生一样&#xff0c;又将面临人生中的重要抉择&#xff1a;选择大学专业。对于许多学生来说&#xff0c;计算机科学…...

windows安装spark

在 Windows 上安装 Spark 并进行配置需要一些步骤&#xff0c;包括安装必要的软件和配置环境变量。以下是详细的步骤指南&#xff1a; 步骤一&#xff1a;安装 Java 下载和安装 Java Development Kit (JDK) 到 Oracle JDK 下载页面 或 OpenJDK 下载页面 下载适合你系统的 JDK。…...

【信息学奥赛】CSP-J/S初赛03 计算机网络与编程语言分类

第1节 计算机网络基础 1.1 网络的定义 所谓计算机网络&#xff0c;就是利用通信线路和设备&#xff0c;把分布在不同地理位置上的多台计算机连 接起来。计算机网络是现代通信技术与计算机技术相结合的产物。 网络中计算机与计算机之间的通信依靠协议进行。协议是计算机收、发…...

python20 函数的定及调用

函数的定及调用 函数是将一段实现功能的完整代码&#xff0c;使用函数名称进行封装&#xff0c;通过函数名称进行调用。以此达到一次编写&#xff0c;多次调用的目的 用 def 关键字来声明 函数 格式&#xff1a; def 函数名(参数列表):函数体[:return 返回值是可选的&#xff0…...

【Android WebView】WebView基础

一、简介 WebView是一个基于webkit引擎、展现web页面的控件。Android的Webview在低版本和高版本采用了不同的webkit版本内核&#xff0c;4.4后直接使用了Chrome。 二、重要类 以WebView类为基础&#xff0c;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格式…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

Java - Mysql数据类型对应

Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...