参见前一篇
上代码. 警告:以下代码未经过测试,如果放到您的软件出现问题风险自担!
头文件
#ifndef PORTAUDIORECORDER_H#define PORTAUDIORECORDER_H//通过PortAudio录音的类.//这个类特别之处是维护设备热插拔和热更替,提供缺数据(比如蓝牙话筒关话筒)解决方案//当开始后,没有录音设备,将提供最多5分钟的静音录制,如果及时接入设备,能够正常录音;//如果不提供设备5分钟后则整体放弃.//如果设备中途断开,则持续提供静音录制.力保时间同步性.#include "portaudio/portaudio.h"#include#include 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//说明: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"< 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:"< <<"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"< dataCallBack(input,frameCount,timeInfo,statusFlags);}void PortAudioRecord::checkDataFeed(){ if(haltFlag_) { if(paStream_){ PaError err; err = Pa_CloseStream( paStream_ ); qDebug()<<"Pa_CloseStream:"< tPaNotice(ISinkForPA::ETError); } if(paStream_ == NULL) { //枚举设备,处理热插拔. if(gPaInit.set()) { int didx = defaultInputDevice(); if(didx != deviceIdx_) { qInfo()<<"Pa_device changed!"< <<"->"< 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"< <<"lastDacTime_"< 0); Q_ASSERT(gap*1000 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