一、环境介绍
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\单片机基础教程: