From 9fb74ed6e13ff70bac4482859db45174edf8eee8 Mon Sep 17 00:00:00 2001 From: Elliott Liggett Date: Tue, 23 Aug 2022 22:24:05 -0700 Subject: [PATCH] Audio metering initial. Very messy but you can select TxRxAudio as the meter type and wfview will show you TX or RX audio depending upon if you are transmitting or not. You can also select only TxAudio or RxAudio. This is nice for looking at Tx audio levels prior to transmitting, for example, or metering the radio's "monitor" audio while transmitting. --- audioconverter.cpp | 4 +++- audioconverter.h | 3 ++- audiohandler.cpp | 12 +++++++++--- audiohandler.h | 6 ++++++ pahandler.cpp | 4 ++-- rigcommander.cpp | 7 +++++++ rigcommander.h | 8 ++++++++ rthandler.cpp | 4 ++-- udpbase.h | 9 +++++++++ udphandler.cpp | 31 +++++++++++++++++++++++++++++++ udphandler.h | 14 ++++++++++++-- wfmain.cpp | 31 ++++++++++++++++++++++++++++++- wfmain.h | 2 ++ 13 files changed, 123 insertions(+), 12 deletions(-) diff --git a/audioconverter.cpp b/audioconverter.cpp index 2316229..d97da05 100644 --- a/audioconverter.cpp +++ b/audioconverter.cpp @@ -157,7 +157,9 @@ bool audioConverter::convert(audioPacket audio) if (samplesF.size() > 0) { - audio.amplitude = samplesF.array().abs().maxCoeff(); + audio.amplitudePeak = samplesF.array().abs().maxCoeff(); + audio.amplitudeRMS = samplesF.squaredNorm(); + // Set the volume samplesF *= audio.volume; diff --git a/audioconverter.h b/audioconverter.h index a0d2953..b3a1492 100644 --- a/audioconverter.h +++ b/audioconverter.h @@ -29,7 +29,8 @@ struct audioPacket { quint16 sent; QByteArray data; quint8 guid[GUIDLEN]; - float amplitude; + float amplitudePeak; + float amplitudeRMS; qreal volume = 1.0; }; diff --git a/audiohandler.cpp b/audiohandler.cpp index cf49045..6968410 100644 --- a/audiohandler.cpp +++ b/audiohandler.cpp @@ -270,12 +270,18 @@ void audioHandler::convertedOutput(audioPacket packet) { } */ lastSentSeq = packet.seq; + amplitude = packet.amplitudePeak; + computeLevels(); emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun, isOverrun); - - amplitude = packet.amplitude; } } +void audioHandler::computeLevels() +{ + if(levelMean) + levelMean[(levelPosition++)%levelSize] = amplitude * 255; +} + void audioHandler::getNextAudioChunk() { if (audioDevice) { @@ -312,7 +318,7 @@ void audioHandler::convertedInput(audioPacket audio) qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Time since last audio packet" << lastReceived.msecsTo(QTime::currentTime()) << "Expected around" << setup.blockSize ; } lastReceived = QTime::currentTime(); - amplitude = audio.amplitude; + amplitude = audio.amplitudePeak; emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun, isOverrun); } } diff --git a/audiohandler.h b/audiohandler.h index a793973..7c8412b 100644 --- a/audiohandler.h +++ b/audiohandler.h @@ -116,6 +116,12 @@ private: float amplitude=0.0; qreal volume = 1.0; + unsigned char *levelMean = Q_NULLPTR; + unsigned char *levelPeak = Q_NULLPTR; + unsigned char levelSize = 50; + unsigned char levelPosition = 0; + void computeLevels(); + audioSetup setup; OpusEncoder* encoder = Q_NULLPTR; diff --git a/pahandler.cpp b/pahandler.cpp index 9db91d1..f437e0c 100644 --- a/pahandler.cpp +++ b/pahandler.cpp @@ -278,7 +278,7 @@ void paHandler::convertedOutput(audioPacket packet) { currentLatency = packet.time.msecsTo(QTime::currentTime()) + (info->outputLatency * 1000); } - amplitude = packet.amplitude; + amplitude = packet.amplitudePeak; emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun, isOverrun); } } @@ -289,7 +289,7 @@ void paHandler::convertedInput(audioPacket packet) { if (packet.data.size() > 0) { emit haveAudioData(packet); - amplitude = packet.amplitude; + amplitude = packet.amplitudePeak; const PaStreamInfo* info = Pa_GetStreamInfo(audio); currentLatency = packet.time.msecsTo(QTime::currentTime()) + (info->inputLatency * 1000); emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun, isOverrun); diff --git a/rigcommander.cpp b/rigcommander.cpp index c0d3ea6..d6ea80c 100644 --- a/rigcommander.cpp +++ b/rigcommander.cpp @@ -166,6 +166,8 @@ void rigCommander::commSetup(unsigned char rigCivAddr, udpPreferences prefs, aud // Connect for errors/alerts connect(udp, SIGNAL(haveNetworkError(QString, QString)), this, SLOT(handleSerialPortError(QString, QString))); connect(udp, SIGNAL(haveNetworkStatus(networkStatus)), this, SLOT(handleStatusUpdate(networkStatus))); + connect(udp, SIGNAL(haveNetworkAudioLevels(networkAudioLevels)), this, SLOT(handleNetworkAudioLevels(networkAudioLevels))); + connect(ptty, SIGNAL(haveSerialPortError(QString, QString)), this, SLOT(handleSerialPortError(QString, QString))); connect(this, SIGNAL(getMoreDebug()), ptty, SLOT(debugThis())); @@ -245,6 +247,11 @@ void rigCommander::handleStatusUpdate(const networkStatus status) emit haveStatusUpdate(status); } +void rigCommander::handleNetworkAudioLevels(networkAudioLevels l) +{ + emit haveNetworkAudioLevels(l); +} + bool rigCommander::usingLAN() { return usingNativeLAN; diff --git a/rigcommander.h b/rigcommander.h index 7921b24..e26941e 100644 --- a/rigcommander.h +++ b/rigcommander.h @@ -36,6 +36,7 @@ enum meterKind { meterRxdB, meterTxMod, meterRxAudio, + meterAudio, meterLatency }; @@ -277,6 +278,7 @@ public slots: // Housekeeping: void handleStatusUpdate(const networkStatus status); + void handleNetworkAudioLevels(networkAudioLevels); void radioSelection(QList radios); void radioUsage(quint8 radio, quint8 busy, QString name, QString ip); void setCurrentRadio(quint8 radio); @@ -288,6 +290,7 @@ signals: void commReady(); void haveSerialPortError(const QString port, const QString errorText); void haveStatusUpdate(const networkStatus status); + void haveNetworkAudioLevels(const networkAudioLevels l); void dataForComm(const QByteArray &outData); void toggleRTS(bool rtsOn); @@ -397,6 +400,11 @@ private: quint16 decodeTone(QByteArray eTone); quint16 decodeTone(QByteArray eTone, bool &tinv, bool &rinv); + unsigned char audioLevelRxMean[50]; + unsigned char audioLevelRxPeak[50]; + unsigned char audioLevelTxMean[50]; + unsigned char audioLevelTxPeak[50]; + void parseMode(); void parseSpectrum(); void parseWFData(); diff --git a/rthandler.cpp b/rthandler.cpp index f848c38..73b8edb 100644 --- a/rthandler.cpp +++ b/rthandler.cpp @@ -331,7 +331,7 @@ void rtHandler::convertedOutput(audioPacket packet) audioMutex.lock(); arrayBuffer.append(packet.data); audioMutex.unlock(); - amplitude = packet.amplitude; + amplitude = packet.amplitudePeak; currentLatency = packet.time.msecsTo(QTime::currentTime()) + (outFormat.durationForBytes(audio->getStreamLatency() * (outFormat.sampleSize() / 8) * outFormat.channelCount())/1000); emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun, isOverrun); } @@ -342,7 +342,7 @@ void rtHandler::convertedInput(audioPacket packet) { if (packet.data.size() > 0) { emit haveAudioData(packet); - amplitude = packet.amplitude; + amplitude = packet.amplitudePeak; currentLatency = packet.time.msecsTo(QTime::currentTime()) + (outFormat.durationForBytes(audio->getStreamLatency() * (outFormat.sampleSize() / 8) * outFormat.channelCount())/1000); emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun, isOverrun); } diff --git a/udpbase.h b/udpbase.h index f042b4d..e5c67cf 100644 --- a/udpbase.h +++ b/udpbase.h @@ -37,6 +37,15 @@ struct udpPreferences { quint8 waterfallFormat; }; +struct networkAudioLevels { + bool haveTxLevels = false; + bool haveRxLevels = false; + quint8 rxAudioRMS = 0; + quint8 txAudioRMS = 0; + quint8 rxAudioPeak = 0; + quint8 txAudioPeak = 0; +}; + struct networkStatus { quint8 rxAudioBufferPercent; quint8 txAudioBufferPercent; diff --git a/udphandler.cpp b/udphandler.cpp index f0ffd60..fd32779 100644 --- a/udphandler.cpp +++ b/udphandler.cpp @@ -149,6 +149,17 @@ void udpHandler::getRxLevels(quint16 amplitude,quint16 latency,quint16 current, status.rxCurrentLatency = current; status.rxUnderrun = under; status.rxOverrun = over; + audioLevelsRxPeak[(audioLevelsRxPeakPosition++)%audioLevelBufferSize] = amplitude; + if((audioLevelsRxPeakPosition++)%3 == 0) + { + // calculate mean and emit signal + unsigned char mean = findMean(audioLevelsRxPeak); + networkAudioLevels l; + l.haveRxLevels = true; + l.rxAudioPeak = mean; + //qDebug(logSystem()) << "audio level meter being emitted from udpHandler"; + emit haveNetworkAudioLevels(l); + } } void udpHandler::getTxLevels(quint16 amplitude,quint16 latency, quint16 current, bool under, bool over) { @@ -157,6 +168,26 @@ void udpHandler::getTxLevels(quint16 amplitude,quint16 latency, quint16 current, status.txCurrentLatency = current; status.txUnderrun = under; status.txOverrun = over; + audioLevelsTxPeak[(audioLevelsTxPeakPosition++)%audioLevelBufferSize] = amplitude; + if((audioLevelsTxPeakPosition++)%3 == 0) + { + // calculate mean and emit signal + unsigned char mean = findMean(audioLevelsTxPeak); + networkAudioLevels l; + l.haveTxLevels = true; + l.txAudioPeak = mean; + emit haveNetworkAudioLevels(l); + } +} + +unsigned char udpHandler::findMean(unsigned char *data) +{ + unsigned int sum=0; + for(int p=0; p < audioLevelBufferSize; p++) + { + sum += data[p]; + } + return sum / audioLevelBufferSize; } void udpHandler::dataReceived() diff --git a/udphandler.h b/udphandler.h index 8300805..231577c 100644 --- a/udphandler.h +++ b/udphandler.h @@ -29,7 +29,7 @@ #include "udpcivdata.h" #include "udpaudio.h" - +#define audioLevelBufferSize (3) // Class to handle the connection/disconnection of the radio. class udpHandler: public udpBase @@ -58,7 +58,8 @@ public slots: void setCurrentRadio(quint8 radio); void getRxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under, bool over); void getTxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under, bool over); - + //void handleRxLevels(networkAudioLevels); + //void handleTxLevels(networkAudioLevels); signals: void haveDataFromPort(QByteArray data); // emit this when we have data, connect to rigcommander @@ -67,6 +68,7 @@ signals: void haveChangeLatency(quint16 value); void haveSetVolume(unsigned char value); void haveNetworkStatus(networkStatus); + void haveNetworkAudioLevels(networkAudioLevels); void haveBaudRate(quint32 baudrate); void requestRadioSelection(QList radios); void setRadioUsage(quint8, quint8 busy, QString name, QString mac); @@ -122,6 +124,14 @@ private: quint16 txSampleRates = 0; networkStatus status; bool splitWf = false; + + unsigned char audioLevelsTxPeak[audioLevelBufferSize]; + unsigned char audioLevelsRxPeak[audioLevelBufferSize]; + unsigned char audioLevelsTxPeakPosition = 0; + unsigned char audioLevelsRxPeakPosition = 0; + unsigned char findMean(unsigned char *d); + + }; diff --git a/wfmain.cpp b/wfmain.cpp index c55d6cc..6ed376b 100644 --- a/wfmain.cpp +++ b/wfmain.cpp @@ -47,6 +47,7 @@ wfmain::wfmain(const QString serialPortCL, const QString hostCL, const QString s qRegisterMetaType(); qRegisterMetaType>(); qRegisterMetaType(); + qRegisterMetaType(); haveRigCaps = false; @@ -424,6 +425,7 @@ void wfmain::makeRig() // Rig status and Errors: connect(rig, SIGNAL(haveSerialPortError(QString, QString)), this, SLOT(receiveSerialPortError(QString, QString))); connect(rig, SIGNAL(haveStatusUpdate(networkStatus)), this, SLOT(receiveStatusUpdate(networkStatus))); + connect(rig, SIGNAL(haveNetworkAudioLevels(networkAudioLevels)), this, SLOT(receiveNetworkAudioLevels(networkAudioLevels))); connect(rig, SIGNAL(requestRadioSelection(QList)), this, SLOT(radioSelection(QList))); connect(rig, SIGNAL(setRadioUsage(quint8, quint8, QString, QString)), selRad, SLOT(setInUse(quint8, quint8, QString, QString))); connect(selRad, SIGNAL(selectedRadio(quint8)), rig, SLOT(setCurrentRadio(quint8))); @@ -615,6 +617,22 @@ void wfmain::receiveStatusUpdate(networkStatus status) //qInfo(logSystem()) << "Got Status Update" << status.rxAudioLevel; } +void wfmain::receiveNetworkAudioLevels(networkAudioLevels l) +{ + qInfo(logSystem()) << "audio level meter received."; + meterKind m = meterNone; + if(l.haveRxLevels) + { + m = meterRxAudio; + receiveMeter(m, l.rxAudioPeak); + } + if(l.haveTxLevels) + { + m = meterTxMod; + receiveMeter(m, l.txAudioPeak); + } +} + void wfmain::setupPlots() { spectrumDrawLock = true; @@ -746,6 +764,10 @@ void wfmain::setupMainUI() ui->meter2selectionCombo->addItem("Voltage", meterVoltage); ui->meter2selectionCombo->addItem("Current", meterCurrent); ui->meter2selectionCombo->addItem("Center", meterCenter); + ui->meter2selectionCombo->addItem("TxRxAudio", meterAudio); + ui->meter2selectionCombo->addItem("RxAudio", meterRxAudio); + ui->meter2selectionCombo->addItem("TxAudio", meterTxMod); + ui->meter2Widget->hide(); ui->meter2selectionCombo->show(); @@ -5096,6 +5118,12 @@ void wfmain::receiveMeter(meterKind inMeter, unsigned char level) if(ui->meter2Widget->getMeterType() == inMeter) { ui->meter2Widget->setLevel(level); + } else if ( (ui->meter2Widget->getMeterType() == meterAudio) && + (inMeter == meterTxMod) && amTransmitting) { + ui->meter2Widget->setLevel(level); + } else if ( (ui->meter2Widget->getMeterType() == meterAudio) && + (inMeter == meterRxAudio) && !amTransmitting) { + ui->meter2Widget->setLevel(level); } break; } @@ -5765,7 +5793,8 @@ void wfmain::on_meter2selectionCombo_activated(int index) } else { ui->meter2Widget->show(); ui->meter2Widget->setMeterType(newMeterType); - insertPeriodicCommandUnique(newCmd); + if((newMeterType!=meterRxAudio) && (newMeterType!=meterTxMod) && (newMeterType!=meterAudio)) + insertPeriodicCommandUnique(newCmd); } prefs.meter2Type = newMeterType; diff --git a/wfmain.h b/wfmain.h index 55fc8d2..446967a 100644 --- a/wfmain.h +++ b/wfmain.h @@ -275,6 +275,7 @@ private slots: void receiveFoundRigID(rigCapabilities rigCaps); void receiveSerialPortError(QString port, QString errorText); void receiveStatusUpdate(networkStatus status); + void receiveNetworkAudioLevels(networkAudioLevels l); void handlePlotClick(QMouseEvent *); void handlePlotDoubleClick(QMouseEvent *); void handleWFClick(QMouseEvent *); @@ -943,6 +944,7 @@ Q_DECLARE_METATYPE(struct SERVERCONFIG) Q_DECLARE_METATYPE(struct timekind) Q_DECLARE_METATYPE(struct datekind) Q_DECLARE_METATYPE(struct networkStatus) +Q_DECLARE_METATYPE(struct networkAudioLevels) Q_DECLARE_METATYPE(enum rigInput) Q_DECLARE_METATYPE(enum meterKind) Q_DECLARE_METATYPE(enum spectrumMode)