QT采用QAudioInput、QAudioOutput实现远程语音对讲功能

896 阅读8分钟

一、环境介绍

QT版本: 5.12.6

操作系统:  Android、ubuntu、windows 都测试OK。

二、功能介绍

两台设备之间实现语音对讲功能。类似于QQ、微信语音电话效果。

采用QAudioInput获取声卡输出,QAudioOutpu用于输出数据到声卡。

音频数据采用TCP网络方式双向传输。

(1)对讲APP服务器端:  服务器收到客户端的数据就播放,同时采集声卡数据发送给客户端。

(2) 对讲APP客户端:客户端收到服务器的数据就播放,同时采集声卡数据发送给服务器。

对讲APP服务器端

对讲APP客户端

三、音频客户端: 核心代码

AudioInputOut.h文件代码:
#ifndef AUDIOINPUTOUT_H
#define AUDIOINPUTOUT_H
#include <QObject>
#include <QWidget>
#include <QTcpServer>
#include <QHostInfo>  //获取计算机网络信息
#include <QUdpSocket>
#include <QtNetwork>
#include <QHostInfo>
#include <QDebug>
#include <QTcpSocket>
#include <QHostAddress>
#include <QDebug>
#include <QMessageBox>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QComboBox>
#include <QFile>
#include <QTimer>
#include <QScrollBar>
#include <QScrollBar>
#include <QAudio>     //这五个是QT处理音频的库
#include <QAudioFormat>
#include <QAudioInput>
#include <QAudioOutput>
#include <QIODevice>

#define AUDIO_SERVER_IP "192.168.1.10"
#define AUDIO_SERVER_PROT 8888

//用于连接服务器,实现语音对讲功能
class AudioInputOutput : public QObject
{
    Q_OBJECT
public:
    QTcpSocket *LocalTcpClientSocket;
    AudioInputOutput(QObject* parent=nullptr):QObject(parent)
    {
        LocalTcpClientSocket=nullptr;
        audio_in=nullptr;
        audio_out=nullptr;
    }
    void TCP_ConnectServer(QString Ipaddr, quint16 prot);

    void Audio_in_Init();
    void Audio_Out_Init();
    QAudioInput *audio_in;
    QAudioOutput *audio_out;
    QIODevice* audio_streamIn;
    QIODevice* audio_out_streamIn;
public slots:
    void LocalTcpClientConnectedSlot();
    void LocalTcpClientDisconnectedSlot();
    void LocalTcpClientReadDtatSlot();
    void run();
    void handleStateChanged_input(QAudio::State newState);
    void audio_ReadyRead();
    void stop_connect();
signals:
    void LogSend(QString text);
};
#endif // AUDIOINPUTOUT_H

AudioInputOut.cpp文件代码:

#include "AudioInputOut.h"

//创建客户端,连接至服务器
void AudioInputOutput::TCP_ConnectServer(QString Ipaddr,quint16  prot)
{
      /* 设置服务器IP地址*/
    QHostAddress FarServerAddr(Ipaddr);
    if(LocalTcpClientSocket==nullptr)
    {
        /*1. 创建本地客户端TCP套接字*/
        LocalTcpClientSocket = new QTcpSocket;
        /*2. 连接客户端的信号槽*/
        connect(LocalTcpClientSocket,SIGNAL(connected()),this,SLOT(LocalTcpClientConnectedSlot()));
        connect(LocalTcpClientSocket,SIGNAL(disconnected()),this,SLOT(LocalTcpClientDisconnectedSlot()));
        connect(LocalTcpClientSocket,SIGNAL(readyRead()),this,SLOT(LocalTcpClientReadDtatSlot()));
    }
    /*3. 尝试连接服务器主机*/
    LocalTcpClientSocket->connectToHost(FarServerAddr,prot);
}

//开始执行程序
void AudioInputOutput::run()
{
    TCP_ConnectServer(AUDIO_SERVER_IP,AUDIO_SERVER_PROT);
    Audio_in_Init();
    Audio_Out_Init();
}

