Qt使用QAudioInput、QAudioOutput实现局域网的音频通话

本文旨在介绍一下用Qt来实现局域网音频通话功能

项目背景

最近项目需要,要制作一个局域网的音频通话软件,所以就动手写了一个局域网音频通话软件。

技术实现

  1. QAudioInput、QAudioOutput(Qt采集和播放音频类)
  2. QUdpSocket(Qt的UDP通信类)

  话不多说,直接上代码链接,想下载的朋友可以直接去gitee下载。
  整体的思路就是,读取声卡的数据,通过UDP发送出去,同时也会读取UDP发送过来的流的数据,写入到音频播放设备里进行播放。
以下是一些比较简单的对这两个技术点的解释,以及部分代码实现细节。

QAudioFormat(音频采样格式)

这个类,保存了音频流的参数信息。主要的参数有:

Parameter Description
Sample Rate(采样频率) Samples per second of audio data in Hertz.
Sample Channels(采样通道数) Number of channelsThe number of audio channels (typically one for mono or two for stereo)
Sample size(采样位数) How much data is stored in each sample (typically 8 or 16 bits)
Sample type(采样种类) Numerical representation of sample (typically signed integer, unsigned integer or float)
Byte order(字节序) Byte ordering of sample (typically little endian, big endian)

详细的音频采集知识请看:科普常识:常用音频参数解析。而在实际使用中,我们一般只关注Sample Rate(采样频率)Sample Size(采样位数)
采样频率代表,在一秒钟里面,采样的音频的数量。采样频率越大,就代表这个声音的振幅越准确,换言之就是声音的质量也就越高
采样位数代表,对采样的声音的振幅等级数量。采样位数越大,声音振幅的划分越细,得到的声音的就越真实,噪声就越少

QAudioDeviceInfo

这个类是用来保存音频播放设备的一些信息的,在这里,我们主要用来获取设备所支持的语音格式。

QAudioInput、QAudioOutput

这两个类,是Qt中的用于采集和播放音频的类。简单的用法如下:

// 设置音频采样的参数
m_format.setSampleRate(8000);
m_format.setChannelCount(1);
m_format.setSampleSize(8);
m_format.setCodec("audio/pcm");
m_format.setByteOrder(QAudioFormat::LittleEndian);
m_format.setSampleType(QAudioFormat::SignedInt);
QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
if (!info.isFormatSupported(m_format)) {
qWarning() << "Default format not supported, trying to use the nearest.";
m_format = info.nearestFormat(m_format);
} // 用采样的参数来实例化一个QAudioInput对象
m_audioInput = new QAudioInput(m_format); // 用采样的参数来实例化一个QAudioOutput对象
m_audioOutput = new QAudioOutput(m_format, this);
m_outputDevice = m_audioOutput->start();

这两个类有一个函数start( ),这个函数会开启音频的读取或者写入,并返回一个对应的QIODevice,用来从设备里读取和写入音频数据。

当通话接通的时候,打开QAudioInput,将音频流数据,通过UDP发送到对方端口。

void MainWindow::slot_callResponse(int response)
{
ui->stackedWidget->setCurrentIndex(0);
m_dialogTimer.stop();
if (response == 0) {
slot_connected();
m_inputDevice = m_audioInput->start();
connect(m_inputDevice, &QIODevice::readyRead, this, &MainWindow::slot_sendAudioData, Qt::UniqueConnection);
} else if (response == 1) {
// TODO 添加拒绝通话时,将等待框关掉
} } void MainWindow::slot_sendAudioData()
{
m_socket.writeDatagram(m_inputDevice->read(1024), QHostAddress(m_targetIP), m_targetPort);
}

QUdpSocket

这个类是Qt的udp通信的类,详细的类的介绍,可以看Qt的帮助文档。在这个项目,主要用到了几个函数:

  1. bind

  这个函数用来绑定到某个ip和端口上,代表发到这个ip和这个端口上的数据,能被当前socket认为是发给自己的。当然,如果你仅仅只要发送udp数据的话,是不需要进行bind的。

  1. readyRead

  这是一个信号,当数据准备好可以读取的时候,就会发射这个信号。这个时候,就可以调用reciveDatagram来读取数据。
使用代码如下:

void RecvData::slot_start()
{
qDebug() << QThread::currentThread();
QString dir = QApplication::applicationDirPath();
QSettings settings(dir+"/config.ini", QSettings::IniFormat);
int port = settings.value("Network/hostPort").toInt();
QString ip = settings.value("Network/hostIP").toString(); m_socket = new QUdpSocket;
int ret = m_socket->bind(QHostAddress(ip), port);
qDebug() << ip << port;
if (!ret) {
QString error = QString("%1:%2 绑定失败, 原因: %3")
.arg(ip)
.arg(port)
.arg(m_socket->errorString());
Q_EMIT signal_bindFailed(error);
} connect(m_socket, &QUdpSocket::readyRead, this, &RecvData::slot_writeDataToOutput);
}

