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.
merge-requests/2/head
Phil Taylor 2021-02-27 00:37:00 +00:00
rodzic 6d8d1df45e
commit 27eb855adb
9 zmienionych plików z 346 dodań i 215 usunięć

Wyświetl plik

@ -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<qint16>(ulaw_decode[(quint8)buffer.at(f)], data + (f * 2));
else
qToLittleEndian<qint16>((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() && sentlen<maxlen)
{
if (packet->time.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<qint16>(ulaw_decode[(quint8)packet->data[f+packet->sent]], data + (f * 2 + sentlen));
else
qToLittleEndian<qint16>((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()

Wyświetl plik

@ -14,20 +14,31 @@
#include <QIODevice>
#include <QThread>
#include <QTimer>
#include <QTime>
#include <QDebug>
//#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<AUDIOPACKET> audioBuffer;
};
#endif // AUDIOHANDLER_H

Wyświetl plik

@ -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()

Wyświetl plik

@ -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();

Wyświetl plik

@ -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<AUDIOPACKET>();
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 : " <<missingSeqs.toHex();
missingSeqs.insert(0, p.packet, sizeof(p.packet));
QMutexLocker locker(&mutex);
udp->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)
{

Wyświetl plik

@ -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;

Wyświetl plik

@ -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()

Wyświetl plik

@ -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;

Wyświetl plik

@ -18,7 +18,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
<number>3</number>
</property>
<widget class="QWidget" name="mainTab">
<attribute name="title">
@ -1765,20 +1765,23 @@
<item>
<widget class="QLabel" name="label_16">
<property name="text">
<string>RX Audio Buffer Size</string>
<string>RX Latency (ms)</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="audioBufferSizeSlider">
<widget class="QSlider" name="rxLatencySlider">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<number>30</number>
</property>
<property name="maximum">
<number>65536</number>
<number>500</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -1786,7 +1789,34 @@
</widget>
</item>
<item>
<widget class="QLabel" name="bufferValue">
<widget class="QLabel" name="rxLatencyValue">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_24">
<property name="text">
<string>TX Latency (ms)</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="txLatencySlider">
<property name="minimum">
<number>30</number>
</property>
<property name="maximum">
<number>500</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="txLatencyValue">
<property name="text">
<string>0</string>
</property>
@ -1948,7 +1978,7 @@
<x>0</x>
<y>0</y>
<width>810</width>
<height>22</height>
<height>21</height>
</rect>
</property>
</widget>