//客户端模式:响应连接上服务器之后的操作
void AudioInputOutput::LocalTcpClientConnectedSlot()
{
    LocalTcpClientSocket->write("device connect server.");
    LogSend("连接上服务器.\n");
}

//客户端模式:断开服务器
void AudioInputOutput::LocalTcpClientDisconnectedSlot()
{
    //停止音频输入输出
    audio_in->stop();
    audio_out->stop();
    LogSend("服务器断开.\n");
}

//客户端模式:读取服务器发过来的数据
void AudioInputOutput::LocalTcpClientReadDtatSlot()
{
   QByteArray array=LocalTcpClientSocket->readAll();
   audio_out_streamIn->write(array);
}


//音频输出初始化
void AudioInputOutput::Audio_Out_Init()
{
    QAudioFormat auido_out_format;
    //设置录音的格式
    auido_out_format.setSampleRate(44100); //设置采样率以对赫兹采样。 以秒为单位,每秒采集多少声音数据的频率.
    auido_out_format.setChannelCount(1);   //将通道数设置为通道。
    auido_out_format.setSampleSize(16);     /*将样本大小设置为指定的sampleSize(以位为单位)通常为8或16,但是某些系统可能支持更大的样本量。*/
    auido_out_format.setCodec("audio/pcm"); //设置编码格式
    auido_out_format.setByteOrder(QAudioFormat::LittleEndian); //样本是小端字节顺序
    auido_out_format.setSampleType(QAudioFormat::SignedInt); //样本类型
    QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
    if(audio_out)
    {
        delete audio_out;
        audio_out=nullptr;
    }
    audio_out = new QAudioOutput(auido_out_format);
    audio_out_streamIn=audio_out->start();
}

// 音频输入初始化
void AudioInputOutput::Audio_in_Init()
{
    QString text;
    QAudioFormat auido_input_format;

    //设置录音的格式
    auido_input_format.setSampleRate(44100); //设置采样率以对赫兹采样。 以秒为单位,每秒采集多少声音数据的频率.
    auido_input_format.setChannelCount(1);   //将通道数设置为通道。
    auido_input_format.setSampleSize(16);     /*将样本大小设置为指定的sampleSize(以位为单位)通常为8或16,但是某些系统可能支持更大的样本量。*/
    auido_input_format.setCodec("audio/pcm"); //设置编码格式
    auido_input_format.setByteOrder(QAudioFormat::LittleEndian); //样本是小端字节顺序
    auido_input_format.setSampleType(QAudioFormat::SignedInt); //样本类型
    //选择设备作为输入源
    QAudioDeviceInfo info =QAudioDeviceInfo::defaultInputDevice();

    emit LogSend(tr("当前的录音设备的名字:%1\n").arg(info.deviceName()));

    //判断输入的格式是否支持,如果不支持就使用系统支持的默认格式
    if(!info.isFormatSupported(auido_input_format))
    {
      emit LogSend("返回与系统支持的提供的设置最接近的QAudioFormat\n");
      auido_input_format=info.nearestFormat(auido_input_format);
      /*
       * 返回与系统支持的提供的设置最接近的QAudioFormat。
         这些设置由所使用的平台/音频插件提供。
         它们还取决于所使用的QAudio :: Mode。
      */
    }
    //当前设备支持的编码
    emit LogSend("当前设备支持的编码格式:\n");
    QStringList list=info.supportedCodecs();
    for(int i=0;i<list.size();i++)
    {
        text=list.at(i)+"\n";
        emit LogSend(text);
    }
    emit LogSend(tr("当前录音的采样率=%1\n").arg(auido_input_format.sampleRate()));

    emit LogSend(tr("当前录音的通道数=%1\n").arg(auido_input_format.channelCount()));

    emit LogSend(tr("当前录音的样本大小=%1\n").arg(auido_input_format.sampleSize()));

    emit LogSend(tr("当前录音的编码格式=%1\n").arg(auido_input_format.codec()));
    if(audio_in)
    {
        delete audio_in;
        audio_in=nullptr;
    }
    audio_in = new QAudioInput(auido_input_format);
    connect(audio_in,SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged_input(QAudio::State)),Qt::QueuedConnection);
    audio_streamIn=audio_in->start(); //开始音频采集
    //关联音频读数据信号
    connect(audio_streamIn,SIGNAL(readyRead()),this,SLOT(audio_ReadyRead()),Qt::QueuedConnection);
}