在收到UDP的数据时,会对数据进行解析,然后通过信号和槽的方式来执行对应的步骤:

int RecvData::analysisData(const QByteArray &data)
{
if (data.size() > 30)
return 0; if (data == m_protocolManager.protocolContent(Protocol::CallRequest)) {
m_connectStatus = ConnectStatus::Connected;
Q_EMIT signal_callRequest();
} if (data == m_protocolManager.protocolContent(Protocol::Accept)) {
m_connectStatus = ConnectStatus::Connected;
Q_EMIT signal_callResponse(0);
} if (data == m_protocolManager.protocolContent(Protocol::Refuse)) {
m_connectStatus = ConnectStatus::Disconnected;
Q_EMIT signal_callResponse(1);
} if (data == m_protocolManager.protocolContent(Protocol::HangUp)) {
m_connectStatus = ConnectStatus::Disconnected;
Q_EMIT signal_hangUp();
} if (data == m_protocolManager.protocolContent(Protocol::Cancel)) {
m_connectStatus = ConnectStatus::Disconnected;
Q_EMIT signal_callCancel();
} return 1;
}

如果是音频的数据,就直接将数据写入到QAudioOutput开启时返回的QIODevice里,

void RecvData::slot_writeDataToOutput()
{
QNetworkDatagram datagram = m_socket->receiveDatagram();
int ret = analysisData(datagram.data());
if (ret == 1)
return;
if (m_connectStatus != ConnectStatus::Connected)
return;
int writeSize = m_outputDevice->write(datagram.data());
Q_UNUSED(writeSize)
}

踩过的坑

  1. 音频采集时,出现很大的杂音

  这个问题,在介绍完音频的各种参数之后就开始了解了,但是当时做的时候,一个劲的去加载采样频率,但是发现根本就不起作用。于是怀疑是不是因为没有降噪算法的加持,所以导致有很大的噪音。但是偶然在网上发现说QAudioRecord录制的音频,播放效果比QAudioOutput效果好多了,于是我就很纳闷,后面发现,是因为QAudioRecord设置了一个高质量的参数,所以就采样效果很好。于是,我才找到上面那片文章对应的每一个音频采集的参数效果,最后把Sample Size设置成了16之后,效果就好很多了。

  1. 协议的指定以及部分的逻辑的编写

  另外一个比较棘手的问题就是关于双方协议的编写,主要是需要考虑接听、挂断、拒绝、超时接听等情况都考虑在内,所以协议就有点麻烦。

  1. 本机的音频参数和对端的音频参数不一致

  早期的时候,我对这个没有经验,我没有写音频的参数可配置以及也没有进行检验,这种会出现,很多都是不很好的,然后比较脏的问题。解决方法就是:使用配置文件,来解决不同配置的问题

