From 27eb855adb6b72ca2f7f15f71b567c2a4226e759 Mon Sep 17 00:00:00 2001 From: Phil Taylor Date: Sat, 27 Feb 2021 00:37:00 +0000 Subject: [PATCH] Create rx audio packets with timestamp Lots of other changes but if this works OK, I will update tx audio to use the same system. --- audiohandler.cpp | 148 ++++++++++++++++++---------- audiohandler.h | 26 +++-- rigcommander.cpp | 10 +- rigcommander.h | 6 +- udphandler.cpp | 247 ++++++++++++++++++++++++++--------------------- udphandler.h | 31 +++--- wfmain.cpp | 40 +++++--- wfmain.h | 11 ++- wfmain.ui | 42 ++++++-- 9 files changed, 346 insertions(+), 215 deletions(-) diff --git a/audiohandler.cpp b/audiohandler.cpp index aed567a..c416fa6 100644 --- a/audiohandler.cpp +++ b/audiohandler.cpp @@ -735,7 +735,7 @@ audioHandler::audioHandler(QObject* parent) : audioOutput(Q_NULLPTR), audioInput(Q_NULLPTR), isUlaw(false), - bufferSize(0), + latency(0), isInput(0), volume(1.0f) { @@ -752,7 +752,7 @@ audioHandler::~audioHandler() } } -bool audioHandler::init(const quint8 bits, const quint8 channels, const quint16 samplerate, const quint16 buffer, const bool ulaw, const bool isinput) +bool audioHandler::init(const quint8 bits, const quint8 channels, const quint16 samplerate, const quint16 latency, const bool ulaw, const bool isinput) { if (isInitialized) { return false; @@ -765,8 +765,8 @@ bool audioHandler::init(const quint8 bits, const quint8 channels, const quint16 format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); - this->bufferSize = buffer; - this->isUlaw = ulaw; + this->latency = latency; + this->isUlaw = ulaw; this->isInput = isinput; this->radioSampleBits = bits; this->radioSampleRate = samplerate; @@ -831,7 +831,7 @@ void audioHandler::reinit() if (audioInput != Q_NULLPTR) delete audioInput; audioInput = new QAudioInput(deviceInfo, format, this); - //audioInput->setBufferSize(audioBuffer); + //audioInput->setLatency(audioBuffer); //audioInput->setNotifyInterval(20); connect(audioInput, SIGNAL(notify()), SLOT(notified())); @@ -844,7 +844,7 @@ void audioHandler::reinit() delete audioOutput; audioOutput = Q_NULLPTR; audioOutput = new QAudioOutput(deviceInfo, format, this); - //audioOutput->setBufferSize(audioBuffer); + //audioOutput->setLatency(audioBuffer); connect(audioOutput, SIGNAL(notify()), SLOT(notified())); connect(audioOutput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State))); } @@ -899,8 +899,8 @@ void audioHandler::stop() // Stop audio output audioOutput->stop(); QByteArray ret; - - buffer.clear(); + audioBuffer.clear(); + //buffer.clear(); this->close(); } @@ -915,47 +915,92 @@ void audioHandler::stop() qint64 audioHandler::readData(char* data, qint64 maxlen) { // Calculate output length, always full samples - int outlen = 0; - if (radioSampleBits == 8) - { - // Input buffer is 8bit and output buffer is 16bit - outlen = qMin(buffer.length(), (int)maxlen / 2); - for (int f = 0; f < outlen; f++) - { - if (isUlaw) - qToLittleEndian(ulaw_decode[(quint8)buffer.at(f)], data + (f * 2)); - else - qToLittleEndian((qint16)(buffer[f] << 8) - 32640, data + (f * 2)); - } - QMutexLocker locker(&mutex); - buffer.remove(0, outlen); - outlen = outlen * 2; - } - else if (radioSampleBits == 16) { - // Just copy it - outlen = qMin(buffer.length(), (int)maxlen); - if (outlen % 2 != 0) { - outlen += 1; // Not sure if this is necessary as we should always have an even number! - } - memcpy(data, buffer.data(), outlen); - QMutexLocker locker(&mutex); - buffer.remove(0, outlen); - } - else { - qDebug(logAudio()) << "Sample bits MUST be 8 or 16 - got: " << radioSampleBits; // Should never happen? - } - return outlen; + int sentlen = 0; + + // Get next packet from buffer. + if (!audioBuffer.isEmpty()) + { + + // Sort the buffer by seq number. + std::sort(audioBuffer.begin(), audioBuffer.end(), + [](const AUDIOPACKET& a, const AUDIOPACKET& b) -> bool + { + return a.seq < b.seq; + }); + + // Output buffer is ALWAYS 16 bit. + int divisor = 16 / radioSampleBits; + + auto packet = audioBuffer.begin(); + while (packet != audioBuffer.end() && sentlentime.msecsTo(QTime::currentTime()) > latency) { + qDebug(logAudio()) << "Packet " << hex << packet->seq << "is too late, deleting" << dec << packet->time.msecsTo(QTime::currentTime()) << "ms"; + QMutexLocker locker(&mutex); + packet=audioBuffer.erase(packet); // returns next packet + } + else + { + // Will this packet fit in the current buffer? + int send = qMin((int)((maxlen/divisor) - (sentlen/divisor)), packet->data.length() - packet->sent); + + if (divisor == 2) + { + // Input buffer is 8bit and output buffer is 16bit + for (int f = 0; f < send; f++) + { + if (isUlaw) + qToLittleEndian(ulaw_decode[(quint8)packet->data[f+packet->sent]], data + (f * 2 + sentlen)); + else + qToLittleEndian((qint16)(packet->data[f+packet->sent] << 8) - 32640, data + (f * 2 + sentlen)); + } + sentlen = sentlen + (send*divisor); + } + else if (divisor == 1) + { + // 16 bit audio so just copy it in place. + //qDebug(logAudio()) << "Adding packet to buffer:" << (*packet).seq << ": " << (*packet).data.length()-(*packet).sent; + memcpy(data+sentlen, packet->data.constData()+packet->sent, send); + sentlen = sentlen + (send*divisor); + } + else + { + //qDebug(logAudio()) << "Invalid number of bits in audio " << radioSampleBits; + break; + } + + if (send == packet->data.length()) + { + QMutexLocker locker(&mutex); + packet = audioBuffer.erase(packet); // returns next packet + if (maxlen - sentlen == 0) + { + break; + } + } + else if (send == 0) + { + // We have no more space or no packets so just break. + break; + } + else + { + // We ended-up with a partial packet left so add it to the buffer and store where we left off. + packet->sent = send; + break; + } + } + } + } + //qDebug(logAudio()) << "Returning: " << sentlen << " max: " << maxlen; + + return sentlen; } qint64 audioHandler::writeData(const char* data, qint64 len) { QMutexLocker locker(&mutex); - if (buffer.length() > bufferSize * 4) - { - qWarning() << "writeData() Buffer overflow"; - buffer.clear(); // Will cause a click! - } if (isUlaw) { for (int f = 0; f < len / 2; f++) @@ -1000,8 +1045,6 @@ void audioHandler::notified() } - - void audioHandler::stateChanged(QAudio::State state) { if (state == QAudio::IdleState && audioOutput->error() == QAudio::UnderrunError) { @@ -1016,12 +1059,11 @@ void audioHandler::stateChanged(QAudio::State state) -void audioHandler::incomingAudio(const QByteArray& data) +void audioHandler::incomingAudio(const AUDIOPACKET data) { - //qDebug(logAudio()) << "Got " << data.length() << " samples"; if (audioOutput != Q_NULLPTR && audioOutput->state() != QAudio::StoppedState) { QMutexLocker locker(&mutex); - buffer.append(data); + audioBuffer.push_back(data); // Restart playback if (audioOutput->state() == QAudio::SuspendedState) { @@ -1031,15 +1073,15 @@ void audioHandler::incomingAudio(const QByteArray& data) } } -void audioHandler::changeBufferSize(const quint16 newSize) +void audioHandler::changeLatency(const quint16 newSize) { - qDebug(logAudio()) << this->metaObject()->className() << ": Changing buffer size to: " << newSize << " from " << bufferSize; - bufferSize = newSize; + qDebug(logAudio()) << this->metaObject()->className() << ": Changing latency to: " << newSize << " from " << latency; + latency = newSize; } -void audioHandler::getBufferSize() +void audioHandler::getLatency() { - emit sendBufferSize(audioOutput->bufferSize()); + emit sendLatency(latency); } bool audioHandler::isChunkAvailable() diff --git a/audiohandler.h b/audiohandler.h index 9375c95..26f1ab8 100644 --- a/audiohandler.h +++ b/audiohandler.h @@ -14,20 +14,31 @@ #include #include #include +#include #include //#define BUFFER_SIZE (32*1024) + +struct AUDIOPACKET { + quint16 seq; + QTime time; + quint16 sent; + QByteArray data; +}; + + class audioHandler : public QIODevice { Q_OBJECT + public: audioHandler(QObject* parent = 0); ~audioHandler(); - void getBufferSize(); + void getLatency(); bool setDevice(QAudioDeviceInfo deviceInfo); @@ -43,9 +54,9 @@ public: void getNextAudioChunk(QByteArray &data); bool isChunkAvailable(); public slots: - bool init(const quint8 bits, const quint8 channels, const quint16 samplerate, const quint16 bufferSize, const bool isulaw, const bool isinput); - void incomingAudio(const QByteArray& data); - void changeBufferSize(const quint16 newSize); + bool init(const quint8 bits, const quint8 channels, const quint16 samplerate, const quint16 latency, const bool isulaw, const bool isinput); + void incomingAudio(const AUDIOPACKET data); + void changeLatency(const quint16 newSize); private slots: void notified(); @@ -53,7 +64,7 @@ private slots: signals: void audioMessage(QString message); - void sendBufferSize(quint16 newSize); + void sendLatency(quint16 newSize); void haveAudioData(const QByteArray& data); @@ -67,7 +78,7 @@ private: QAudioOutput* audioOutput; QAudioInput* audioInput; bool isUlaw; - int bufferSize; + quint16 latency; bool isInput; // Used to determine whether input or output audio float volume; @@ -76,8 +87,7 @@ private: QAudioDeviceInfo deviceInfo; quint16 radioSampleRate; quint8 radioSampleBits; - - + QVector audioBuffer; }; #endif // AUDIOHANDLER_H diff --git a/rigcommander.cpp b/rigcommander.cpp index ea638b0..d2b7e00 100644 --- a/rigcommander.cpp +++ b/rigcommander.cpp @@ -64,7 +64,7 @@ void rigCommander::commSetup(unsigned char rigCivAddr, QString rigSerialPort, qu } void rigCommander::commSetup(unsigned char rigCivAddr, QString ip, quint16 cport, quint16 sport, quint16 aport, - QString username, QString password, quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec) + QString username, QString password, quint16 rxlatency, quint16 txlatency, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec) { // construct // TODO: Bring this parameter and the comm port from the UI. @@ -88,7 +88,7 @@ void rigCommander::commSetup(unsigned char rigCivAddr, QString ip, quint16 cport */ if (udp == Q_NULLPTR) { - udp = new udpHandler(ip, cport, sport, aport, username, password, buffer, rxsample, rxcodec, txsample, txcodec); + udp = new udpHandler(ip, cport, sport, aport, username, password, rxlatency, txlatency, rxsample, rxcodec, txsample, txcodec); udpHandlerThread = new QThread(this); @@ -107,7 +107,7 @@ void rigCommander::commSetup(unsigned char rigCivAddr, QString ip, quint16 cport // data from the program to the comm port: connect(this, SIGNAL(dataForComm(QByteArray)), udp, SLOT(receiveDataFromUserToRig(QByteArray))); - connect(this, SIGNAL(haveChangeBufferSize(quint16)), udp, SLOT(changeBufferSize(quint16))); + connect(this, SIGNAL(haveChangeLatency(quint16)), udp, SLOT(changeLatency(quint16))); // Connect for errors/alerts connect(udp, SIGNAL(haveNetworkError(QString, QString)), this, SLOT(handleSerialPortError(QString, QString))); @@ -2480,9 +2480,9 @@ void rigCommander::getRigID() prepDataAndSend(payload); } -void rigCommander::changeBufferSize(const quint16 value) +void rigCommander::changeLatency(const quint16 value) { - emit haveChangeBufferSize(value); + emit haveChangeLatency(value); } void rigCommander::sayAll() diff --git a/rigcommander.h b/rigcommander.h index 08c0561..75ab5ac 100644 --- a/rigcommander.h +++ b/rigcommander.h @@ -48,7 +48,7 @@ public slots: void process(); void commSetup(unsigned char rigCivAddr, QString rigSerialPort, quint32 rigBaudRate); void commSetup(unsigned char rigCivAddr, QString ip, quint16 cport, quint16 sport, quint16 aport, - QString username, QString password, quint16 buffer, quint16 rxsample, quint8 rxcodec,quint16 txsample, quint8 txcodec); + QString username, QString password, quint16 rxlatency,quint16 txlatency, quint16 rxsample, quint8 rxcodec,quint16 txsample, quint8 txcodec); void closeComm(); void enableSpectOutput(); @@ -139,7 +139,7 @@ public slots: void handleNewData(const QByteArray &data); void handleSerialPortError(const QString port, const QString errorText); void handleStatusUpdate(const QString text); - void changeBufferSize(const quint16 value); + void changeLatency(const quint16 value); void sayFrequency(); void sayMode(); void sayAll(); @@ -197,7 +197,7 @@ signals: void finished(); void havePTTStatus(bool pttOn); void haveATUStatus(unsigned char status); - void haveChangeBufferSize(quint16 value); + void haveChangeLatency(quint16 value); void haveDataForServer(QByteArray outData); void initUdpHandler(); diff --git a/udphandler.cpp b/udphandler.cpp index 4795f01..2b26baa 100644 --- a/udphandler.cpp +++ b/udphandler.cpp @@ -4,7 +4,7 @@ #include "udphandler.h" #include "logcategories.h" udpHandler::udpHandler(QString ip, quint16 controlPort, quint16 civPort, quint16 audioPort, QString username, QString password, - quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec) : + quint16 rxlatency, quint16 txlatency, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec) : controlPort(controlPort), civPort(civPort), audioPort(audioPort) @@ -13,13 +13,14 @@ udpHandler::udpHandler(QString ip, quint16 controlPort, quint16 civPort, quint16 this->port = this->controlPort; this->username = username; this->password = password; - this->rxBufferSize = buffer; + this->rxLatency = rxlatency; + this->txLatency = txlatency; this->rxSampleRate = rxsample; this->txSampleRate = txsample; this->rxCodec = rxcodec; this->txCodec = txcodec; - qDebug(logUdp()) << "Starting udpHandler user:" << username << " buffer:" << buffer << " rx sample rate: " << rxsample << + qDebug(logUdp()) << "Starting udpHandler user:" << username << " rx latency:" << rxLatency << " tx latency:" << txLatency << " rx sample rate: " << rxsample << " rx codec: " << rxcodec << " tx sample rate: " << txsample << " tx codec: " << txcodec; // Try to set the IP address, if it is a hostname then perform a DNS lookup. @@ -110,9 +111,9 @@ udpHandler::~udpHandler() } -void udpHandler::changeBufferSize(quint16 value) +void udpHandler::changeLatency(quint16 value) { - emit haveChangeBufferSize(value); + emit haveChangeLatency(value); } void udpHandler::receiveFromCivStream(QByteArray data) @@ -291,10 +292,10 @@ void udpHandler::dataReceived() } else { civ = new udpCivData(localIP, radioIP, civPort); - audio = new udpAudio(localIP, radioIP, audioPort, rxBufferSize, rxSampleRate, rxCodec, txSampleRate, txCodec); + audio = new udpAudio(localIP, radioIP, audioPort, rxLatency, txLatency, rxSampleRate, rxCodec, txSampleRate, txCodec); QObject::connect(civ, SIGNAL(receive(QByteArray)), this, SLOT(receiveFromCivStream(QByteArray))); - QObject::connect(this, SIGNAL(haveChangeBufferSize(quint16)), audio, SLOT(changeBufferSize(quint16))); + QObject::connect(this, SIGNAL(haveChangeLatency(quint16)), audio, SLOT(changeLatency(quint16))); streamOpened = true; @@ -360,7 +361,6 @@ void udpHandler::sendRequestStream() QByteArray usernameEncoded; passcode(username, usernameEncoded); - int txSeqBufLengthMs = 300; conninfo_packet p; memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! @@ -385,7 +385,7 @@ void udpHandler::sendRequestStream() p.txsample = qToBigEndian((quint32)txSampleRate); p.civport = qToBigEndian((quint32)civPort); p.audioport = qToBigEndian((quint32)audioPort); - p.txbuffer = qToBigEndian((quint32)txSeqBufLengthMs); + p.txbuffer = qToBigEndian((quint32)txLatency); authInnerSendSeq++; @@ -631,13 +631,14 @@ void udpCivData::dataReceived() // Audio stream -udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec) +udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint16 rxlatency, quint16 txlatency, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec) { qDebug(logUdp()) << "Starting udpAudio"; this->localIP = local; this->port = audioPort; this->radioIP = ip; - this->bufferSize = buffer; + this->rxLatency = rxlatency; + this->txLatency = txlatency; this->rxSampleRate = rxsample; this->txSampleRate = txsample; this->rxCodec = rxcodec; @@ -674,9 +675,10 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint rxaudio->moveToThread(rxAudioThread); connect(this, SIGNAL(setupRxAudio(quint8, quint8, quint16, quint16, bool, bool)), rxaudio, SLOT(init(quint8, quint8, quint16, quint16, bool, bool))); - connect(this, SIGNAL(haveAudioData(QByteArray)), rxaudio, SLOT(incomingAudio(QByteArray))); - connect(this, SIGNAL(haveChangeBufferSize(quint16)), rxaudio, SLOT(changeBufferSize(quint16))); - connect(this, SIGNAL(haveChangeBufferSize(quint16)), rxaudio, SLOT(changeBufferSize(quint16))); + + qRegisterMetaType(); + connect(this, SIGNAL(haveAudioData(AUDIOPACKET)), rxaudio, SLOT(incomingAudio(AUDIOPACKET))); + connect(this, SIGNAL(haveChangeLatency(quint16)), rxaudio, SLOT(changeLatency(quint16))); connect(rxAudioThread, SIGNAL(finished()), rxaudio, SLOT(deleteLater())); if (txCodec == 0x01) @@ -692,7 +694,6 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint txaudio->moveToThread(txAudioThread); connect(this, SIGNAL(setupTxAudio(quint8, quint8, quint16, quint16, bool, bool)), txaudio, SLOT(init(quint8, quint8, quint16, quint16, bool, bool))); - //connect(txaudio, SIGNAL(haveAudioData(QByteArray)), this, SLOT(sendTxAudio(QByteArray))); connect(txAudioThread, SIGNAL(finished()), txaudio, SLOT(deleteLater())); rxAudioThread->start(); @@ -705,8 +706,8 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint connect(pingTimer, &QTimer::timeout, this, &udpBase::sendPing); pingTimer->start(PING_PERIOD); // send ping packets every 100ms - emit setupTxAudio(txNumSamples, txChannelCount, txSampleRate, bufferSize, txIsUlawCodec, true); - emit setupRxAudio(rxNumSamples, rxChannelCount, rxSampleRate, bufferSize, rxIsUlawCodec, false); + emit setupTxAudio(txNumSamples, txChannelCount, txSampleRate, txLatency, txIsUlawCodec, true); + emit setupRxAudio(rxNumSamples, rxChannelCount, rxSampleRate, txLatency, rxIsUlawCodec, false); watchdogTimer = new QTimer(); connect(watchdogTimer, &QTimer::timeout, this, &udpAudio::watchdog); @@ -719,9 +720,6 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint areYouThereTimer = new QTimer(); connect(areYouThereTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, false, 0x03, 0)); areYouThereTimer->start(AREYOUTHERE_PERIOD); - - - } udpAudio::~udpAudio() @@ -793,9 +791,9 @@ void udpAudio::sendTxAudio() } } -void udpAudio::changeBufferSize(quint16 value) +void udpAudio::changeLatency(quint16 value) { - emit haveChangeBufferSize(value); + emit haveChangeLatency(value); } @@ -836,14 +834,20 @@ void udpAudio::dataReceived() r.mid(0, 2) == QByteArrayLiteral("\x70\x04")) { lastReceived = QTime::currentTime(); - emit haveAudioData(r.mid(24)); + AUDIOPACKET tempAudio; + tempAudio.seq = in->seq; + tempAudio.time = lastReceived; + tempAudio.sent = 0; + tempAudio.data = r.mid(24); + emit haveAudioData(tempAudio); + //rxaudio->incomingAudio(tempAudio); //rxaudio->incomingAudio(r.mid(24)); } } break; } } - + udpBase::dataReceived(r); // Call parent function to process the rest. r.clear(); datagram.clear(); @@ -861,6 +865,11 @@ void udpBase::init() qDebug(logUdp()) << "UDP Stream bound to local port:" << localPort << " remote port:" << port; uint32_t addr = localIP.toIPv4Address(); myId = (addr >> 8 & 0xff) << 24 | (addr & 0xff) << 16 | (localPort & 0xffff); + + retransmitTimer = new QTimer(); + connect(retransmitTimer, &QTimer::timeout, this, &udpBase::sendRetransmitRequest); + retransmitTimer->start(RETRANSMIT_PERIOD); + } udpBase::~udpBase() @@ -887,10 +896,16 @@ udpBase::~udpBase() idleTimer->stop(); delete idleTimer; } + if (retransmitTimer != Q_NULLPTR) + { + retransmitTimer->stop(); + delete retransmitTimer; + } pingTimer = Q_NULLPTR; idleTimer = Q_NULLPTR; areYouThereTimer = Q_NULLPTR; + retransmitTimer = Q_NULLPTR; } @@ -1023,13 +1038,13 @@ void udpBase::dataReceived(QByteArray r) packetsLost++; } } - } else - if (in->len != PING_SIZE && in->type == 0x00 && in->seq != 0x00) + } + else if (in->len != PING_SIZE && in->type == 0x00 && in->seq != 0x00) { if (rxSeqBuf.isEmpty()) { rxSeqBuf.append(in->seq); } - else + else { std::sort(rxSeqBuf.begin(), rxSeqBuf.end()); if (in->seq < rxSeqBuf.front()) @@ -1038,11 +1053,12 @@ void udpBase::dataReceived(QByteArray r) // Looks like it has rolled over so clear buffer and start again. rxSeqBuf.clear(); + return; } if (!rxSeqBuf.contains(in->seq)) { - // Add incoming packet to the received buffer and if it is in the mising buffer, remove it. + // Add incoming packet to the received buffer and if it is in the missing buffer, remove it. rxSeqBuf.append(in->seq); // Check whether this is one of our missing ones! auto s = std::find_if(rxMissing.begin(), rxMissing.end(), [&cs = in->seq](SEQBUFENTRY& s) { return s.seqNum == cs; }); @@ -1051,95 +1067,104 @@ void udpBase::dataReceived(QByteArray r) qDebug(logUdp()) << this->metaObject()->className() << ": Missing SEQ has been received! " << hex << in->seq; s = rxMissing.erase(s); } - std::sort(rxSeqBuf.begin(), rxSeqBuf.end()); - } - - // Find all gaps in received packets - - QByteArray missingSeqs; - - auto i = std::adjacent_find(rxSeqBuf.begin(), rxSeqBuf.end(), [](quint16 l, quint16 r) {return l + 1 < r; }); - while (i != rxSeqBuf.end()) - { - if (i + 1 != rxSeqBuf.end()) - { - if (*(i + 1) - *i < 30) - { - for (quint16 j = *i + 1; j < *(i + 1); j++) - { - //qDebug(logUdp()) << this->metaObject()->className() << ": Found missing seq between " << *i << " : " << *(i + 1) << " (" << j << ")"; - auto s = std::find_if(rxMissing.begin(), rxMissing.end(), [&cs = j](SEQBUFENTRY& s) { return s.seqNum == cs; }); - if (s == rxMissing.end()) - { - // We haven't seen this missing packet before - //qDebug(logUdp()) << this->metaObject()->className() << ": Adding to missing buffer (len="<< rxMissing.length() << "): " << j; - SEQBUFENTRY b; - b.seqNum = j; - b.retransmitCount = 0; - b.timeSent = QTime::currentTime(); - rxMissing.append(b); - packetsLost++; - } - else { - if (s->retransmitCount == 10) - { - // We have tried 10 times to request this packet, time to give up! - s = rxMissing.erase(s); - rxSeqBuf.append(j); // Final thing is to add to received buffer! - } - - } - } - } - else { - qDebug(logUdp()) << this->metaObject()->className() << ": Too many missing, flushing buffers"; - rxSeqBuf.clear(); - missingSeqs.clear(); - break; - } - } - i++; } - - for (auto it = rxMissing.begin(); it != rxMissing.end(); ++it) - { - if (it->retransmitCount < 10) - { - missingSeqs.append(it->seqNum & 0xff); - missingSeqs.append(it->seqNum >> 8 & 0xff); - missingSeqs.append(it->seqNum & 0xff); - missingSeqs.append(it->seqNum >> 8 & 0xff); - it->retransmitCount++; - } - } - if (missingSeqs.length() != 0) - { - control_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.type = 0x01; - p.seq = 0x0000; - p.sentid = myId; - p.rcvdid = remoteId; - if (missingSeqs.length() == 4) // This is just a single missing packet so send using a control. - { - p.seq = (missingSeqs[0] &0xff) |(quint16)(missingSeqs[1] << 8) ; - qDebug(logUdp()) << this->metaObject()->className() << ": sending request for missing packet : " << hex << p.seq; - QMutexLocker locker(&mutex); - udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); - } - else - { - qDebug(logUdp()) << this->metaObject()->className() << ": sending request for multiple missing packets : " <writeDatagram(missingSeqs, radioIP, port); - } - } } } } + + +void udpBase::sendRetransmitRequest() +{ + // Find all gaps in received packets and then send requests for them. + // This will run every 100ms so out-of-sequence packets will not trigger a retransmit request. + + QByteArray missingSeqs; + + auto i = std::adjacent_find(rxSeqBuf.begin(), rxSeqBuf.end(), [](quint16 l, quint16 r) {return l + 1 < r; }); + while (i != rxSeqBuf.end()) + { + if (i + 1 != rxSeqBuf.end()) + { + if (*(i + 1) - *i < 30) + { + for (quint16 j = *i + 1; j < *(i + 1); j++) + { + //qDebug(logUdp()) << this->metaObject()->className() << ": Found missing seq between " << *i << " : " << *(i + 1) << " (" << j << ")"; + auto s = std::find_if(rxMissing.begin(), rxMissing.end(), [&cs = j](SEQBUFENTRY& s) { return s.seqNum == cs; }); + if (s == rxMissing.end()) + { + // We haven't seen this missing packet before + //qDebug(logUdp()) << this->metaObject()->className() << ": Adding to missing buffer (len="<< rxMissing.length() << "): " << j; + SEQBUFENTRY b; + b.seqNum = j; + b.retransmitCount = 0; + b.timeSent = QTime::currentTime(); + rxMissing.append(b); + packetsLost++; + } + else { + if (s->retransmitCount == 4) + { + // We have tried 4 times to request this packet, time to give up! + s = rxMissing.erase(s); + rxSeqBuf.append(j); // Final thing is to add to received buffer! + } + + } + } + } + else { + qDebug(logUdp()) << this->metaObject()->className() << ": Too many missing, flushing buffers"; + rxSeqBuf.clear(); + missingSeqs.clear(); + break; + } + } + i++; + } + + + for (auto it = rxMissing.begin(); it != rxMissing.end(); ++it) + { + if (it->retransmitCount < 10) + { + missingSeqs.append(it->seqNum & 0xff); + missingSeqs.append(it->seqNum >> 8 & 0xff); + missingSeqs.append(it->seqNum & 0xff); + missingSeqs.append(it->seqNum >> 8 & 0xff); + it->retransmitCount++; + } + } + if (missingSeqs.length() != 0) + { + control_packet p; + memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! + p.type = 0x01; + p.seq = 0x0000; + p.sentid = myId; + p.rcvdid = remoteId; + if (missingSeqs.length() == 4) // This is just a single missing packet so send using a control. + { + p.seq = (missingSeqs[0] & 0xff) | (quint16)(missingSeqs[1] << 8); + qDebug(logUdp()) << this->metaObject()->className() << ": sending request for missing packet : " << hex << p.seq; + QMutexLocker locker(&mutex); + udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); + } + else + { + qDebug(logUdp()) << this->metaObject()->className() << ": sending request for multiple missing packets : " << missingSeqs.toHex(); + missingSeqs.insert(0, p.packet, sizeof(p.packet)); + QMutexLocker locker(&mutex); + udp->writeDatagram(missingSeqs, radioIP, port); + } + } + +} + + + // Used to send idle and other "control" style messages void udpBase::sendControl(bool tracked=true, quint8 type=0, quint16 seq=0) { diff --git a/udphandler.h b/udphandler.h index 9d01421..96c6823 100644 --- a/udphandler.h +++ b/udphandler.h @@ -31,7 +31,9 @@ #define TXAUDIO_PERIOD 10 #define AREYOUTHERE_PERIOD 500 #define WATCHDOG_PERIOD 500 +#define RETRANSMIT_PERIOD 100 +Q_DECLARE_METATYPE(AUDIOPACKET) void passcode(QString in, QByteArray& out); QByteArray parseNullTerminatedString(QByteArray c, int s); @@ -40,6 +42,7 @@ QByteArray parseNullTerminatedString(QByteArray c, int s); class udpBase : public QObject { + public: ~udpBase(); @@ -98,6 +101,7 @@ public: QTimer* idleTimer = Q_NULLPTR; // Start watchdog once we are connected. QTimer* watchdogTimer = Q_NULLPTR; + QTimer* retransmitTimer = Q_NULLPTR; QDateTime lastPingSentTime; uint16_t pingSendSeq = 0; @@ -107,6 +111,9 @@ public: quint32 packetsSent=0; quint32 packetsLost=0; +private: + void sendRetransmitRequest(); + }; @@ -142,19 +149,19 @@ class udpAudio : public udpBase Q_OBJECT public: - udpAudio(QHostAddress local, QHostAddress ip, quint16 aport, quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec); + udpAudio(QHostAddress local, QHostAddress ip, quint16 aport, quint16 rxlatency, quint16 txlatency, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec); ~udpAudio(); signals: - void haveAudioData(QByteArray data); + void haveAudioData(AUDIOPACKET data); - void setupTxAudio(const quint8 samples, const quint8 channels, const quint16 samplerate, const quint16 bufferSize, const bool isUlaw, const bool isInput); - void setupRxAudio(const quint8 samples, const quint8 channels, const quint16 samplerate, const quint16 bufferSize, const bool isUlaw, const bool isInput); + void setupTxAudio(const quint8 samples, const quint8 channels, const quint16 samplerate, const quint16 latency, const bool isUlaw, const bool isInput); + void setupRxAudio(const quint8 samples, const quint8 channels, const quint16 samplerate, const quint16 latency, const bool isUlaw, const bool isInput); - void haveChangeBufferSize(quint16 value); + void haveChangeLatency(quint16 value); public slots: - void changeBufferSize(quint16 value); + void changeLatency(quint16 value); private: @@ -163,7 +170,8 @@ private: void watchdog(); QAudioFormat format; - quint16 bufferSize; + quint16 rxLatency; + quint16 txLatency; quint16 rxSampleRate; quint16 txSampleRate; quint8 rxCodec; @@ -197,7 +205,7 @@ class udpHandler: public udpBase public: udpHandler(QString ip, quint16 cport, quint16 sport, quint16 aport, QString username, QString password, - quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec); + quint16 rxlatency, quint16 txlatency, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec); ~udpHandler(); bool streamOpened = false; @@ -209,14 +217,14 @@ public: public slots: void receiveDataFromUserToRig(QByteArray); // This slot will send data on to void receiveFromCivStream(QByteArray); - void changeBufferSize(quint16 value); + void changeLatency(quint16 value); void init(); signals: void haveDataFromPort(QByteArray data); // emit this when we have data, connect to rigcommander void haveNetworkError(QString, QString); void haveNetworkStatus(QString); - void haveChangeBufferSize(quint16 value); + void haveChangeLatency(quint16 value); private: @@ -243,7 +251,8 @@ private: quint16 rxSampleRate; quint16 txSampleRate; - quint16 rxBufferSize; + quint16 rxLatency; + quint16 txLatency; quint8 rxCodec; quint8 txCodec; diff --git a/wfmain.cpp b/wfmain.cpp index a284ce4..af42e86 100644 --- a/wfmain.cpp +++ b/wfmain.cpp @@ -533,11 +533,11 @@ void wfmain::openRig() connect(rig, SIGNAL(haveSerialPortError(QString, QString)), this, SLOT(receiveSerialPortError(QString, QString))); connect(rig, SIGNAL(haveStatusUpdate(QString)), this, SLOT(receiveStatusUpdate(QString))); - connect(this, SIGNAL(sendCommSetup(unsigned char, QString, quint16, quint16, quint16, QString, QString,quint16,quint16,quint8,quint16,quint8)), rig, SLOT(commSetup(unsigned char, QString, quint16, quint16, quint16, QString, QString,quint16,quint16,quint8,quint16,quint8))); + connect(this, SIGNAL(sendCommSetup(unsigned char, QString, quint16, quint16, quint16, QString, QString,quint16,quint16,quint16,quint8,quint16,quint8)), rig, SLOT(commSetup(unsigned char, QString, quint16, quint16, quint16, QString, QString,quint16,quint16,quint16,quint8,quint16,quint8))); connect(this, SIGNAL(sendCommSetup(unsigned char, QString, quint32)), rig, SLOT(commSetup(unsigned char, QString, quint32))); connect(this, SIGNAL(sendCloseComm()), rig, SLOT(closeComm())); - connect(this, SIGNAL(sendChangeBufferSize(quint16)), rig, SLOT(changeBufferSize(quint16))); + connect(this, SIGNAL(sendChangeLatency(quint16)), rig, SLOT(changeLatency(quint16))); connect(this, SIGNAL(getRigCIV()), rig, SLOT(findRigs())); connect(rig, SIGNAL(discoveredRigID(rigCapabilities)), this, SLOT(receiveFoundRigID(rigCapabilities))); connect(rig, SIGNAL(commReady()), this, SLOT(receiveCommReady())); @@ -546,7 +546,7 @@ void wfmain::openRig() if (prefs.enableLAN) { emit sendCommSetup(prefs.radioCIVAddr, prefs.ipAddress, prefs.controlLANPort, - prefs.serialLANPort, prefs.audioLANPort, prefs.username, prefs.password,prefs.audioRXBufferSize,prefs.audioRXSampleRate,prefs.audioRXCodec,prefs.audioTXSampleRate,prefs.audioTXCodec); + prefs.serialLANPort, prefs.audioLANPort, prefs.username, prefs.password,prefs.audioRXLatency,prefs.audioTXLatency,prefs.audioRXSampleRate,prefs.audioRXCodec,prefs.audioTXSampleRate,prefs.audioTXCodec); } else { if( (prefs.serialPortRadio == QString("auto")) && (serialPortCL.isEmpty())) @@ -699,7 +699,8 @@ void wfmain::setDefPrefs() defPrefs.password = QString(""); defPrefs.audioOutput = QAudioDeviceInfo::defaultOutputDevice().deviceName(); defPrefs.audioInput = QAudioDeviceInfo::defaultInputDevice().deviceName(); - defPrefs.audioRXBufferSize = 12000; + defPrefs.audioRXLatency = 150; + defPrefs.audioTXLatency = 150; defPrefs.audioRXSampleRate = 48000; defPrefs.audioRXCodec = 4; defPrefs.audioTXSampleRate = 48000; @@ -769,10 +770,15 @@ void wfmain::loadSettings() ui->passwordTxt->setEnabled(ui->lanEnableChk->isChecked()); ui->passwordTxt->setText(QString("%1").arg(prefs.password)); - prefs.audioRXBufferSize = settings.value("AudioRXBufferSize", defPrefs.audioRXBufferSize).toInt(); - ui->audioBufferSizeSlider->setEnabled(ui->lanEnableChk->isChecked()); - ui->audioBufferSizeSlider->setValue(prefs.audioRXBufferSize); - ui->audioBufferSizeSlider->setTracking(false); // Stop it sending value on every change. + prefs.audioRXLatency = settings.value("AudioRXLatency", defPrefs.audioRXLatency).toInt(); + ui->rxLatencySlider->setEnabled(ui->lanEnableChk->isChecked()); + ui->txLatencySlider->setValue(prefs.audioRXLatency); + ui->rxLatencySlider->setTracking(false); // Stop it sending value on every change. + + prefs.audioTXLatency = settings.value("AudioTXLatency", defPrefs.audioTXLatency).toInt(); + ui->rxLatencySlider->setEnabled(ui->lanEnableChk->isChecked()); + ui->rxLatencySlider->setValue(prefs.audioTXLatency); + ui->rxLatencySlider->setTracking(false); // Stop it sending value on every change. prefs.audioRXSampleRate = settings.value("AudioRXSampleRate", defPrefs.audioRXSampleRate).toInt(); prefs.audioTXSampleRate = settings.value("AudioTXSampleRate", defPrefs.audioTXSampleRate).toInt(); @@ -917,10 +923,10 @@ void wfmain::saveSettings() settings.setValue("AudioLANPort", prefs.audioLANPort); settings.setValue("Username", prefs.username); settings.setValue("Password", prefs.password); - settings.setValue("AudioRXBufferSize", prefs.audioRXBufferSize); + settings.setValue("AudioRXLatency", prefs.audioRXLatency); + settings.setValue("AudioTXLatency", prefs.audioTXLatency); settings.setValue("AudioRXSampleRate", prefs.audioRXSampleRate); settings.setValue("AudioRXCodec", prefs.audioRXCodec); - settings.setValue("AudioTXBufferSize", prefs.audioRXBufferSize); settings.setValue("AudioTXSampleRate", prefs.audioRXSampleRate); settings.setValue("AudioTXCodec", prefs.audioTXCodec); settings.setValue("AudioOutput", prefs.audioOutput); @@ -2866,11 +2872,17 @@ void wfmain::on_audioTXCodecCombo_currentIndexChanged(int value) prefs.audioTXCodec = ui->audioTXCodecCombo->itemData(value).toInt(); } -void wfmain::on_audioBufferSizeSlider_valueChanged(int value) +void wfmain::on_rxLatencySlider_valueChanged(int value) { - prefs.audioRXBufferSize = value; - ui->bufferValue->setText(QString::number(value)); - emit sendChangeBufferSize(value); + prefs.audioRXLatency = value; + ui->rxLatencyValue->setText(QString::number(value)); + emit sendChangeLatency(value); +} + +void wfmain::on_txLatencySlider_valueChanged(int value) +{ + prefs.audioTXLatency = value; + ui->txLatencyValue->setText(QString::number(value)); } void wfmain::on_toFixedBtn_clicked() diff --git a/wfmain.h b/wfmain.h index 8af61a3..0a14587 100644 --- a/wfmain.h +++ b/wfmain.h @@ -108,9 +108,9 @@ signals: void sayAll(); void sendCommSetup(unsigned char rigCivAddr, QString rigSerialPort, quint32 rigBaudRate); void sendCommSetup(unsigned char rigCivAddr, QString ip, quint16 cport, quint16 sport, quint16 aport, - QString username, QString password, quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec); + QString username, QString password, quint16 rxlatency, quint16 txlatency, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec); void sendCloseComm(); - void sendChangeBufferSize(quint16 value); + void sendChangeLatency(quint16 latency); void initServer(); void sendServerConfig(SERVERCONFIG conf); @@ -329,7 +329,9 @@ private slots: void on_connectBtn_clicked(); - void on_audioBufferSizeSlider_valueChanged(int value); + void on_rxLatencySlider_valueChanged(int value); + + void on_txLatencySlider_valueChanged(int value); void on_audioRXCodecCombo_currentIndexChanged(int value); @@ -527,7 +529,8 @@ private: QString password; QString audioOutput; QString audioInput; - quint16 audioRXBufferSize; + quint16 audioRXLatency; + quint16 audioTXLatency; quint16 audioRXSampleRate; quint8 audioRXCodec; quint16 audioTXSampleRate; diff --git a/wfmain.ui b/wfmain.ui index f254078..2307656 100644 --- a/wfmain.ui +++ b/wfmain.ui @@ -18,7 +18,7 @@ - 0 + 3 @@ -1765,20 +1765,23 @@ - RX Audio Buffer Size + RX Latency (ms) - + 0 0 + + 30 + - 65536 + 500 Qt::Horizontal @@ -1786,7 +1789,34 @@ - + + + 0 + + + + + + + TX Latency (ms) + + + + + + + 30 + + + 500 + + + Qt::Horizontal + + + + + 0 @@ -1948,7 +1978,7 @@ 0 0 810 - 22 + 21