//有音频信号可以读
void AudioInputOutput::audio_ReadyRead()
{
    QByteArray byte=audio_streamIn->readAll();
    //判断服务器是否处于连接状态
    if(LocalTcpClientSocket->socketDescriptor()!=-1)
    {
        LocalTcpClientSocket->write(byte);
    }
}

//录音状态
void AudioInputOutput::handleStateChanged_input(QAudio::State newState)
{
 switch (newState) {
     case QAudio::StoppedState:
         if (audio_in->error() != QAudio::NoError) {
             // Error handling
             qDebug()<<"录音出现错误.\n";
         } else {
             // Finished recording
             qDebug()<<"完成录音\n";
         }
         break;
     case QAudio::ActiveState:
         // Started recording - read from IO device
            qDebug()<<"开始从IO设备读取PCM声音数据.\n";
         break;
     default:
         // ... other cases as appropriate
         break;
 }
}

//停止连接
void AudioInputOutput::stop_connect()
{
    //判断服务器是否处于连接状态
    if(LocalTcpClientSocket->socketDescriptor()!=-1)
    {
        //断开服务器连接
        LocalTcpClientSocket->disconnectFromHost();
        //停止音频输入输出
        audio_in->stop();
        audio_out->stop();
    }
}
RecvServerCMD.h
#ifndef RECVSERVERCMD_H
#define RECVSERVERCMD_H
#include <QObject>
#include <QWidget>
#include <QTcpServer>
#include <QHostInfo>  //获取计算机网络信息
#include <QUdpSocket>
#include <QtNetwork>
#include <QHostInfo>
#include <QDebug>
#include <QTcpSocket>
#include <QHostAddress>
#include <QDebug>
#include <QMessageBox>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QComboBox>
#include <QFile>
#include <QTimer>
#include <QScrollBar>
#include <QScrollBar>
#include <QAudio>     //这五个是QT处理音频的库
#include <QAudioFormat>
#include <QAudioInput>
#include <QAudioOutput>
#include <QIODevice>
#include <QTimer>

#define RECV_SERVER_CMD_IP "127.0.0.1"
#define RECV_SERVER_CMD_PROT 6666

//用于连接服务器,接收服务器下发的命令
class RecvServerCmd : public QObject
{
    Q_OBJECT
public:
    QTcpSocket *LocalTcpClientSocket;
    QTimer *timer_check_connect;
    RecvServerCmd(QObject* parent=nullptr):QObject(parent){LocalTcpClientSocket=nullptr;}
    void TCP_ConnectServer(QString Ipaddr, quint16 prot);
public slots:
    void LocalTcpClientConnectedSlot();
    void LocalTcpClientDisconnectedSlot();
    void LocalTcpClientReadDtatSlot();
    void run();
    void check_connect();
signals:
    void LogSend(QString text);
};
#endif // RECVSERVERCMD_H

RecvServerCMD.cpp