Qt使用QAudioInput、QAudioOutput实现局域网的音频通话的更多相关文章

  1. 用 Qt 的 QAudioOutput 类播放 WAV 音频文件

    用 Qt 的 QAudioOutput 类播放 WAV 音频文件 最近有一个项目,需要同时控制 4 个声卡播放不同的声音,声音文件很简单就是没有任何压缩的 wav 文件. 如果只是播放 wav 文件, ...

  2. 40.qt quick- 高仿微信实现局域网聊天V4版本(支持gif动图表情包、消息聊天、拖动缩放窗口)

    在上章37.qt quick- 高仿微信实现局域网聊天V3版本(添加登录界面.UDP校验登录.皮肤更换.3D旋转),我们已经实现了: 添加登录界面. UDP校验登录. 皮肤更换. 3D旋转(主界面和登 ...

  3. qt中采用宽带speex进行网络语音通话实验程序

    qt中采用宽带speex进行网络语音通话实验程序 本文博客链接:http://blog.csdn.NET/jdh99,作者:jdh,转载请注明.   环境: 主机:WIN8 开发环境:Qt5 3.1. ...

  4. WebRTC VoiceEngine综合应用示例(二)——音频通话的基本流程(转)

    下面将以实现一个音频通话功能为示例详细介绍VoiceEngine的使用,在文末将附上相应源码的下载地址.这里参考的是voiceengine\voe_cmd_test. 第一步是创建VoiceEngin ...

  5. WebRTC音频通话升级为视频通话

    我们有时候在音频通话过程中,想要改成视频通话.如果挂断当前通话再重新发起视频通话就会显得比较麻烦. 因此很多app提供了将音频通话升级成视频通话的功能,同时也有将视频通话降为音频通话的功能. 本文演示 ...

  6. AliIAC 智能音频编解码器:在有限带宽条件下带来更高质量的音频通话体验

    随着信息技术的发展,人们对实时通信的需求不断增加,并逐渐成为工作生活中不可或缺的一部分.每年海量的音视频通话分钟数对互联网基础设施提出了巨大的挑战.尽管目前全球的互联网用户绝大多数均处于良好的网络状况 ...

  7. 思必驰周强:AI 和传统信号技术在实时音频通话中的应用

    如何用 AI 解决声音传输&处理中的三大问题?三大问题又是哪三大问题? 在「RTE2022 实时互联网大会」中,思必驰研发总监 @周强以<AI 和传统信号技术在实时音频通话中的应用> ...

  8. Qt学习笔记-Qt5和Qt4在音频方面的不同-QtAV

    之前学习qt4的时候,播放音频用的是phonon播放后端插件+mplayer. 今天改用qt5了.qt5中去掉了phonon模块加了multimedia.但是依然无法播放音乐,因为没有ffmpge s ...

  9. 37.qt quick- 高仿微信实现局域网聊天V3版本(添加登录界面、UDP校验登录、皮肤更换、3D旋转)

    1.版本介绍(已上传至群里) 版本说明: 添加登录界面. UDP校验登录. 皮肤更换. 3D旋转(主界面和登录界面之间切换) . 效果图如下所示: 如果效果图加载失败,可以去哔哩哔哩 https:// ...

  10. 【Qt编程】基于Qt的词典开发系列<十三>音频播放

    在上一篇文章中,我是在Qt4平台上调用本地发音的,后来由于用到JSON解析,就将平台转到了Qt5,因为Qt5自带解析JSON的类.然后发现上一篇文章的方法无法运行,当然网上可以找到解决方法,我在这里直 ...

随机推荐

  1. Qt编写物联网管理平台33-设备面板

    一.前言 设备面板展示数据,相对于表格展示,可能在一个页面中能够展示的设备数据量少一些,但是有些用户和场景,又需要这种面板的形式,可能更生动形象一些.尤其是经过这么些年的社会的毒打,我的原则是:用户是 ...

  2. [转]gcc的-g,-o,-c,-D,-w,-W,-Wall,-O3等参数的意义

    一.-g -g可执行程序包含调试信息-g为了调试用的加个-g 是为了gdb 用,不然gdb用不到 二.-o -o指定输出文件名-o output_filename,确定输出文件的名称为output_f ...

  3. 使用 httputils + sbe (Simple Binary Encoding) 实现金融级 java rpc

    1.认识 Simple Binary Encoding (sbe) 高性能Java库 Agrona 的主要目标是减少性能瓶颈,通过提供线程安全的直接和原子缓冲区.无装箱操作的原始类型列表.开散列映射和 ...

  4. R语言画图常用参数

    plot(x,xlab = "degree",ylab = "number of nodes",main = "mRNA degree distrib ...

  5. deeplearning4j~实现简单模型训练和测试

    DeepLearning4j (DL4J) 是一个开源的深度学习库,专为 Java 和 Scala 设计.它可以用于构建.训练和部署深度学习模型.以下是关于如何使用 DL4J 的基本指南以及一个简单的 ...

  6. 前端学习openLayers配合vue3(修改地图样式)

    这一块的东西非常简单,基于上一步的继续操作 关键代码,当然对应的对象需要进行相关的引入,为了方便理解,把背景色和边框放在了一起 //填充颜色 style:new Style({ fill:new Fi ...

  7. kafka的server.properties文件描述

    版本:基于 kafka 2.4.0  http://archive.apache.org/dist/kafka/2.4.0/kafka_2.11-2.4.0.tgz # Licensed to the ...

  8. MongoDB:使用场景简介

  9. DPDK简介和原理

    本文分享自天翼云开发者社区<DPDK简介和原理>,作者:s****n DPDK是一种绕过内核直接在用户态收发包来解决内核性能的瓶颈技术. 什么是中断 了解DPDK之前,首先需要先了解什么是 ...

  10. 安全可信 | 通过双项测试!TeleDB实力亮剑!

    近日,天翼云TeleDB数据库在中国信通院"可信数据库"系列测试的赛道上,一次性跨越"分布式事务型数据库基础能力测试"与"性能测试"的双重大 ...