diff --git a/devsetup.cpp b/devsetup.cpp index 8d1e61a39..2d1d926a8 100644 --- a/devsetup.cpp +++ b/devsetup.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #define MAXDEVICES 100 @@ -35,6 +37,25 @@ void DevSetup::initDlg() QString catPortDriver = settings.value("CATdriver","None").toString(); settings.endGroup(); +/* + QList InDevices; + QList OutDevices; + QAudioDeviceInfo deviceInfo; + + InDevices = deviceInfo.availableDevices(QAudio::AudioInput); + OutDevices = deviceInfo.availableDevices(QAudio::AudioOutput); + + foreach (const QAudioDeviceInfo &deviceInfo, InDevices) { + ui.comboBoxSndIn->addItem(deviceInfo.deviceName(), + qVariantFromValue(deviceInfo)); + } + + foreach (const QAudioDeviceInfo &deviceInfo, OutDevices) { + ui.comboBoxSndOut->addItem(deviceInfo.deviceName(), + qVariantFromValue(deviceInfo)); + } +*/ + int k,id; int numDevices=Pa_GetDeviceCount(); diff --git a/mainwindow.cpp b/mainwindow.cpp index fddf5798d..54de092bf 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -86,11 +86,11 @@ MainWindow::MainWindow(QSharedMemory *shdmem, QString *thekey, \ SLOT(doubleClickOnCall2(bool,bool))); setWindowTitle(Program_Title_Version); - connect(&soundInThread, SIGNAL(readyForFFT(int)), + connect(&m_soundInput, SIGNAL(readyForFFT(int)), this, SLOT(dataSink(int))); - connect(&soundInThread, SIGNAL(error(QString)), this, + connect(&m_soundInput, SIGNAL(error(QString)), this, SLOT(showSoundInError(QString))); - connect(&soundInThread, SIGNAL(status(QString)), this, + connect(&m_soundInput, SIGNAL(status(QString)), this, SLOT(showStatusMessage(QString))); createStatusBar(); @@ -302,13 +302,12 @@ MainWindow::MainWindow(QSharedMemory *shdmem, QString *thekey, \ watcher2 = new QFutureWatcher; connect(watcher2, SIGNAL(finished()),this,SLOT(diskWriteFinished())); - soundInThread.setInputDevice(m_paInDevice); - soundInThread.start(QThread::HighestPriority); + m_soundInput.start(m_paInDevice); soundOutThread.setOutputDevice(m_paOutDevice); soundOutThread.setTxFreq(m_txFreq); soundOutThread.setTune(false); m_monitoring=!m_monitorStartOFF; // Start with Monitoring ON/OFF - soundInThread.setMonitoring(m_monitoring); + m_soundInput.setMonitoring(m_monitoring); m_diskData=false; // Create "m_worked", a dictionary of all calls in wsjtx.log @@ -373,10 +372,6 @@ MainWindow::MainWindow(QSharedMemory *shdmem, QString *thekey, \ MainWindow::~MainWindow() { writeSettings(); - if (soundInThread.isRunning()) { - soundInThread.quit(); - soundInThread.wait(3000); - } if (soundOutThread.isRunning()) { soundOutThread.quitExecution=true; soundOutThread.wait(3000); @@ -712,7 +707,7 @@ void MainWindow::dataSink(int k) watcher2->setFuture(*future2); } } - soundInThread.m_dataSinkBusy=false; + // m_soundInput.m_dataSinkBusy=false; } void MainWindow::showSoundInError(const QString& errorMsg) @@ -832,10 +827,8 @@ void MainWindow::on_actionDeviceSetup_triggered() //Setup Dialog m_After73=dlg.m_After73; if(dlg.m_restartSoundIn) { - soundInThread.quit(); - soundInThread.wait(1000); - soundInThread.setInputDevice(m_paInDevice); - soundInThread.start(QThread::HighestPriority); + m_soundInput.stop(); + m_soundInput.start(m_paInDevice); } if(dlg.m_restartSoundOut) { @@ -870,7 +863,7 @@ void MainWindow::on_actionDeviceSetup_triggered() //Setup Dialog void MainWindow::on_monitorButton_clicked() //Monitor { m_monitoring=true; - soundInThread.setMonitoring(true); + m_soundInput.setMonitoring(true); m_diskData=false; } @@ -1132,7 +1125,7 @@ void MainWindow::OnExit() void MainWindow::on_stopButton_clicked() //stopButton { m_monitoring=false; - soundInThread.setMonitoring(m_monitoring); + m_soundInput.setMonitoring(m_monitoring); m_loopall=false; } @@ -1173,7 +1166,7 @@ void MainWindow::on_actionWide_Waterfall_triggered() //Display Waterfalls void MainWindow::on_actionOpen_triggered() //Open File { m_monitoring=false; - soundInThread.setMonitoring(m_monitoring); + m_soundInput.setMonitoring(m_monitoring); QString fname; fname=QFileDialog::getOpenFileName(this, "Open File", m_path, "WSJT Files (*.wav)"); @@ -1787,7 +1780,7 @@ void MainWindow::guiUpdate() signalMeter->setValue(0); m_monitoring=false; - soundInThread.setMonitoring(false); + m_soundInput.setMonitoring(false); btxok=true; m_transmitting=true; ui->pbTxMode->setEnabled(false); @@ -1907,7 +1900,7 @@ void MainWindow::startTx2() soundOutThread.start(QThread::HighestPriority); signalMeter->setValue(0); m_monitoring=false; - soundInThread.setMonitoring(false); + m_soundInput.setMonitoring(false); btxok=true; m_transmitting=true; ui->pbTxMode->setEnabled(false); @@ -1927,7 +1920,7 @@ void MainWindow::stopTx() lab1->setText(""); ptt0Timer->start(200); //Sequencer delay m_monitoring=true; - soundInThread.setMonitoring(true); + m_soundInput.setMonitoring(true); } void MainWindow::stopTx2() @@ -2534,7 +2527,7 @@ void MainWindow::on_actionJT9_1_triggered() m_TRperiod=60; m_nsps=6912; m_hsymStop=173; - soundInThread.setPeriod(m_TRperiod,m_nsps); + m_soundInput.setPeriod(m_TRperiod,m_nsps); soundOutThread.setPeriod(m_TRperiod,m_nsps); lab3->setStyleSheet("QLabel{background-color: #ff6ec7}"); lab3->setText(m_mode); @@ -2553,7 +2546,7 @@ void MainWindow::on_actionJT65_triggered() m_TRperiod=60; m_nsps=6912; //For symspec only m_hsymStop=173; - soundInThread.setPeriod(m_TRperiod,m_nsps); + m_soundInput.setPeriod(m_TRperiod,m_nsps); soundOutThread.setPeriod(m_TRperiod,m_nsps); lab3->setStyleSheet("QLabel{background-color: #ffff00}"); lab3->setText(m_mode); @@ -2572,7 +2565,7 @@ void MainWindow::on_actionJT9_JT65_triggered() m_TRperiod=60; m_nsps=6912; m_hsymStop=173; - soundInThread.setPeriod(m_TRperiod,m_nsps); + m_soundInput.setPeriod(m_TRperiod,m_nsps); soundOutThread.setPeriod(m_TRperiod,m_nsps); lab3->setStyleSheet("QLabel{background-color: #ffa500}"); lab3->setText(m_mode); diff --git a/mainwindow.h b/mainwindow.h index 10601d5a1..1efb7de7c 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -337,7 +337,7 @@ private: QDateTime m_dateTimeQSO; - SoundInThread soundInThread; //Instantiate the audio threads + SoundInput m_soundInput; //Instantiate the audio objects SoundOutThread soundOutThread; QSharedMemory *mem_jt9; // Multiple instances: diff --git a/soundin.cpp b/soundin.cpp index 332e5d371..7458f06b6 100644 --- a/soundin.cpp +++ b/soundin.cpp @@ -33,30 +33,19 @@ extern struct { } jt9com_; } -typedef struct -{ - int kin; //Parameters sent to/from the portaudio callback function - int ncall; - bool bzero; - bool monitoring; -} paUserData; - //--------------------------------------------------------------- a2dCallback -extern "C" int a2dCallback( const void *inputBuffer, void *outputBuffer, - unsigned long framesToProcess, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData ) +int a2dCallback( const void *inputBuffer, void * /* outputBuffer */, + unsigned long framesToProcess, + const PaStreamCallbackTimeInfo * /* timeInfo */, + PaStreamCallbackFlags statusFlags, + void *userData ) // This routine called by the PortAudio engine when samples are available. // It may be called at interrupt level, so don't do anything // that could mess up the system like calling malloc() or free(). { - paUserData *udata=(paUserData*)userData; - (void) outputBuffer; //Prevent unused variable warnings. - (void) timeInfo; - (void) userData; + SoundInput::CallbackData * udata = reinterpret_cast(userData); int nbytes,k; udata->ncall++; @@ -79,22 +68,31 @@ extern "C" int a2dCallback( const void *inputBuffer, void *outputBuffer, return paContinue; } -void SoundInThread::run() //SoundInThread::run() +SoundInput::SoundInput() + : m_inStream(0), + m_dataSinkBusy(false), + m_TRperiod(60), + m_nsps(6912), + m_monitoring(false), + m_intervalTimer(this) { - quitExecution = false; + connect(&m_intervalTimer, &QTimer::timeout, this, &SoundInput::intervalNotify); +} + +void SoundInput::start(qint32 device) +{ + stop(); //---------------------------------------------------- Soundcard Setup PaError paerr; PaStreamParameters inParam; - PaStream *inStream; - paUserData udata; - udata.kin=0; //Buffer pointer - udata.ncall=0; //Number of callbacks - udata.bzero=false; //Flag to request reset of kin - udata.monitoring=m_monitoring; + m_callbackData.kin=0; //Buffer pointer + m_callbackData.ncall=0; //Number of callbacks + m_callbackData.bzero=false; //Flag to request reset of kin + m_callbackData.monitoring=m_monitoring; - inParam.device=m_nDevIn; //### Input Device Number ### + inParam.device=device; //### Input Device Number ### inParam.channelCount=1; //Number of analog channels inParam.sampleFormat=paInt16; //Get i*2 from Portaudio inParam.suggestedLatency=0.05; @@ -106,103 +104,83 @@ void SoundInThread::run() //SoundInThread::run() // return; } qDebug() << ""; - paerr=Pa_OpenStream(&inStream, //Input stream - &inParam, //Input parameters - NULL, //No output parameters - 12000.0, //Sample rate - FRAMES_PER_BUFFER, //Frames per buffer + paerr=Pa_OpenStream(&m_inStream, //Input stream + &inParam, //Input parameters + NULL, //No output parameters + 12000.0, //Sample rate + FRAMES_PER_BUFFER, //Frames per buffer // paClipOff+paDitherOff, //No clipping or dithering - paClipOff, //No clipping - a2dCallback, //Input callback routine - &udata); //userdata - - paerr=Pa_StartStream(inStream); + paClipOff, //No clipping + a2dCallback, //Input callback routine + &m_callbackData); //userdata + paerr=Pa_StartStream(m_inStream); if(paerr<0) { emit error("Failed to start audio input stream."); return; } + m_ntr0 = 99; // initial value higher than any expected + m_nBusy = 0; + m_intervalTimer.start(100); + m_ms0 = QDateTime::currentMSecsSinceEpoch(); + m_nsps0 = 0; +} - bool qe = quitExecution; - static int ntr0=99; - int k=0; - int nsec; - int ntr; - int nBusy=0; - int nstep0=0; - int nsps0=0; - qint64 ms0 = QDateTime::currentMSecsSinceEpoch(); - -//---------------------------------------------- Soundcard input loop - while (!qe) { - qe = quitExecution; - if (qe) break; - udata.monitoring=m_monitoring; - qint64 ms = QDateTime::currentMSecsSinceEpoch(); - m_SamFacIn=1.0; - if(udata.ncall>100) { - m_SamFacIn=udata.ncall*FRAMES_PER_BUFFER*1000.0/(12000.0*(ms-ms0-50)); - } - ms=ms % 86400000; - nsec = ms/1000; // Time according to this computer - ntr = nsec % m_TRperiod; - -// Reset buffer pointer and symbol number at start of minute - if(ntr < ntr0 or !m_monitoring or m_nsps!=nsps0) { - nstep0=0; - nsps0=m_nsps; - udata.bzero=true; - } - k=udata.kin; - if(m_monitoring) { - int kstep=m_nsps/2; -// m_step=k/kstep; - m_step=(k-1)/kstep; - if(m_step != nstep0) { - if(m_dataSinkBusy) { - nBusy++; - } else { -// m_dataSinkBusy=true; -// emit readyForFFT(k); //Signal to compute new FFTs - emit readyForFFT(k-1); //Signal to compute new FFTs - } - nstep0=m_step; - } - } - msleep(100); - ntr0=ntr; +void SoundInput::intervalNotify() +{ + m_callbackData.monitoring=m_monitoring; + qint64 ms = QDateTime::currentMSecsSinceEpoch(); + m_SamFacIn=1.0; + if(m_callbackData.ncall>100) { + m_SamFacIn=m_callbackData.ncall*FRAMES_PER_BUFFER*1000.0/(12000.0*(ms-m_ms0-50)); } - Pa_StopStream(inStream); - Pa_CloseStream(inStream); + ms=ms % 86400000; + int nsec = ms/1000; // Time according to this computer + int ntr = nsec % m_TRperiod; + + // Reset buffer pointer and symbol number at start of minute + if(ntr < m_ntr0 or !m_monitoring or m_nsps!=m_nsps0) { + m_nstep0=0; + m_nsps0=m_nsps; + m_callbackData.bzero=true; + } + int k=m_callbackData.kin; + if(m_monitoring) { + int kstep=m_nsps/2; + // m_step=k/kstep; + m_step=(k-1)/kstep; + if(m_step != m_nstep0) { + if(m_dataSinkBusy) { + m_nBusy++; + } else { + // m_dataSinkBusy=true; + // emit readyForFFT(k); //Signal to compute new FFTs + emit readyForFFT(k-1); //Signal to compute new FFTs + } + m_nstep0=m_step; + } + } + m_ntr0=ntr; } -void SoundInThread::setInputDevice(int n) //setInputDevice() +SoundInput::~SoundInput() { - if (isRunning()) return; - this->m_nDevIn=n; + if (m_inStream) + { + Pa_CloseStream(m_inStream), m_inStream = 0; + } } -void SoundInThread::quit() //quit() +void SoundInput::stop() { - quitExecution = true; + m_intervalTimer.stop(); + if (m_inStream) + { + Pa_StopStream(m_inStream); + Pa_CloseStream(m_inStream), m_inStream = 0; + } } -void SoundInThread::setMonitoring(bool b) //setMonitoring() +void SoundInput::setMonitoring(bool b) { m_monitoring = b; } - -void SoundInThread::setPeriod(int ntrperiod, int nsps) -{ - m_TRperiod=ntrperiod; - m_nsps=nsps; -} - -int SoundInThread::mstep() -{ - return m_step; -} - -double SoundInThread::samFacIn() -{ - return m_SamFacIn; -} diff --git a/soundin.h b/soundin.h index 955f08d79..59c6cb0fe 100644 --- a/soundin.h +++ b/soundin.h @@ -1,34 +1,32 @@ #ifndef SOUNDIN_H #define SOUNDIN_H +#include + #include +#include #include +extern "C" int a2dCallback( const void *, void *, unsigned long, PaStreamCallbackTimeInfo const *, PaStreamCallbackFlags, void *); -// Thread gets audio data from soundcard and signals when a buffer of +// Gets audio data from soundcard and signals when a buffer of // specified size is available. -class SoundInThread : public QThread +class SoundInput : public QObject { Q_OBJECT - bool quitExecution; // if true, thread exits gracefully - -protected: - virtual void run(); public: - bool m_dataSinkBusy; + SoundInput(); + ~SoundInput(); - SoundInThread(): - quitExecution(false), - m_dataSinkBusy(false) - { - } - - void setInputDevice(qint32 n); void setMonitoring(bool b); - void setPeriod(int ntrperiod, int nsps); - int mstep(); - double samFacIn(); + void setPeriod(int ntrperiod, int nsps) /* this can be called while processing samples */ + { + m_TRperiod=ntrperiod; + m_nsps=nsps; + } + int mstep() const {return m_step;} + double samFacIn() const {return m_SamFacIn;} signals: void readyForFFT(int k); @@ -36,15 +34,35 @@ signals: void status(const QString& message); public slots: - void quit(); + void start(qint32 device); + void intervalNotify(); + void stop(); private: + PaStream * m_inStream; + bool m_dataSinkBusy; double m_SamFacIn; //(Input sample rate)/12000.0 qint32 m_step; - qint32 m_nDevIn; qint32 m_TRperiod; qint32 m_TRperiod0; qint32 m_nsps; bool m_monitoring; + qint64 m_ms0; + int m_ntr0; + int m_nBusy; + int m_nstep0; + int m_nsps0; + + QTimer m_intervalTimer; + + struct CallbackData + { + int kin; //Parameters sent to/from the portaudio callback function + int ncall; + bool bzero; + bool monitoring; + } m_callbackData; + + friend int a2dCallback(void const *, void *, unsigned long, PaStreamCallbackTimeInfo const *, PaStreamCallbackFlags, void *); }; #endif // SOUNDIN_H diff --git a/soundin_1.cpp b/soundin_1.cpp new file mode 100644 index 000000000..bfbaddce0 --- /dev/null +++ b/soundin_1.cpp @@ -0,0 +1,210 @@ +#include "soundin.h" +#include + +#define FRAMES_PER_BUFFER 1024 +//#define NSMAX 1365 +#define NSMAX 6827 +#define NTMAX 120 + +extern "C" { +#include +extern struct { + float ss[184*NSMAX]; //This is "common/jt9com/..." in fortran + float savg[NSMAX]; +// float c0[2*NTMAX*1500]; + short int d2[NTMAX*12000]; + int nutc; //UTC as integer, HHMM + int ndiskdat; //1 ==> data read from *.wav file + int ntrperiod; //TR period (seconds) + int mousefqso; //User-selected QSO freq (kHz) + int newdat; //1 ==> new data, must do long FFT + int npts8; //npts in c0() array + int nfa; //Low decode limit (Hz) + int nfb; //High decode limit (Hz) + int ntol; //+/- decoding range around fQSO (Hz) + int kin; + int nzhsym; + int nsave; + int nagain; + int ndepth; + int ntxmode; + int nmode; + char datetime[20]; +} jt9com_; +} + +QString reportAudioError(QAudio::Error audioError) +{ + switch (audioError) { + case QAudio::NoError: Q_ASSERT(false); + case QAudio::OpenError: return QObject::tr( + "An error opening the audio device has occurred."); + case QAudio::IOError: return QObject::tr( + "An error occurred during read/write of audio device."); + case QAudio::UnderrunError: return QObject::tr( + "Audio data not being fed to the audio device fast enough."); + case QAudio::FatalError: return QObject::tr( + "Non-recoverable error, audio device not usable at this time."); + } + Q_ASSERT(false); + return ""; +} + +typedef struct +{ + int kin; //Parameters sent to/from the portaudio callback function + int ncall; + bool bzero; + bool monitoring; +} paUserData; + + +void SoundInThread::run() //SoundInThread::run() +{ + quitExecution = false; + +//---------------------------------------------------- Soundcard Setup + + quitExecutionMutex.lock(); + quitExecution = false; + quitExecutionMutex.unlock(); + + //### Temporary: hardwired device selection + QAudioDeviceInfo DeviceInfo; + QList m_InDevices; + QAudioDeviceInfo m_InDeviceInfo; + m_InDevices = DeviceInfo.availableDevices(QAudio::AudioInput); + inputDevice = m_InDevices.at(0); + //### + + const char* pcmCodec = "audio/pcm"; + QAudioFormat audioFormat = inputDevice.preferredFormat(); + audioFormat.setChannelCount(1); + audioFormat.setCodec(pcmCodec); + audioFormat.setSampleRate(12000); + audioFormat.setSampleType(QAudioFormat::SignedInt); + audioFormat.setSampleSize(16); + + if (!audioFormat.isValid()) { + emit error(tr("Requested audio format is not available.")); + return; + } + + QAudioInput audioInput(inputDevice, audioFormat); + if (audioInput.error() != QAudio::NoError) { + emit error(reportAudioError(audioInput.error())); + return; + } + + QIODevice* stream = audioInput.start(); + + bool qe = quitExecution; + static int ntr0=99; + int k=0; + int nsec; + int ntr; + int nBusy=0; + int nstep0=0; + int nsps0=0; + qint16 buf0[4096]; + +//---------------------------------------------- Soundcard input loop + while (!qe) { + quitExecutionMutex.lock(); + qe = quitExecution; + quitExecutionMutex.unlock(); + if (qe) break; + + // Error checking... + if (audioInput.error() != QAudio::NoError) { + emit error(reportAudioError(audioInput.error())); + return; + } + +// udata.monitoring=m_monitoring; + qint64 ms = QDateTime::currentMSecsSinceEpoch(); + ms=ms % 86400000; + nsec = ms/1000; // Time according to this computer + ntr = nsec % m_TRperiod; + +// Reset buffer pointer and symbol number at start of minute + if(ntr < ntr0 or !m_monitoring or m_nsps!=nsps0) { + nstep0=0; + nsps0=m_nsps; +// udata.bzero=true; + k=0; + } +// k=udata.kin; + + // How many new samples have been acquired? + const qint32 bytesReady = audioInput.bytesReady(); + Q_ASSERT(bytesReady >= 0); + Q_ASSERT(bytesReady % 2 == 0); + if (bytesReady == 0) { + msleep(50); + continue; + } + + // Get the new samples + qint32 bytesRead; + bytesRead = stream->read((char*)buf0, bytesReady); + Q_ASSERT(bytesRead <= bytesReady); + if (bytesRead < 0) { + emit error(tr("audio stream QIODevice::read returned -1.")); + return; + } + Q_ASSERT(bytesRead % 2 == 0); + +// memcpy(jt9com_.d2[k],buf0,bytesRead); +// k+=bytesRead/2; + + for(int i=0; im_nDevIn=n; +} + +void SoundInThread::quit() //quit() +{ + quitExecution = true; +} + +void SoundInThread::setMonitoring(bool b) //setMonitoring() +{ + m_monitoring = b; +} + +void SoundInThread::setPeriod(int ntrperiod, int nsps) +{ + m_TRperiod=ntrperiod; + m_nsps=nsps; +} + +int SoundInThread::mstep() +{ + return m_step; +} + diff --git a/soundin_1.h b/soundin_1.h new file mode 100644 index 000000000..f81322ea8 --- /dev/null +++ b/soundin_1.h @@ -0,0 +1,53 @@ +#ifndef SOUNDIN_H +#define SOUNDIN_H + +#include +#include + +#include +#include +#include + +// Thread gets audio data from soundcard and signals when a buffer of +// specified size is available. +class SoundInThread : public QThread +{ + Q_OBJECT + bool quitExecution; // if true, thread exits gracefully + QMutex quitExecutionMutex; // protects the quitExecution variable + QAudioDeviceInfo inputDevice; // audioinput device name + +protected: + virtual void run(); + +public: + bool m_dataSinkBusy; + + SoundInThread(): + quitExecution(false), + m_dataSinkBusy(false) + { + } + + void setInputDevice(qint32 n); + void setMonitoring(bool b); + void setPeriod(int ntrperiod, int nsps); + int mstep(); + +signals: + void readyForFFT(int k); + void error(const QString& message); + void status(const QString& message); + +public slots: + void quit(); + +private: + qint32 m_step; + qint32 m_nDevIn; + qint32 m_TRperiod; + qint32 m_TRperiod0; + qint32 m_nsps; + bool m_monitoring; +}; +#endif // SOUNDIN_H diff --git a/wsjtx.pro b/wsjtx.pro index de91e1fec..c39fca093 100644 --- a/wsjtx.pro +++ b/wsjtx.pro @@ -10,6 +10,7 @@ CONFIG += thread #CONFIG += console TARGET = wsjtx +#DESTDIR = ../qt4_install DESTDIR = ../wsjtx_install VERSION = 1.1 TEMPLATE = app