一个音频发送接收的程序,服务器端发送音频,客户端进行音频接收并播放。需要增加一个麦克风热插拔的功能,就是在音频传输过程中,拔掉麦克风会导致音频发送中断,重新连接麦克风后保证音频仍能继续发送。
源程序的音频代码使用windows下waveIn系列函数,waveInOpen函数采用回调的形式获取音频。当发生麦克风热插拔操作后,会无法重新获取音频。我想到的办法是,加入麦克风热插拔检测的功能,在录音过程中实时检测麦克风的连接,当检测到麦克风断开连接后,就使用waveInStop、waveInReset和waveInClose函数关闭音频设备接口。当检测到麦克风重新连接后,再调用waveInOpen函数开启设备连接,从而继续录音并发送。
结果这么做并不能实现,在debug过程中,发现只要程序执行到任何一句waveIn函数,都会产生死锁,这个思路时不可行的。
后来尝试了很多方法,都不可以实现,网上说在waveInOpen函数中采用线程的形式获取音频,但我并不知道该怎么写。最终决定修改整个音频代码,放弃waveIn,采用qt中的QAudioInput类,最后成功解决问题。实现过程如下。
为音频发送模块开辟一个工作线程,主线程用来控制软件界面操作,两个线程之间采用QMutex进行线程同步。以下是音频部分代码。
工作线程头文件:
#include <QThread>
#include <QMutex>
#include <iostream>
#include <Qlist.h>
#include <qaudiodeviceinfo.h>
#include <qaudioinput.h>
#include <qaudioformat.h>
#include <QIODevice>
#include <qthread.h>
#include <qobject.h>
#include <qdebug.h>
#include <qaudio.h>
#include <QtNetwork\qudpsocket.h>
#define AUDIOBUFFER_SIZE 2048
using namespace std;
typedef enum
{
OPENED,
CLOSED
}AudioDeviceState;
typedef enum
{
FIRST,
SECOND
}IODeviceState;
struct audioBuffer{
char audioData[2048];
int lens;
};
class WorkerThread : public QThread
{
Q_OBJECT
protected:
void run();
public:
explicit WorkerThread(QObject *parent = NULL);
~WorkerThread();
bool openAudioDevice(void);
void initAudioDevice(void);
void mystop();
int isAudioReady(void);
void waveInGetSound();
private:
QList<QAudioDeviceInfo> devicelist; //音频设备列表
QAudioInput *audioInput; //音频输入
QAudioFormat pFormat; //音频格式
QIODevice *inputDevice; //音频设备
audioBuffer buff; //音频数据存储
AudioDeviceState state;
IODeviceState iostate;
QUdpSocket *udpSocket;
};
工作线程.cpp
#include "workerthread.h"
WorkerThread::WorkerThread(QObject *parent): QThread(parent)
{
message = MS_NULL;
iostate=FIRST;
}
WorkerThread::~WorkerThread(){}
void WorkerThread::initAudioDevice(void)
{
pFormat.setCodec("audio/pcm");
pFormat.setChannelCount(1);
pFormat.setSampleRate(8000);
pFormat.setSampleSize(16);
pFormat.setByteOrder(QAudioFormat::LittleEndian);
pFormat.setSampleType(QAudioFormat::SignedInt);
audioInput = new QAudioInput(pFormat);
}
bool WorkerThread::openAudioDevice(void)
{
devicelist=QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
if(devicelist.size()>0)
{
state=OPENED;
qDebug() << "true";
return true;
}else
{
state=CLOSED;
qDebug() << "false";
return false;
}
}
void WorkerThread::mystop()
{
audioInput->stop();
state=CLOSED;
}
void WorkerThread::waveInGetSound() //音频发送
{
memset(&buff,0,sizeof(buff));
buff.lens=inputDevice->read(buff.audioData,AUDIOBUFFER_SIZE);
udpSocket->writeDatagram((const char*)&buff,sizeof(buff),QHostAddress::Broadcast,port_num); //广播形式发送音频
qDebug() << buff.lens;
}
void WorkerThread::run()
{
udpSocket = new QUdpSocket();
udpSocket->bind(QHostAddress::Any,port_num);
initAudioDevice();
while(true)
{
if(iostate==FIRST)
{
openAudioDevice(); //检测麦克风连接状态
if(state==OPENED)
{
inputDevice = audioInput->start(); //开始录音
waveInGetSound();
iostate=SECOND;
}
}else{
openAudioDevice();
if(state==OPENED){
waveInGetSound();
}else{
mystop(); //关闭录音
iostate=FIRST;
}
}
usleep(10);
}
}
上述代码中,用iostate判断音频设备接口状态,保证每次发送过程中,音频设备接口只开启和关闭一次。当麦克风插拔后,重新开启音频设备接口,进行录音发送。
主线程中开启工作线程,头文件中添加:
#include "workerthread.h"
WorkerThread *worker;
主线程.cpp中添加:
worker = new WorkerThread();
worker->start();
这么改虽说像在把感冒当癌症治,但最终完成了需求,还是比较有用的。