qt使用qaudioinputqaudiooutput实现局域网的音频通话(代码片段)

师从名剑山 师从名剑山     2023-03-30     777

关键词:

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

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

文章目录

项目背景

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

技术实现

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

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

QAudioFormat(音频采样格式)

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

ParameterDescription
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 插件管理 Qt 对象

】使用Qt插件管理Qt对象【英文标题】:QtobjectmanagementwithQtPlugins【发布时间】:2015-12-1613:07:53【问题描述】:我的问题是在使用Qt插件时如何进行适当的对象/资源管理。默认RAII似乎不适用于Qt。在我们的应用程序中,我们使用在... 查看详情

Qt:如何使用 QT 复制大数据?

】Qt:如何使用QT复制大数据?【英文标题】:Qt:HowcanIcopyabigdatausingQT?【发布时间】:2018-04-0512:47:30【问题描述】:我想读取一个大数据,然后使用Qt将其写入一个新文件。我试图读取一个大文件。而且大文件只有一行。我使用rea... 查看详情

通过 Qt Jambi 使用粒子(Qt 的惊人功能)

】通过QtJambi使用粒子(Qt的惊人功能)【英文标题】:UsingParticles(theamazingfeautureofQt)throughQtJambi【发布时间】:2015-01-0301:11:16【问题描述】:标题说明了一切;我正在尝试通过QtJambiLibraries使用Qt中可用的粒子功能。我知道要在Qt中... 查看详情

非qt工程使用qt的信号槽机制

非Qt工程,使用Qt的信号槽机制,蛋疼不?反正我现在就是要做这样一件蛋疼的事。要使用Qt的信号槽机制,下面是从QtAssist里面关于signal&slots的一句介绍:AllclassesthatcontainsignalsorslotsmustmentionQ_OBJECTatthetopoftheirdeclaration.Theymustalsode... 查看详情

qt博客汇总

...特殊效果窗体QtCreator常用快捷键1.2常用窗口部件Qt—QLabel使用总结Qt—QPushButton使用总结Qt—QLineEdit使用总结Qt—QCheckBox使用总结Qt—QSlider使用总结1.3布局Qt布局之一:布局初探Qt布局之二:水平、垂直布局的使用详解Qt布局之三:... 查看详情

qt4安装使用qserialport

     本来一直使用的是Qt5自带的QSerialPort,但是Qt5无法在xp系统上运行,考虑到公司的电脑都是xp的,为了兼容性,这里准备使用Qt4去进行开发,但是考虑到Qt4本身没有串口类,多数是使用第三方的类库。考虑到... 查看详情

Qt - 使用内置翻译

】Qt-使用内置翻译【英文标题】:Qt-Usebuiltintranslations【发布时间】:2017-04-2615:35:04【问题描述】:我正在使用Qt并希望翻译Qt小部件显示的“本地”文本。例如,“本机显示的文本”指的是上下文菜单中显示的文本编辑(复制、... 查看详情

Qt::使用dbus需要配置啥?

】Qt::使用dbus需要配置啥?【英文标题】:Qt::Whatneedstobeincludedintheconfigurationtousedbus?Qt::使用dbus需要配置什么?【发布时间】:2011-01-1200:30:00【问题描述】:我正在使用尽可能精简的Qt配置,但现在我需要使用dbus并且无法弄清楚我... 查看详情

qt的安装和使用中的常见问题(简略版)(代码片段)

对于喜欢研究细节的朋友,可参考Qt的安装和使用中的常见问题(详细版)。目录1、引入2、Qt简介3、Qt版本3.1查看安装的Qt版本3.2查看当前项目使用的Qt版本3.3查看当前项目使用的QtCreator版本3.4Linux命令行下查看和使用不同版本的... 查看详情

如何在xcode中进行qt开发(可使用使用homebrew来安装qt)

第一步安装Qt分为两种情况:1.希望使用QtQuick等先进Qt5技术,2.只需要Qt4.8的类库即可.第一种,直接去官网下载Mac安装包,在此不作过多说明,开发时也不建议使用Xcode,不如直接使用QtCreator.第二种,建议使用 Homebrew 来安装.brewinstal... 查看详情

如何将 Qt 与 Rust 一起使用?

】如何将Qt与Rust一起使用?【英文标题】:HowtouseQtwithRust?【发布时间】:2017-03-1517:24:45【问题描述】:今天我决定学习Rust,我想知道将Qt与Rust结合使用有多难。第一个谷歌结果给了我qt.rs,所以我决定试一试。我的Qt安装位于C:\... 查看详情

在ros中使用qt界面

...导入到Qt即可这里参考的是qt_createTutorialsQtAppTemplates(1)要使用catkin_create_qt_pkg命令需要安装一个包,执行如下命令:$sudoapt-getinstallros-indigo-qt-ros这样我就可以使用catkin_cre 查看详情

使用cmake构建qt程序

...切换版本麻烦,可能导致某些依赖系统Qt的程序无法正常使用去官网使用离线安装包或在线安装包安装任意路径即可在CMakeLists.txt文件中添加如下,指定Qt库位置 查看详情

qt使用2:qt例程

转至:http://blog.51cto.com/9291927/2138876Qt开发学习教程一、Qt开发基础学习教程本部分博客主要根据狄泰学院唐老师的《QT实验分析教程》创作,同时根据天山老妖自己的理解和网络搜集的资料及QT官方文档对部分知识点进行了扩展。... 查看详情

如何使用 Qt 获取屏幕尺寸?

】如何使用Qt获取屏幕尺寸?【英文标题】:HowtogetscreendimensionsusingQt?【发布时间】:2011-06-2717:03:52【问题描述】:我需要使用Qt获取屏幕尺寸。我想我会在这里使用Qt移动API:http://doc.qt.nokia.com/qtmobility-1.0-tp/但我看不到任何可以... 查看详情

一qt初尝试,做一个qt计算器《qt入门到实战》(代码片段)

...信号与槽的设置与编写了解控件添加的方式了解控件如何使用代码获取其文本了解控件如何使用代码设置其文本使用connect自定义信号与槽了解使用样式修饰控件外观了解使用代码清空控件文本学习使用Qt编写一个四则算术计算器... 查看详情

如何使用基于 Qt 的依赖项来组织 Qt DLL

】如何使用基于Qt的依赖项来组织QtDLL【英文标题】:HowtoorganizeQtDLLswithQt-baseddependency【发布时间】:2013-10-2919:09:20【问题描述】:我有一个使用Qt4.8.5的Qt应用程序。此应用程序依赖于使用Qt4.6.0构建的DLL。我们称之为“MyDLL.dll”... 查看详情

如何使用 meta-toolchain-qt5 构建 Qt(支持 QtWebEngine)?

】如何使用meta-toolchain-qt5构建Qt(支持QtWebEngine)?【英文标题】:HowtobuildQt(withQtWebEnginesupport)usingmeta-toolchain-qt5?【发布时间】:2016-03-3019:54:53【问题描述】:我正在尝试使用meta-toolchain-qt5构建Qt,但是当我通过poky-glibc-x86_64-meta-t... 查看详情