#include "RecvServerCMD.h"
//创建客户端,连接至服务器
void RecvServerCmd::TCP_ConnectServer(QString Ipaddr,quint16  prot)
{
    if(LocalTcpClientSocket==nullptr)
    {
        /*1. 创建本地客户端TCP套接字*/
        LocalTcpClientSocket = new QTcpSocket;
        /*2. 设置服务器IP地址*/
        QHostAddress FarServerAddr(Ipaddr);
        /*3. 连接客户端的信号槽*/
        connect(LocalTcpClientSocket,SIGNAL(connected()),this,SLOT(LocalTcpClientConnectedSlot()));
        connect(LocalTcpClientSocket,SIGNAL(disconnected()),this,SLOT(LocalTcpClientDisconnectedSlot()));
        connect(LocalTcpClientSocket,SIGNAL(readyRead()),this,SLOT(LocalTcpClientReadDtatSlot()));
        /*4. 尝试连接服务器主机*/
        LocalTcpClientSocket->connectToHost(FarServerAddr,prot);
        /*5. 检测服务器是否断开,并发送心跳包*/
        timer_check_connect=new QTimer;
        connect(timer_check_connect,SIGNAL(timeout()),this,SLOT(check_connect()));
        timer_check_connect->start(1000*30);
    }
}

//开始执行程序
void RecvServerCmd::run()
{
    TCP_ConnectServer(RECV_SERVER_CMD_IP,RECV_SERVER_CMD_PROT);
}

//客户端模式:响应连接上服务器之后的操作
void RecvServerCmd::LocalTcpClientConnectedSlot()
{
    LocalTcpClientSocket->write("device connect server.");
    LogSend("命令长连接:连接上服务器.\n");
}

//客户端模式:断开服务器
void RecvServerCmd::LocalTcpClientDisconnectedSlot()
{
    LogSend("命令长连接:服务器断开.\n");
}

//客户端模式:读取服务器发过来的数据
void RecvServerCmd::LocalTcpClientReadDtatSlot()
{
   QByteArray array=LocalTcpClientSocket->readAll();
   qDebug()<<"服务器下发的命令:"<<array;
   LocalTcpClientSocket->write(array);
}

//心跳包
void RecvServerCmd::check_connect()
{
    //判断该客户端是否已经断开
    if(LocalTcpClientSocket->socketDescriptor()==-1)
    {
        /*4. 尝试连接服务器主机*/
        LocalTcpClientSocket->connectToHost(QHostAddress(RECV_SERVER_CMD_IP),RECV_SERVER_CMD_PROT);
    }
    else
    {
        LocalTcpClientSocket->write("device connect ok.");
    }
}

四、音频服务器端: 核心代码

widget.cpp代码:
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    audio_in=nullptr;
    audio_out=nullptr;
    audio_streamIn=nullptr;
    audio_out_streamIn=nullptr;

    QList<QHostAddress> list = QNetworkInterface::allAddresses();
    for(int i=0;i<list.count();i++)
    {
        QHostAddress addr=list.at(i);
        if(addr.protocol() == QAbstractSocket::IPv4Protocol)
        {
          ui->comboBox->addItem(addr.toString());
        }
    }
}


Widget::~Widget()
{
    delete ui;
}


//服务器模式:创建服务器
void Widget::Create_Server()
{
    /*1. 实例化服务器*/
    LocalTcpServer= new QTcpServer;
    /*2. 设置监听的端口和IP地址*/
    QHostAddress addr(ui->comboBox->currentText());
    LocalTcpServer->listen(addr,8888);
    /*3. 关联连接信号,检测是否有新的客户端连接*/
    connect(LocalTcpServer,SIGNAL(newConnection()),this,SLOT(NewTcpConnection()));
}

//服务器模式:响应新连接的客户端
void Widget::NewTcpConnection()
{
    /*创建本地服务器套接字*/
    QTcpSocket *ServerSocket=LocalTcpServer->nextPendingConnection();
    /*关联可读信号*/
    connect(ServerSocket,SIGNAL(readyRead()),this,SLOT(ReadTcpClientData()));
    /*关联断开信号*/
    connect(ServerSocket,SIGNAL(disconnected()),this,SLOT(TcpClientDisconnected()));

    TcpFarClientList.append(ServerSocket);//添加到列表
    //显示已经连接的客户端
    ui->ClientComboBoxList->clear();
    for(int i=0;i<TcpFarClientList.count();i++)
    {
        QString info=TcpFarClientList.at(i)->peerAddress().toString();
        info+=":";
        info+=QString::number(TcpFarClientList.at(i)->peerPort());
        ui->ClientComboBoxList->addItem(info);
    }
}

//服务器模式:响应断开的客户端
void Widget::TcpClientDisconnected()
{
    for(int i=0;i<TcpFarClientList.count();i++)
    {
        //取出地址列表中的一个客户端地址
        QTcpSocket *item = TcpFarClientList.at(i);
        //判断该客户端是否已经断开
        if(item->socketDescriptor()==-1)
        {
            TcpFarClientList.removeAt(i);
        }
    }
    //显示已经连接的客户端
    ui->ClientComboBoxList->clear();
    for(int i=0;i<TcpFarClientList.count();i++)
    {
        QString info=TcpFarClientList.at(i)->peerAddress().toString();
        info+=":";
        info+=QString::number(TcpFarClientList.at(i)->peerPort());
        ui->ClientComboBoxList->addItem(info);
    }
}

//服务器模式:读数据
void Widget::ReadTcpClientData()
{
    for(int i=0;i<TcpFarClientList.count();i++)
    {
       if(!TcpFarClientList.at(i)->atEnd())
       {
          QByteArray array=TcpFarClientList.at(i)->readAll();
          audio_out_streamIn->write(array);
       }
    }
}

//音频输出初始化
void Widget::Audio_Out_Init()
{
    QAudioFormat auido_out_format;
    //设置录音的格式
    auido_out_format.setSampleRate(44100); //设置采样率以对赫兹采样。 以秒为单位,每秒采集多少声音数据的频率.
    auido_out_format.setChannelCount(1);   //将通道数设置为通道。
    auido_out_format.setSampleSize(16);     /*将样本大小设置为指定的sampleSize(以位为单位)通常为8或16,但是某些系统可能支持更大的样本量。*/
    auido_out_format.setCodec("audio/pcm"); //设置编码格式
    auido_out_format.setByteOrder(QAudioFormat::LittleEndian); //样本是小端字节顺序
    auido_out_format.setSampleType(QAudioFormat::SignedInt); //样本类型
    QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
    if(audio_out)
    {
        delete audio_out;
        audio_out=nullptr;
    }
    audio_out = new QAudioOutput(auido_out_format);
    audio_out_streamIn=audio_out->start();
}

// 音频输入初始化
void Widget::Audio_in_Init()
{
    QString text;
    QAudioFormat auido_input_format;

    //设置录音的格式
    auido_input_format.setSampleRate(44100); //设置采样率以对赫兹采样。 以秒为单位,每秒采集多少声音数据的频率.
    auido_input_format.setChannelCount(1);   //将通道数设置为通道。
    auido_input_format.setSampleSize(16);     /*将样本大小设置为指定的sampleSize(以位为单位)通常为8或16,但是某些系统可能支持更大的样本量。*/
    auido_input_format.setCodec("audio/pcm"); //设置编码格式
    auido_input_format.setByteOrder(QAudioFormat::LittleEndian); //样本是小端字节顺序
    auido_input_format.setSampleType(QAudioFormat::SignedInt); //样本类型
    //选择设备作为输入源
    QAudioDeviceInfo info =QAudioDeviceInfo::defaultInputDevice();

    ui->plainTextEdit->appendPlainText(tr("当前的录音设备的名字:%1\n").arg(info.deviceName()));

    //判断输入的格式是否支持,如果不支持就使用系统支持的默认格式
    if(!info.isFormatSupported(auido_input_format))
    {
      ui->plainTextEdit->appendPlainText("返回与系统支持的提供的设置最接近的QAudioFormat\n");
      auido_input_format=info.nearestFormat(auido_input_format);
      /*
       * 返回与系统支持的提供的设置最接近的QAudioFormat。
         这些设置由所使用的平台/音频插件提供。
         它们还取决于所使用的QAudio :: Mode。
      */
    }
    //当前设备支持的编码
    ui->plainTextEdit->appendPlainText("当前设备支持的编码格式:\n");
    QStringList list=info.supportedCodecs();
    for(int i=0;i<list.size();i++)
    {
        text=list.at(i)+"\n";
       ui->plainTextEdit->appendPlainText(text);
    }
    ui->plainTextEdit->appendPlainText(tr("当前录音的采样率=%1\n").arg(auido_input_format.sampleRate()));

    ui->plainTextEdit->appendPlainText(tr("当前录音的通道数=%1\n").arg(auido_input_format.channelCount()));

    ui->plainTextEdit->appendPlainText(tr("当前录音的样本大小=%1\n").arg(auido_input_format.sampleSize()));

    ui->plainTextEdit->appendPlainText(tr("当前录音的编码格式=%1\n").arg(auido_input_format.codec()));
    if(audio_in)
    {
        delete audio_in;
        audio_in=nullptr;
    }
    audio_in = new QAudioInput(auido_input_format);
    connect(audio_in,SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged_input(QAudio::State)),Qt::QueuedConnection);
    audio_streamIn=audio_in->start(); //开始音频采集
    //关联音频读数据信号
    connect(audio_streamIn,SIGNAL(readyRead()),this,SLOT(audio_ReadyRead()),Qt::QueuedConnection);
}

