一年多以前曾经写过一篇用QT audio语音库实现音频的采集和播放的博文:https://blog.csdn.net/hanzhen7541/article/details/80152381
上面那个是初级版,实现的是从inputdevice音频采集,发送到目的地址端口;目的主机收到音频收到了信号,直接写入音频的设备进行播放。这几天又用到了QT的语音库,所以做了改进,因为网络环境不稳定,所以做了优化:接受端接收到音频流之后,并不直接写在输出的outputdevice进行播放,而是定义了一个缓冲区m_PCMDataBuffer,接受端sicket接收到音频流数据,调用函数addAudioBuffer向缓冲区里面写; 同时,重载了QThread的run函数,调用run()函数,每当缓冲区超过960字节(我社定的8000采样率,16比特采样,960字节对应60ms数据)就从缓冲区读取数据,写入QOutputDevice从而听到声音。
这样当网络不稳定的时候,每一包语音流有不同的延迟,在接受端也能听到较为流畅的声音。
话不多说,现在来看具体代码:
首先是发送线程,这个和之前博文里面的没用太多区别,在主函数中包含头文件之后初始化对象,调用mystart()函数就可以实现发送,调用mystop()函数就可以停止。发送端的头文件:
#ifndef AUDIOSENDTHREAD_H
#define AUDIOSENDTHREAD_H
//这是发送线程
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
class audiosendthread : public QThread
{
Q_OBJECT
public:
explicit audiosendthread(QObject *parent = nullptr);
~audiosendthread();
QUdpSocket *udpSocket;
QHostAddress destaddr;
QAudioInput *input;
QIODevice *inputDevice;
QAudioFormat format;
struct video{
int lens;
char data[960];
};
void setaudioformat(int samplerate, int channelcount, int samplesize);
void mystart();
void mystop();
public slots:
void onReadyRead();
};
#endif // AUDIOSENDTHREAD_H
发送的.cpp
#include "audiosendthread.h"
audiosendthread::audiosendthread(QObject *parent)
: QThread(parent)
{
udpSocket = new QUdpSocket(this);
udpSocket -> bind(QHostAddress::Any, 10005);
destaddr.setAddress("127.0.0.1");//改成你的目的地址就行
}
audiosendthread::~audiosendthread(){
delete udpSocket;
delete input;
delete inputDevice;
}
void audiosendthread::setaudioformat(int samplerate, int channelcount, int samplesize){
format.setSampleRate(samplerate);
format.setChannelCount(channelcount);
format.setSampleSize(samplesize);
format.setCodec("audio/pcm");
format.setSampleType(QAudioFormat::SignedInt);
format.setByteOrder(QAudioFormat::LittleEndian);
input = new QAudioInput(format, this);
}
void audiosendthread::mystart(){
qDebug()<<"audio begins to send";
inputDevice = input->start();
connect(inputDevice,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
}
void audiosendthread::mystop(){
qDebug()<<"audio ends!";
input->stop();
}
void audiosendthread::onReadyRead(){
video vp;
memset(&vp.data,0,sizeof(vp));
// read audio from input device
vp.lens = inputDevice -> read(vp.data,960);
int num = udpSocket -> writeDatagram((const char*)&vp, sizeof(vp),destaddr,10004);
qDebug()<
以及接受线程的头文件:
#ifndef AUDIO_PLAY_THREAD_H
#define AUDIO_PLAY_THREAD_H
//这是接收线程
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_AUDIO_LEN 960000 //如果接收缓冲区大于这个数值就剪掉
#define FRAME_LEN_60ms 960 //每一个语音帧长度是960字节
class AudioPlayThread : public QThread
{
Q_OBJECT
public:
AudioPlayThread(QObject *parent = nullptr);
~AudioPlayThread();
// ----------- 添加数据相关 ----------------------------------------
// 设置当前的PCM Buffer
void setCurrentBuffer(QByteArray buffer);
// 添加数据
void addAudioBuffer(char* pData, int len);
// 清空当前的数据
void cleanAllAudioBuffer(void);
// ------------- End ----------------------------------------------
// 设置当前的采样率、采样位数、通道数目
void setCurrentSampleInfo(int sampleRate, int sampleSize, int channelCount);
virtual void run(void) override;//多线程重载运行函数run
// 设置音量
void setCurrentVolumn(qreal volumn);
void stop();//停止
private:
QAudioOutput *m_OutPut = nullptr;
QIODevice *m_AudioIo = nullptr;
QByteArray m_PCMDataBuffer;
int m_CurrentPlayIndex = 0;
QMutex m_Mutex;
// 播放状态
volatile bool m_IsPlaying = true;
//for Audio
QUdpSocket *udpsocket;
struct video{
int lens;
char data[960];
};
private slots:
void readyReadSlot();
};
#endif
接受线程.cpp
#include "audioplaythread.h"
AudioPlayThread::AudioPlayThread(QObject *parent)
:QThread(parent)
{
m_PCMDataBuffer.clear();
udpsocket = new QUdpSocket(this);
udpsocket->bind(QHostAddress::Any,10004);
connect(udpsocket,SIGNAL(readyRead()),this,SLOT(readyReadSlot()));//收到网络数据报就开始往outputDevice写入,进行播放
}
AudioPlayThread::~AudioPlayThread()
{
delete udpsocket;
delete m_OutPut;
delete m_AudioIo;
}
void AudioPlayThread::setCurrentVolumn(qreal volumn){
m_OutPut->setVolume(volumn);
}
void AudioPlayThread::setCurrentSampleInfo(int sampleRate, int sampleSize, int channelCount)
{
QMutexLocker locker(&m_Mutex);
// Format
QAudioFormat nFormat;
nFormat.setSampleRate(sampleRate);
nFormat.setSampleSize(sampleSize);
nFormat.setChannelCount(channelCount);
nFormat.setCodec("audio/pcm");
nFormat.setSampleType(QAudioFormat::SignedInt);
nFormat.setByteOrder(QAudioFormat::LittleEndian);
if (m_OutPut != nullptr) delete m_OutPut;
m_OutPut = new QAudioOutput(nFormat);
m_AudioIo = m_OutPut->start();
//this->start();
}
void AudioPlayThread::run(void)
{
while (!this->isInterruptionRequested())
{
if (!m_IsPlaying)
{
break;
}
QMutexLocker locker(&m_Mutex);
if(m_PCMDataBuffer.size() < m_CurrentPlayIndex + FRAME_LEN_60ms){//缓冲区不够播放60ms音频
continue;
}
else{
//拷贝960字节的数据
char *writeData = new char[FRAME_LEN_60ms];
memcpy(writeData,&m_PCMDataBuffer.data()[m_CurrentPlayIndex], FRAME_LEN_60ms);
// 写入音频数据
m_AudioIo->write(writeData, FRAME_LEN_60ms);
m_CurrentPlayIndex += FRAME_LEN_60ms;
qDebug()< MAX_AUDIO_LEN){
m_PCMDataBuffer = m_PCMDataBuffer.right(m_PCMDataBuffer.size()-MAX_AUDIO_LEN);
m_CurrentPlayIndex -= MAX_AUDIO_LEN;
}
}
}
m_PCMDataBuffer.clear();
qDebug()<<"audio receiver stop!";
}
// 添加数据
void AudioPlayThread::addAudioBuffer(char* pData, int len)
{
QMutexLocker locker(&m_Mutex);
m_PCMDataBuffer.append(pData, len);
//m_IsPlaying = true;
}
void AudioPlayThread::cleanAllAudioBuffer(void)
{
QMutexLocker locker(&m_Mutex);
m_CurrentPlayIndex = 0;
m_PCMDataBuffer.clear();
m_IsPlaying = false;
}
void AudioPlayThread::readyReadSlot(){
while(udpsocket->hasPendingDatagrams()){
QHostAddress senderip;
quint16 senderport;
qDebug()<<"audio is being received..."<readDatagram((char*)&vp,sizeof(vp),&senderip,&senderport);
//outputDevice->write(vp.data,vp.lens);
addAudioBuffer(vp.data, vp.lens);
}
}
void AudioPlayThread::stop(){
udpsocket->close();
m_OutPut->stop();
cleanAllAudioBuffer();
}
在主函数mainwindow.h中首先定义
AudioPlayThread aud;
audiosendthread audsend;
然后在mainwindow.cpp中分别开启两个线程就行了:
//接受线程启动
aud.setCurrentSampleInfo(8000,16,1);
aud.setCurrentVolumn(100);
aud.start();
//发送线程启动
audsend.setaudioformat(8000,1,16);
audsend.mystart();
//... 其他代码
//结束两个线程
audsend.mystop();
aud.stop();
别忘了在.pro文件中首先加入下面两个库,否则会造成编译错误:
QT += network
QT += multimedia
以上就是QT发送接受语音的实现,欢迎指正。
相关代码已经上传至github: https://github.com/Wzing0421/QTAudio