Qt使用QAudioInputQAudioOutput实现局域网的音频通话

Posted 师从名剑山

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt使用QAudioInputQAudioOutput实现局域网的音频通话相关的知识,希望对你有一定的参考价值。

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——一些工具的使用

本文主要介绍在windows系统中使用C++编写Qt程序所需要的一些工具,不会具体地讲工具怎么使用。

其它系统的安装本文不会涉及,在http://wiki.qt.io/Main中,有关于各种系统qt安装的相关说明。

一、用什么编写Qt程序

平时我们编写C++程序一般是用Visual Studio,所以可以用它来编码,不过需要安装一些插件。

不过,Qt官方提供的工具Qt Creator,安装之后就可以直接使用,不需要额外安装插件了。关于Qt Creator的介绍,请参考官方文档

这里是Qt安装包的下载地址,根据我们的系统和开发需求选择合适的进行下载:http://download.qt.io/archive/qt/

有一点需要注意:

msvc版本是用于Visual Studio的,安装它我们仍然可以使用Qt Creator编写运行程序,只是刚安装完不能调试,需要自己指定调试器。如果安装mingw版本的话,可以直接调试,关于mingw的介绍请参考http://wiki.qt.io/Mingw

根据自己的喜好和习惯选择合适的编码工具,比如我平时习惯用VS,虽然刚开始配置麻烦些,但它的快捷、纠错能力比Qt Creator强太多,个人觉得。

 

二、使用VS编写Qt程序

使用VS需要安装下面这些工具——

1.Visual Studio(不用说太多)

2.Qt安装包(在http://download.qt.io/archive/qt/中选择msvc版本的)

3.VS的Qt插件(在http://download.qt.io/archive/vsaddin/下载)

4.最好再安装一个VAssistX(因为方便代码编写提示嘛)

上面这些安装过程我就不提了,下面讲讲安装完成之后需要做哪些事,以及有哪些小技巧。

1、VS中Qt插件的设置

配置开发环境,选择VS的Qt插件 - Qt Options - Add

将Qt安装路径中的msvc文件夹添加进去——

这里建议Qt安装为32位的,与VS保持一致,不过也得看实际情况。设置之后程序仍然不能运行,一般就是和Qt的版本有关。

2、VAssistX的设置(并非如此,不用设置)

VAssistX - C/C++ Directories - Qt安装目录中的include文件夹

将include文件夹添加进去。

如图,设置完成后,选择VAssistX - Performance - Rebuild,然后重启VS。接下来写代码时就有相关提示啦!

除了上面所说的,我们也可以自己添加一些自动补全,以提高工作效率。在VAssistX - Suggestions - Edit VA Snippets中添加。

3.Qt Creator调试问题

使用msvc版本的qt creator时,会遇到下面这个情况:

问题很明显,没有指定调试器。

首先去微软官网下载安装cdb——点我

安装之后的cdb一般在自己电脑中C:\\Program Files (x86)\\Windows Kits\\8.1\\Debuggers文件夹下面,win10在C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86下面

将其添加进去就可以了:

 

that\'s all.

以上是关于Qt使用QAudioInputQAudioOutput实现局域网的音频通话的主要内容,如果未能解决你的问题,请参考以下文章

一Qt初尝试,做一个QT计算器《QT 入门到实战》

使用 Qt 插件管理 Qt 对象

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

一Qt初尝试,做一个QT计算器《QT 入门到实战》

一Qt初尝试,做一个QT计算器《QT 入门到实战》

一Qt初尝试,做一个QT计算器《QT 入门到实战》