//有音频信号可以读
void Widget::audio_ReadyRead()
{
    QByteArray byte=audio_streamIn->readAll();
    //判断是否处于连接状态
    for(int i=0;i<TcpFarClientList.count();i++)
    {
        //取出地址列表中的一个客户端地址
        QTcpSocket *item = TcpFarClientList.at(i);
        //判断该客户端是否已经断开
        if(item->socketDescriptor()!=-1)
        {
            item->write(byte);
        }
    }
}


//录音状态
void Widget::handleStateChanged_input(QAudio::State newState)
{
 switch (newState) {
     case QAudio::StoppedState:
         if (audio_in->error() != QAudio::NoError) {
             // Error handling
             qDebug()<<"录音出现错误.\n";
         } else {
             // Finished recording
             qDebug()<<"完成录音\n";
         }
         break;
     case QAudio::ActiveState:
         // Started recording - read from IO device
            qDebug()<<"开始从IO设备读取PCM声音数据.\n";
         break;
     default:
         // ... other cases as appropriate
         break;
 }
}


void Widget::on_pushButton_clicked()
{
    Create_Server();
    Audio_Out_Init();
    Audio_in_Init();
}

widget.h代码:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QWidget>
#include <QTcpServer>
#include <QHostInfo>  //获取计算机网络信息
#include <QUdpSocket>
#include <QtNetwork>
#include <QHostInfo>
#include <QDebug>
#include <QTcpSocket>
#include <QHostAddress>
#include <QDebug>
#include <QMessageBox>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QComboBox>
#include <QFile>
#include <QTimer>
#include <QScrollBar>
#include <QScrollBar>
#include <QAudio>     //这五个是QT处理音频的库
#include <QAudioFormat>
#include <QAudioInput>
#include <QAudioOutput>
#include <QIODevice>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    QTcpServer *LocalTcpServer;
    QList<QTcpSocket*> TcpFarClientList;
    void Create_Server();

    //对讲功能
    QThread *audioInputOutput_thread;
    void Audio_in_Init();
    void Audio_Out_Init();
    QAudioInput *audio_in;
    QAudioOutput *audio_out;
    QIODevice* audio_streamIn;
    QIODevice* audio_out_streamIn;

private slots:
    void TcpClientDisconnected();
    void ReadTcpClientData();
    void NewTcpConnection();
    void handleStateChanged_input(QAudio::State newState);
    void audio_ReadyRead();
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

 

 

下面公众号有全套QT、C++\C\单片机基础教程: