参见前一篇https://my.oschina.net/jinzei/blog/1305843
上代码. 警告:以下代码未经过测试,如果放到您的软件出现问题风险自担!
头文件
#ifndef PORTAUDIORECORDER_H
#define PORTAUDIORECORDER_H
//通过PortAudio录音的类.
//这个类特别之处是维护设备热插拔和热更替,提供缺数据(比如蓝牙话筒关话筒)解决方案
//当开始后,没有录音设备,将提供最多5分钟的静音录制,如果及时接入设备,能够正常录音;
//如果不提供设备5分钟后则整体放弃.
//如果设备中途断开,则持续提供静音录制.力保时间同步性.
#include "portaudio/portaudio.h"
#include <QTimer>
#include <QElapsedTimer>
class ISinkForPA
{
public:
enum ErrorType{
ETStreamOpen, //流打开,代表正常工作.
ETError, //发生中断,可能是设备断连,可以重插或等待
ETNoDevice,
};
//常规数据处理
virtual bool tPaHandle(const void *input,unsigned long frameCount)=0;
//宣布放弃处理, 设计为一段时间都没有设备或不正常.
virtual void tPaNotice(ErrorType et)=0; //发生故障,time是遗失的时间.如果要继续需要补相应时间的空白.
//需要补足0数据
virtual void tPaNeedPad(double time)=0; //需要补上这么多时间的空数据,否则时间长度不对.(蓝牙话筒关话筒的时候出现!)
};
//这个类没有做成单例,但是按单例来实现.
class PortAudioRecord
{
public:
PortAudioRecord(ISinkForPA *cb);
~PortAudioRecord();
void set(int sampleRate, int framePerBuffer);
int defaultInputDevice();
//除非初始化失败,否则都是成功(哪怕没设备)
bool start();
void stop();
public:
static int PACallback(
const void *input, void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData );
int dataCallBack(
const void *input,
unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags);
protected:
bool openStream();
void closeStream();
void checkDataFeed();
private:
ISinkForPA *cb_;
PaStream *paStream_;
QElapsedTimer etimer_;
QTimer chktimer_;
qint64 feedTime_; //已经喂饱数据的时间,以etimer_参照.
double lastDacTime_; //最后一次提供数据的portaudio时间.注意起始时间是不确定的.(小于0代表未知.)
int deviceIdx_;
int sampleRate_;
int framePerBuffer_;
volatile bool haltFlag_; //pa录制异常的标记.
};
#endif // PORTAUDIORECORDER_H
cpp文件
#include "PortAudioRecord.h"
#include <QDebug>
//说明:input是从设备读到的数据,字节长度是frameCount*sizeof(SAMPLE)
//如果要echo,需要在初始化的时候指定好设备,把input拷到output即可.
//#define NODEVICEGIVEUP (5*60*1000) //无设备放弃时间.
//定义pa的sample类型为int16,这个可以配合webrtc模块
#define PA_SAMPLE_TYPE paInt16
//对应的sample单位是short,占2字节.
typedef short SAMPLE;
class PaInitMana
{
public:
PaInitMana():initOK(false){}
bool set() { if(!initOK) initOK = (paNoError == Pa_Initialize()); return initOK; }
bool reset(){ if(initOK) unset(); return set(); }
void unset(){ if(initOK) Pa_Terminate(); initOK = false; }
operator bool(){ return initOK; }
private:
bool initOK;
} gPaInit;
PortAudioRecord::PortAudioRecord(ISinkForPA *cb)
: cb_(NULL)
, paStream_(NULL)
, feedTime_(0)
, lastDacTime_(-1.0) //小于0代表未知.
, deviceIdx_(paNoDevice)
, sampleRate_ (32000)
, framePerBuffer_(6400)
, haltFlag_(false)
{
Q_ASSERT(cb);
cb_ = cb;
chktimer_.setInterval(500);
chktimer_.setSingleShot(false);
QObject::connect(&chktimer_,&QTimer::timeout,[=](){
//检查数据缺失, 或者没设备导致的数据缺失.
checkDataFeed();
});
chktimer_.start();
}
PortAudioRecord::~PortAudioRecord()
{
this->stop();
}
void PortAudioRecord::set(int sampleRate, int framePerBuffer)
{
sampleRate_ = sampleRate;
framePerBuffer_ = framePerBuffer;
}
bool PortAudioRecord::start()
{
qInfo()<<"Pa_start";
etimer_.start();
if(!gPaInit.set())return false;
lastDacTime_ = -1.0;
Q_ASSERT(deviceIdx_==paNoDevice);
deviceIdx_ = defaultInputDevice();
if(deviceIdx_ != paNoDevice)
{
PaError err;
if(openStream()==false){
paStream_ = NULL;
deviceIdx_ = paNoDevice;
goto start_end;
}
err = Pa_StartStream( paStream_ );
qInfo()<<"Pa_StartStream"<<Pa_GetErrorText(err);
if(err != paNoError){
Pa_CloseStream(paStream_);
paStream_ = NULL;
deviceIdx_ = paNoDevice;
goto start_end;
}
haltFlag_ = false;
qInfo()<<"Pa_startStream1 succ!";
cb_->tPaNotice(ISinkForPA::ETStreamOpen);
return true;
}
start_end:
if(deviceIdx_ == paNoDevice)
{
cb_->tPaNotice(ISinkForPA::ETNoDevice);
gPaInit.unset();
}
return true;
}
void PortAudioRecord::stop()
{
qInfo()<<"Pa_stop";
chktimer_.stop();
if(paStream_){
closeStream();
}
gPaInit.unset();
}
int PortAudioRecord::dataCallBack(
const void *input,
unsigned long frameCount,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags)
{
if(statusFlags==0)
{
lastDacTime_ = timeInfo->currentTime;
bool rt = cb_->tPaHandle(input,frameCount);
feedTime_ = etimer_.elapsed();
return rt?paContinue:paComplete;
}
else
{
qInfo()<<"PACallback statusFlags:"<<statusFlags<<"Abort!";
haltFlag_ = true;
return paAbort;
}
}
bool PortAudioRecord::openStream()
{
Q_ASSERT(gPaInit);
if(!gPaInit) return false;
PaError err;
PaStreamParameters inputDev;
inputDev.device = deviceIdx_; //Pa_GetDefaultInputDevice();
inputDev.channelCount = 1;
inputDev.sampleFormat = PA_SAMPLE_TYPE;
inputDev.suggestedLatency = 1;
inputDev.hostApiSpecificStreamInfo = NULL;
PaStreamParameters outputDev;
outputDev.device = Pa_GetDefaultOutputDevice(); //paNoDevice; //Pa_GetDefaultOutputDevice();
outputDev.channelCount = 1;
outputDev.sampleFormat = PA_SAMPLE_TYPE;
outputDev.suggestedLatency = 1;
outputDev.hostApiSpecificStreamInfo = NULL;
err = Pa_OpenStream(
&paStream_,
&inputDev,
&outputDev,
sampleRate_,
framePerBuffer_, /* frames per buffer */
paDitherOff, /* paDitherOff, // flags */
PortAudioRecord::PACallback,
this);
qInfo()<<"openStream"<<Pa_GetErrorText(err);
return (paNoError==err);
}
void PortAudioRecord::closeStream()
{
Q_ASSERT(gPaInit);
if(!gPaInit) return ;
if(paStream_){
PaError err;
err = Pa_CloseStream( paStream_ );
qDebug()<<"Pa_CloseStream:"<<Pa_GetErrorText(err);
paStream_ = NULL;
}
deviceIdx_ = paNoDevice;
}
int PortAudioRecord::PACallback(
const void *input,
void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData)
{
Q_UNUSED(output);
PortAudioRecord *media=(PortAudioRecord*)userData;
return media->dataCallBack(input,frameCount,timeInfo,statusFlags);
}
void PortAudioRecord::checkDataFeed()
{
if(haltFlag_)
{
if(paStream_){
PaError err;
err = Pa_CloseStream( paStream_ );
qDebug()<<"Pa_CloseStream:"<<Pa_GetErrorText(err);
paStream_ = NULL;
}
deviceIdx_ = paNoDevice;
lastDacTime_ = -1.0;
haltFlag_ = false;
Q_ASSERT(gPaInit);
gPaInit.unset();
cb_->tPaNotice(ISinkForPA::ETError);
}
if(paStream_ == NULL)
{ //枚举设备,处理热插拔.
if(gPaInit.set())
{
int didx = defaultInputDevice();
if(didx != deviceIdx_)
{
qInfo()<<"Pa_device changed!"<<deviceIdx_<<"->"<<didx;
closeStream();
deviceIdx_ = didx;
if(openStream()==false){
paStream_ = NULL;
deviceIdx_ = paNoDevice;
}
else
{
PaError err;
err = Pa_StartStream( paStream_ );
qInfo()<<"StartStream"<<Pa_GetErrorText(err);
if(err != paNoError){
Pa_CloseStream(paStream_);
paStream_ = NULL;
deviceIdx_ = paNoDevice;
}
else
{
cb_->tPaNotice(ISinkForPA::ETStreamOpen);
qInfo()<<"Pa_startStream2 succ!";
}
}
}
if(paStream_==NULL)
{//如果不成功,继续释放Pa
gPaInit.unset();
}
}
}
qint64 tnow = etimer_.elapsed();
//检查数据喂养情况,如果有较大出入,补静音.
qint64 feedGap = tnow-feedTime_;
if(feedGap>0 && feedGap > 1000)
{
if(deviceIdx_==paNoDevice)
{ //如果没有录音设备,直接补静音
// if(lastDacTime_ >0 && tnow > NODEVICEGIVEUP)
// { //太久了,放弃本次录音. 只针对从来没录过音的情况.
// cb_->tPaNotice(ISinkForPA::ETHalt);
// }
// else
{
cb_->tPaNeedPad((double)(feedGap)/1000);
feedTime_ = tnow;
}
}
else
{
if(lastDacTime_<=0)
{ //如果没有来过有效数据,补静音
cb_->tPaNeedPad((double)(feedGap)/1000);
feedTime_ = tnow;
}
else
{ //来过有效数据,补到当前时间之前.
Q_ASSERT(paStream_);
double unit = (double)framePerBuffer_/sampleRate_;
double dnow = Pa_GetStreamTime(paStream_);
qDebug()<<"dnow"<<dnow<<"lastDacTime_"<<lastDacTime_;
double gap = dnow-lastDacTime_-unit;
Q_ASSERT(gap>0);
Q_ASSERT(gap*1000<feedGap);
if(gap>0){
cb_->tPaNeedPad(gap);
lastDacTime_ += gap; //dnow-unit;
feedTime_ += qint64(gap*1000);
}
}
}
}
}
int PortAudioRecord::defaultInputDevice()
{
//注意,经过试验,如果不调用Pa_Terminate,Pa_GetDefaultInputDevice的结果不会变化.
//如果没有调用Pa_Terminate,Pa_Initialize也不能刷新DefaultInputDevice.
if(!gPaInit) return paNoDevice;
return Pa_GetDefaultInputDevice();
}
讨论加q群20487942