diff --git a/audiohandler.h b/audiohandler.h index 47e6412..a793973 100644 --- a/audiohandler.h +++ b/audiohandler.h @@ -43,28 +43,28 @@ class audioHandler : public QObject Q_OBJECT public: - explicit audioHandler(QObject* parent = nullptr); - ~audioHandler(); + audioHandler(QObject* parent = nullptr); + virtual ~audioHandler(); - int getLatency(); + virtual int getLatency(); - void start(); - void stop(); + virtual void start(); + virtual void stop(); - quint16 getAmplitude(); + virtual quint16 getAmplitude(); public slots: - bool init(audioSetup setup); - void changeLatency(const quint16 newSize); - void setVolume(unsigned char volume); - void incomingAudio(const audioPacket data); - void convertedInput(audioPacket audio); - void convertedOutput(audioPacket audio); + virtual bool init(audioSetup setup); + virtual void changeLatency(const quint16 newSize); + virtual void setVolume(unsigned char volume); + virtual void incomingAudio(const audioPacket data); + virtual void convertedInput(audioPacket audio); + virtual void convertedOutput(audioPacket audio); private slots: - void stateChanged(QAudio::State state); - void clearUnderrun(); - void getNextAudioChunk(); + virtual void stateChanged(QAudio::State state); + virtual void clearUnderrun(); + virtual void getNextAudioChunk(); signals: void audioMessage(QString message); @@ -82,7 +82,7 @@ private: bool isUnderrun = false; - bool isOverrun = true; + bool isOverrun = false; bool isInitialized=false; bool isReady = false; bool audioBuffered = false; @@ -113,7 +113,7 @@ private: volatile bool ready = false; audioPacket tempBuf; quint16 currentLatency; - float amplitude; + float amplitude=0.0; qreal volume = 1.0; audioSetup setup; diff --git a/commhandler.cpp b/commhandler.cpp index be6fb5b..3b500ef 100644 --- a/commhandler.cpp +++ b/commhandler.cpp @@ -8,12 +8,6 @@ commHandler::commHandler(QObject* parent) : QObject(parent) { //constructor - // grab baud rate and other comm port details - // if they need to be changed later, please - // destroy this and create a new one. - port = new QSerialPort(); - - // TODO: The following should become arguments and/or functions // Add signal/slot everywhere for comm port setup. // Consider how to "re-setup" and how to save the state for next time. @@ -22,13 +16,7 @@ commHandler::commHandler(QObject* parent) : QObject(parent) portName = "/dev/ttyUSB0"; this->PTTviaRTS = false; - setupComm(); // basic parameters - openPort(); - //qInfo(logSerial()) << "Serial buffer size: " << port->readBufferSize(); - //port->setReadBufferSize(1024); // manually. 256 never saw any return from the radio. why... - //qInfo(logSerial()) << "Serial buffer size: " << port->readBufferSize(); - - connect(port, SIGNAL(readyRead()), this, SLOT(receiveDataIn())); + init(); } commHandler::commHandler(QString portName, quint32 baudRate, quint8 wfFormat, QObject* parent) : QObject(parent) @@ -38,7 +26,6 @@ commHandler::commHandler(QString portName, quint32 baudRate, quint8 wfFormat, QO // if they need to be changed later, please // destroy this and create a new one. - port = new QSerialPort(); if (wfFormat == 1) { // Single waterfall packet combineWf = true; @@ -52,7 +39,19 @@ commHandler::commHandler(QString portName, quint32 baudRate, quint8 wfFormat, QO stopbits = 1; this->portName = portName; this->PTTviaRTS = false; + init(); +} + +void commHandler::init() +{ + if (port != Q_NULLPTR) { + delete port; + port = Q_NULLPTR; + isConnected = false; + } + + port = new QSerialPort(); setupComm(); // basic parameters openPort(); // qInfo(logSerial()) << "Serial buffer size: " << port->readBufferSize(); @@ -60,9 +59,10 @@ commHandler::commHandler(QString portName, quint32 baudRate, quint8 wfFormat, QO //qInfo(logSerial()) << "Serial buffer size: " << port->readBufferSize(); connect(port, SIGNAL(readyRead()), this, SLOT(receiveDataIn())); + connect(port, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(handleError(QSerialPort::SerialPortError))); + lastDataReceived = QTime::currentTime(); } - commHandler::~commHandler() { qInfo(logSerial()) << "Closing serial port: " << port->portName(); @@ -87,16 +87,16 @@ void commHandler::receiveDataFromUserToRig(const QByteArray &data) void commHandler::sendDataOut(const QByteArray &writeData) { - mutex.lock(); // Recycle port to attempt reconnection. - if (!this->isConnected) { - closePort(); - openPort(); - } - - if (!this->isConnected) { + if (lastDataReceived.msecsTo(QTime::currentTime()) > 2000) { + qDebug(logSerial()) << "Serial port error? Attempting reconnect..."; + lastDataReceived = QTime::currentTime(); + QTimer::singleShot(500, this, SLOT(init())); return; } + + mutex.lock(); + qint64 bytesWritten; if(PTTviaRTS) @@ -143,7 +143,7 @@ void commHandler::sendDataOut(const QByteArray &writeData) } bytesWritten = port->write(writeData); - + if(bytesWritten != (qint64)writeData.size()) { qDebug(logSerial()) << "bytesWritten: " << bytesWritten << " length of byte array: " << writeData.length()\ @@ -162,6 +162,7 @@ void commHandler::receiveDataIn() // because we know what constitutes a valid "frame" of data. // new code: + lastDataReceived = QTime::currentTime(); port->startTransaction(); inPortData = port->readAll(); @@ -371,3 +372,17 @@ void commHandler::printHex(const QByteArray &pdata, bool printVert, bool printHo } +void commHandler::handleError(QSerialPort::SerialPortError err) +{ + switch (err) { + case QSerialPort::NoError: + break; + default: + if (lastDataReceived.msecsTo(QTime::currentTime()) > 2000) { + qDebug(logSerial()) << "Serial port" << port->portName() << "Error, attempting disconnect/reconnect"; + lastDataReceived = QTime::currentTime(); + QTimer::singleShot(500, this, SLOT(init())); + } + break; + } +} diff --git a/commhandler.h b/commhandler.h index 5546175..acfc8be 100644 --- a/commhandler.h +++ b/commhandler.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include // This class abstracts the comm port in a useful way and connects to // the command creator and command parser. @@ -25,6 +27,8 @@ public: public slots: void setUseRTSforPTT(bool useRTS); void setRTS(bool rtsOn); + void handleError(QSerialPort::SerialPortError error); + void init(); private slots: void receiveDataIn(); // from physical port @@ -58,7 +62,7 @@ private: unsigned char buffer[256]; QString portName; - QSerialPort *port; + QSerialPort *port=Q_NULLPTR; qint32 baudrate; unsigned char stopbits; bool rolledBack; @@ -82,6 +86,7 @@ private: quint8 spectrumInformation; quint8 spectrumOutOfRange; quint8 lastSpectrum = 0; + QTime lastDataReceived; }; #endif // COMMHANDLER_H diff --git a/pahandler.cpp b/pahandler.cpp index 6bd3875..e0de0eb 100644 --- a/pahandler.cpp +++ b/pahandler.cpp @@ -217,7 +217,11 @@ bool paHandler::init(audioSetup setup) void paHandler::setVolume(unsigned char volume) { +#ifdef Q_OS_WIN + this->volume = audiopot[volume] * 5; +#else this->volume = audiopot[volume]; +#endif qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "setVolume: " << volume << "(" << this->volume << ")"; } @@ -246,6 +250,18 @@ int paHandler::writeData(const void* inputBuffer, void* outputBuffer, packet.data.append((char*)inputBuffer, nFrames*inFormat.channelCount()*sizeof(float)); emit sendToConverter(packet); + if (status == paInputUnderflow) { + isUnderrun = true; + } + else if (status == paInputOverflow) { + isOverrun = true; + } + else + { + isUnderrun = false; + isOverrun = false; + } + return paContinue; } @@ -261,44 +277,24 @@ void paHandler::convertedOutput(audioPacket packet) { qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Error writing audio!"; } const PaStreamInfo* info = Pa_GetStreamInfo(audio); - - //currentLatency = packet.time.msecsTo(QTime::currentTime()) + (info->outputLatency * 1000); - currentLatency = (info->outputLatency * 1000); - - } - /* - currentLatency = packet.time.msecsTo(QTime::currentTime()) + (outFormat.durationForBytes(audioOutput->bufferSize() - audioOutput->bytesFree()) / 1000); - - if (audioDevice != Q_NULLPTR) { - if (audioDevice->write(packet.data) < packet.data.size()) { - qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Buffer full!"; - isOverrun = true; - } - else { - isOverrun = false; - } - if (lastReceived.msecsTo(QTime::currentTime()) > 100) { - qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Time since last audio packet" << lastReceived.msecsTo(QTime::currentTime()) << "Expected around" << setup.blockSize; - } - lastReceived = QTime::currentTime(); + currentLatency = packet.time.msecsTo(QTime::currentTime()) + (info->outputLatency * 1000); } - lastSentSeq = packet.seq; - - */ amplitude = packet.amplitude; - emit haveLevels(getAmplitude(), setup.latency, currentLatency, false, false); + emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun, isOverrun); } } -void paHandler::convertedInput(audioPacket audio) +void paHandler::convertedInput(audioPacket packet) { - if (audio.data.size() > 0) { - emit haveAudioData(audio); - amplitude = audio.amplitude; - emit haveLevels(getAmplitude(), setup.latency, currentLatency, false,false); + if (packet.data.size() > 0) { + emit haveAudioData(packet); + amplitude = packet.amplitude; + const PaStreamInfo* info = Pa_GetStreamInfo(audio); + currentLatency = packet.time.msecsTo(QTime::currentTime()) + (info->inputLatency * 1000); + emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun, isOverrun); } } @@ -317,5 +313,5 @@ int paHandler::getLatency() quint16 paHandler::getAmplitude() { - return amplitude; + return static_cast(amplitude * 255.0); } diff --git a/pahandler.h b/pahandler.h index 5e311aa..a8fac05 100644 --- a/pahandler.h +++ b/pahandler.h @@ -18,6 +18,7 @@ /* Logarithmic taper for volume control */ #include "audiotaper.h" +#include "audiohandler.h" /* Audio converter class*/ #include "audioconverter.h" @@ -25,7 +26,7 @@ #include -class paHandler : public QObject +class paHandler : public audioHandler { Q_OBJECT @@ -80,7 +81,7 @@ private: quint32 lastSentSeq = 0; quint16 currentLatency; - quint16 amplitude = 0; + float amplitude=0.0; qreal volume = 1.0; audioSetup setup; @@ -88,6 +89,8 @@ private: QAudioFormat outFormat; audioConverter* converter = Q_NULLPTR; QThread* converterThread = Q_NULLPTR; + bool isUnderrun = false; + bool isOverrun = false; }; #endif // PAHANDLER_H diff --git a/rthandler.cpp b/rthandler.cpp index c6afb31..b7438f5 100644 --- a/rthandler.cpp +++ b/rthandler.cpp @@ -20,7 +20,7 @@ rtHandler::~rtHandler() audio->abortStream(); audio->closeStream(); } - catch (RtAudioError& e) { + catch (RtAudioError& e) { qInfo(logAudio()) << "Error closing stream:" << aParams.deviceId << ":" << QString::fromStdString(e.getMessage()); } delete audio; @@ -41,7 +41,7 @@ bool rtHandler::init(audioSetup setup) } this->setup = setup; - qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "PortAudio handler starting:" << setup.name; + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "RTAudio handler starting:" << setup.name; if (setup.portInt==-1) { @@ -90,9 +90,9 @@ bool rtHandler::init(audioSetup setup) try { info = audio->getDeviceInfo(aParams.deviceId); } - catch (RtAudioError& e) { - qInfo(logAudio()) << "Device error:" << aParams.deviceId << ":" << QString::fromStdString(e.getMessage()); - return isInitialized; + catch (RtAudioError e) { + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Device exception:" << aParams.deviceId << ":" << QString::fromStdString(e.getMessage()); + goto errorHandler; } if (info.probed) @@ -106,7 +106,7 @@ bool rtHandler::init(audioSetup setup) if (info.nativeFormats == 0) { qCritical(logAudio()) << " No natively supported data formats!"; - return false; + goto errorHandler; } else { qDebug(logAudio()) << " Supported formats:" << @@ -133,7 +133,7 @@ bool rtHandler::init(audioSetup setup) else if (outFormat.channelCount() < 1) { qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "No channels found, aborting setup."; - return false; + goto errorHandler; } aParams.nChannels = outFormat.channelCount(); @@ -162,7 +162,7 @@ bool rtHandler::init(audioSetup setup) } else { qCritical(logAudio()) << "Cannot find supported sample format!"; - return false; + goto errorHandler; } } @@ -208,17 +208,33 @@ bool rtHandler::init(audioSetup setup) } catch (RtAudioError& e) { qInfo(logAudio()) << "Error opening:" << QString::fromStdString(e.getMessage()); + // Try again? + goto errorHandler; } } else { - qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << QString::fromStdString(info.name) << "(" << aParams.deviceId << ") could not be probed, check audio configuration!"; + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << QString::fromStdString(info.name) << "(" << aParams.deviceId << ") could not be probed, check audio configuration!"; + goto errorHandler; } this->setVolume(setup.localAFgain); return isInitialized; + +errorHandler: + if (retryConnectCount < 10) { + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "*** Attempting to reconnect to audio device in 500ms"; + QTimer::singleShot(500, this, std::bind(&rtHandler::init, this, setup)); + retryConnectCount++; + } + else + { + qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "*** Retry count exceeded, giving up!"; + } + return false; + } @@ -334,5 +350,5 @@ int rtHandler::getLatency() quint16 rtHandler::getAmplitude() { - return amplitude; + return static_cast(amplitude * 255.0); } diff --git a/rthandler.h b/rthandler.h index c78cf6c..c48a2b4 100644 --- a/rthandler.h +++ b/rthandler.h @@ -6,7 +6,7 @@ #include #include -#ifdef Q_OS_WIN +#ifndef Q_OS_LINUX #include "RtAudio.h" #else #include "rtaudio/RtAudio.h" @@ -16,6 +16,7 @@ #include #include #include +#include /* wfview Packet types */ @@ -24,14 +25,14 @@ /* Logarithmic taper for volume control */ #include "audiotaper.h" - +#include "audiohandler.h" /* Audio converter class*/ #include "audioconverter.h" #include -class rtHandler : public QObject +class rtHandler : public audioHandler { Q_OBJECT @@ -96,7 +97,7 @@ private: quint32 lastSentSeq = 0; quint16 currentLatency; - quint16 amplitude = 0; + float amplitude = 0.0; qreal volume = 1.0; audioSetup setup; @@ -106,8 +107,9 @@ private: QThread* converterThread = Q_NULLPTR; QByteArray arrayBuffer; bool isUnderrun = false; - bool isOverrun = true; + bool isOverrun = false; QMutex audioMutex; + int retryConnectCount = 0; }; #endif // rtHandler_H diff --git a/selectradio.cpp b/selectradio.cpp index 0862ace..61e0192 100644 --- a/selectradio.cpp +++ b/selectradio.cpp @@ -42,21 +42,29 @@ void selectRadio::setInUse(quint8 radio, quint8 busy, QString user, QString ip) ui->table->setItem(radio, 3, new QTableWidgetItem(user)); ui->table->setItem(radio, 4, new QTableWidgetItem(ip)); for (int f = 0; f < 5; f++) { - if (busy == 1) + if (busy == 1) + { ui->table->item(radio, f)->setBackground(Qt::darkGreen); - else if (busy == 2) + } + else if (busy == 2) + { ui->table->item(radio, f)->setBackground(Qt::red); + } else + { ui->table->item(radio, f)->setBackground(Qt::black); + } } } void selectRadio::on_table_cellClicked(int row, int col) { qInfo() << "Clicked on " << row << "," << col; - ui->table->selectRow(row); - emit selectedRadio(row); - this->setVisible(false); + if (ui->table->item(row, col)->backgroundColor() != Qt::darkGreen) { + ui->table->selectRow(row); + emit selectedRadio(row); + this->setVisible(false); + } } diff --git a/servermain.cpp b/servermain.cpp index dd4673c..c2942c5 100644 --- a/servermain.cpp +++ b/servermain.cpp @@ -89,7 +89,7 @@ void servermain::openRig() { //qInfo(logSystem()) << "Got rig"; QMetaObject::invokeMethod(radio->rig, [=]() { - radio->rig->commSetup(radio->civAddr, radio->serialPort, radio->baudRate, QString("none"),prefs.tcpPort,0); + radio->rig->commSetup(radio->civAddr, radio->serialPort, radio->baudRate, QString("none"),prefs.tcpPort,radio->waterfallFormat); }, Qt::QueuedConnection); } } @@ -458,6 +458,7 @@ void servermain::loadSettings() settings->setValue("SerialPortBaud", defPrefs.serialPortBaud); settings->setValue("AudioInput", "default"); settings->setValue("AudioOutput", "default"); + settings->setValue("WaterfallFormat", 0); } settings->endArray(); @@ -497,6 +498,7 @@ void servermain::loadSettings() tempPrefs->baudRate = (quint32)settings->value("SerialPortBaud", defPrefs.serialPortBaud).toInt(); tempPrefs->rxAudioSetup.name = settings->value("AudioInput", "default").toString(); tempPrefs->txAudioSetup.name = settings->value("AudioOutput", "default").toString(); + tempPrefs->waterfallFormat = settings->value("WaterfallFormat", 0).toInt(); tempPrefs->rxAudioSetup.type = prefs.audioSystem; tempPrefs->txAudioSetup.type = prefs.audioSystem; diff --git a/udpaudio.h b/udpaudio.h index 478c342..87ae9cf 100644 --- a/udpaudio.h +++ b/udpaudio.h @@ -69,10 +69,10 @@ private: uint16_t sendAudioSeq = 0; - QObject* rxaudio = Q_NULLPTR; + audioHandler* rxaudio = Q_NULLPTR; QThread* rxAudioThread = Q_NULLPTR; - QObject* txaudio = Q_NULLPTR; + audioHandler* txaudio = Q_NULLPTR; QThread* txAudioThread = Q_NULLPTR; QTimer* txAudioTimer = Q_NULLPTR; diff --git a/udpserver.cpp b/udpserver.cpp index 255ba61..b99f2eb 100644 --- a/udpserver.cpp +++ b/udpserver.cpp @@ -395,13 +395,13 @@ void udpServer::controlReceived() connect(radio->txAudioThread, SIGNAL(finished()), radio->txaudio, SLOT(deleteLater())); // Not sure how we make this work in QT5.9? -#if (QT_VERSION >= QT_VERSION_CHECK(7,10,0)) - QMetaObject::invokeMethod((audioHandler*)radio->txaudio, [=]() { +#if (QT_VERSION >= QT_VERSION_CHECK(5,10,0)) + QMetaObject::invokeMethod(radio->txaudio, [=]() { radio->txaudio->init(radio->txAudioSetup); }, Qt::QueuedConnection); #else emit setupTxAudio(radio->txAudioSetup); - //#warning "QT 5.9 is not fully supported multiple rigs will NOT work!" + #warning "QT 5.9 is not fully supported multiple rigs will NOT work!" #endif hasTxAudio = datagram.senderAddress(); @@ -442,7 +442,7 @@ void udpServer::controlReceived() connect(radio->rxAudioThread, SIGNAL(finished()), radio->rxaudio, SLOT(deleteLater())); connect(radio->rxaudio, SIGNAL(haveAudioData(audioPacket)), this, SLOT(receiveAudioData(audioPacket))); -#if (QT_VERSION >= QT_VERSION_CHECK(7,10,0)) +#if (QT_VERSION >= QT_VERSION_CHECK(5,10,0)) QMetaObject::invokeMethod(radio->rxaudio, [=]() { radio->rxaudio->init(radio->rxAudioSetup); }, Qt::QueuedConnection); diff --git a/udpserver.h b/udpserver.h index 7b98e7c..3cbed99 100644 --- a/udpserver.h +++ b/udpserver.h @@ -70,12 +70,13 @@ struct RIGCONFIG { rigCapabilities rigCaps; rigCommander* rig = Q_NULLPTR; QThread* rigThread = Q_NULLPTR; - QObject* rxaudio = Q_NULLPTR; + audioHandler* rxaudio = Q_NULLPTR; QThread* rxAudioThread = Q_NULLPTR; - QObject* txaudio = Q_NULLPTR; + audioHandler* txaudio = Q_NULLPTR; QThread* txAudioThread = Q_NULLPTR; QTimer* rxAudioTimer = Q_NULLPTR; QTimer* connectTimer = Q_NULLPTR; + quint8 waterfallFormat; }; diff --git a/wfmain.h b/wfmain.h index 29bded4..e691258 100644 --- a/wfmain.h +++ b/wfmain.h @@ -41,7 +41,7 @@ #include #include -#ifdef Q_OS_WIN +#ifndef Q_OS_LINUX #include "RtAudio.h" #else #include "rtaudio/RtAudio.h" @@ -914,4 +914,4 @@ Q_DECLARE_METATYPE(rigstate*) #endif // WFMAIN_H -#endif \ No newline at end of file +#endif diff --git a/wfserver.pro b/wfserver.pro index d17f9eb..bbd4fee 100644 --- a/wfserver.pro +++ b/wfserver.pro @@ -41,8 +41,8 @@ win32:DEFINES += __WINDOWS_WASAPI__ #linux:DEFINES += __LINUX_OSS__ linux:DEFINES += __LINUX_PULSE__ macx:DEFINES += __MACOSX_CORE__ -win32:SOURCES += ../rtaudio/RTAudio.cpp -win32:HEADERS += ../rtaudio/RTAUdio.h +!linux:SOURCES += ../rtaudio/RTAudio.cpp +!linux:HEADERS += ../rtaudio/RTAUdio.h !linux:INCLUDEPATH += ../rtaudio linux:LIBS += -lpulse -lpulse-simple -lrtaudio -lpthread diff --git a/wfview.pro b/wfview.pro index ef0e2be..63d0391 100644 --- a/wfview.pro +++ b/wfview.pro @@ -38,9 +38,10 @@ win32:DEFINES += __WINDOWS_WASAPI__ #linux:DEFINES += __LINUX_OSS__ linux:DEFINES += __LINUX_PULSE__ macx:DEFINES += __MACOSX_CORE__ -win32:SOURCES += ../rtaudio/RTAudio.cpp -win32:HEADERS += ../rtaudio/RTAUdio.h +!linux:SOURCES += ../rtaudio/RTAudio.cpp +!linux:HEADERS += ../rtaudio/RTAUdio.h !linux:INCLUDEPATH += ../rtaudio + linux:LIBS += -lpulse -lpulse-simple -lrtaudio -lpthread win32:INCLUDEPATH += ../portaudio/include