From 50c8b4e5455d462e6a7b5e571c4e854d9f6f1f2f Mon Sep 17 00:00:00 2001 From: Phil Taylor Date: Sun, 10 Apr 2022 23:13:51 +0100 Subject: [PATCH] Change audio output to use single/slot --- audiohandler.cpp | 35 +++++++++----- audiohandler.h | 11 +++-- udphandler.cpp | 108 ++++++++++++++++++------------------------- udphandler.h | 3 +- udpserver.cpp | 118 ++++++++++++++++++++--------------------------- udpserver.h | 2 +- 6 files changed, 127 insertions(+), 150 deletions(-) diff --git a/audiohandler.cpp b/audiohandler.cpp index 7996808..2008b7e 100644 --- a/audiohandler.cpp +++ b/audiohandler.cpp @@ -31,6 +31,12 @@ audioHandler::~audioHandler() audioOutput = Q_NULLPTR; } + if (audioTimer != Q_NULLPTR) { + audioTimer->stop(); + delete audioTimer; + audioTimer = Q_NULLPTR; + } + if (resampler != Q_NULLPTR) { speex_resampler_destroy(resampler); @@ -128,6 +134,11 @@ bool audioHandler::init(audioSetup setupIn) if (setup.isinput) { audioInput = new QAudioInput(setup.port, format, this); + qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Starting audio timer"; + audioTimer = new QTimer(); + audioTimer->setTimerType(Qt::PreciseTimer); + connect(audioTimer, &QTimer::timeout, this, &audioHandler::getNextAudioChunk); + connect(audioInput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State))); } else { @@ -186,6 +197,7 @@ void audioHandler::start() if (setup.isinput) { audioDevice = audioInput->start(); connect(audioInput, &QAudioOutput::destroyed, audioDevice, &QIODevice::deleteLater, Qt::UniqueConnection); + audioTimer->start(setup.blockSize); } else { // Buffer size must be set before audio is started. @@ -394,19 +406,17 @@ void audioHandler::incomingAudio(audioPacket inPacket) } -void audioHandler::getNextAudioChunk(QByteArray& ret) +void audioHandler::getNextAudioChunk() { audioPacket livePacket; + livePacket.time= QTime::currentTime(); livePacket.sent = 0; - // Don't start sending until we have at least 1/2 of setup.latency of audio buffered - // For some reason the audioDevice->bytesAvailable() function always returns 0? - if (audioInput != Q_NULLPTR && audioDevice != Q_NULLPTR && audioInput->bytesReady() > format.bytesForDuration(setup.latency)) { - if (setup.codec == 0x40 || setup.codec == 0x80) { - livePacket.data = audioDevice->read(format.bytesForDuration(20000)); // 20000uS is 20ms in NATIVE format. - } - else { - livePacket.data = audioDevice->readAll(); // 20000uS is 20ms in NATIVE format. - } + memcpy(&livePacket.guid, setup.guid, GUIDLEN); + + if (audioInput != Q_NULLPTR && audioDevice != Q_NULLPTR) { + + livePacket.data = audioDevice->readAll(); + if (livePacket.data.length() > 0) { Eigen::VectorXf samplesF; @@ -555,12 +565,13 @@ void audioHandler::getNextAudioChunk(QByteArray& ret) livePacket.data.clear(); livePacket.data = outPacket; // Copy output packet back to input buffer. } - ret = livePacket.data; + emit haveAudioData(livePacket); + //ret = livePacket.data; } } + emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun); } - emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun); return; diff --git a/audiohandler.h b/audiohandler.h index 31675d3..128b270 100644 --- a/audiohandler.h +++ b/audiohandler.h @@ -69,6 +69,8 @@ struct audioSetup { QAudioDeviceInfo port; quint8 resampleQuality; unsigned char localAFgain; + quint16 blockSize=20; // Each 'block' of audio is 20ms long by default. + quint8 guid[GUIDLEN]; }; // For QtMultimedia, use a native QIODevice @@ -85,7 +87,6 @@ public: void start(); void stop(); - void getNextAudioChunk(QByteArray &data); quint16 getAmplitude(); public slots: @@ -96,14 +97,14 @@ public slots: private slots: void stateChanged(QAudio::State state); - void clearUnderrun(); + void clearUnderrun(); + void getNextAudioChunk(); signals: void audioMessage(QString message); void sendLatency(quint16 newSize); - void haveAudioData(const QByteArray& data); + void haveAudioData(const audioPacket& data); void haveLevels(quint16 amplitude,quint16 latency,quint16 current,bool under); - private: bool isUnderrun = false; @@ -114,9 +115,9 @@ private: QAudioOutput* audioOutput=Q_NULLPTR; QAudioInput* audioInput=Q_NULLPTR; QIODevice* audioDevice=Q_NULLPTR; + QTimer* audioTimer = Q_NULLPTR; QAudioFormat format; QAudioDeviceInfo deviceInfo; - SpeexResamplerState* resampler = Q_NULLPTR; //r8b::CFixedBuffer* resampBufs; diff --git a/udphandler.cpp b/udphandler.cpp index 2854ca9..25f4cee 100644 --- a/udphandler.cpp +++ b/udphandler.cpp @@ -826,17 +826,7 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint txSetup.format.setChannelCount(1); // TX Audio is always single channel. - txaudio = new audioHandler(); - txAudioThread = new QThread(this); - txaudio->moveToThread(txAudioThread); - - txAudioThread->start(QThread::TimeCriticalPriority); - - connect(this, SIGNAL(setupTxAudio(audioSetup)), txaudio, SLOT(init(audioSetup))); - connect(txaudio, SIGNAL(haveLevels(quint16, quint16, quint16, bool)), this, SLOT(getTxLevels(quint16, quint16, quint16, bool))); - - connect(txAudioThread, SIGNAL(finished()), txaudio, SLOT(deleteLater())); sendControl(false, 0x03, 0x00); // First connect packet @@ -845,6 +835,18 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint pingTimer->start(PING_PERIOD); // send ping packets every 100ms if (enableTx) { + txaudio = new audioHandler(); + txAudioThread = new QThread(this); + + txaudio->moveToThread(txAudioThread); + + txAudioThread->start(QThread::TimeCriticalPriority); + + connect(this, SIGNAL(setupTxAudio(audioSetup)), txaudio, SLOT(init(audioSetup))); + connect(txaudio, SIGNAL(haveAudioData(audioPacket)), this, SLOT(receiveAudioData(audioPacket))); + connect(txaudio, SIGNAL(haveLevels(quint16, quint16, quint16, bool)), this, SLOT(getTxLevels(quint16, quint16, quint16, bool))); + + connect(txAudioThread, SIGNAL(finished()), txaudio, SLOT(deleteLater())); emit setupTxAudio(txSetup); } @@ -854,10 +856,6 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint connect(watchdogTimer, &QTimer::timeout, this, &udpAudio::watchdog); watchdogTimer->start(WATCHDOG_PERIOD); - txAudioTimer = new QTimer(); - txAudioTimer->setTimerType(Qt::PreciseTimer); - connect(txAudioTimer, &QTimer::timeout, this, &udpAudio::sendTxAudio); - areYouThereTimer = new QTimer(); connect(areYouThereTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, false, 0x03, 0)); areYouThereTimer->start(AREYOUTHERE_PERIOD); @@ -889,13 +887,6 @@ udpAudio::~udpAudio() watchdogTimer = Q_NULLPTR; } - if (txAudioTimer != Q_NULLPTR) - { - qDebug(logUdp()) << "Stopping txaudio timer"; - txAudioTimer->stop(); - delete txAudioTimer; - } - if (rxAudioThread != Q_NULLPTR) { qDebug(logUdp()) << "Stopping rxaudio thread"; rxAudioThread->quit(); @@ -935,44 +926,41 @@ void udpAudio::sendTxAudio() if (txaudio == Q_NULLPTR) { return; } - QByteArray audio; - if (audioMutex.try_lock_for(std::chrono::milliseconds(LOCK_PERIOD))) - { - txaudio->getNextAudioChunk(audio); - // Now we have the next audio chunk, we can release the mutex. - audioMutex.unlock(); - if (audio.length() > 0) { - int counter = 1; - int len = 0; +} - while (len < audio.length()) { - QByteArray partial = audio.mid(len, 1364); - audio_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.len = sizeof(p) + partial.length(); - p.sentid = myId; - p.rcvdid = remoteId; - if (partial.length() == 0xa0) { - p.ident = 0x9781; - } - else { - p.ident = 0x0080; // TX audio is always this? - } - p.datalen = (quint16)qToBigEndian((quint16)partial.length()); - p.sendseq = (quint16)qToBigEndian((quint16)sendAudioSeq); // THIS IS BIG ENDIAN! - QByteArray tx = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); - tx.append(partial); - len = len + partial.length(); - //qInfo(logUdp()) << "Sending audio packet length: " << tx.length(); - sendTrackedPacket(tx); - sendAudioSeq++; - counter++; - } - } +void udpAudio::receiveAudioData(audioPacket audio) { + // I really can't see how this could be possible but a quick sanity check! + if (txaudio == Q_NULLPTR) { + return; } - else { - qInfo(logUdpServer()) << "Unable to lock mutex for rxaudio"; + if (audio.data.length() > 0) { + int counter = 1; + int len = 0; + + while (len < audio.data.length()) { + QByteArray partial = audio.data.mid(len, 1364); + audio_packet p; + memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! + p.len = sizeof(p) + partial.length(); + p.sentid = myId; + p.rcvdid = remoteId; + if (partial.length() == 0xa0) { + p.ident = 0x9781; + } + else { + p.ident = 0x0080; // TX audio is always this? + } + p.datalen = (quint16)qToBigEndian((quint16)partial.length()); + p.sendseq = (quint16)qToBigEndian((quint16)sendAudioSeq); // THIS IS BIG ENDIAN! + QByteArray tx = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); + tx.append(partial); + len = len + partial.length(); + //qInfo(logUdp()) << "Sending audio packet length: " << tx.length(); + sendTrackedPacket(tx); + sendAudioSeq++; + counter++; + } } } @@ -1006,12 +994,7 @@ void udpAudio::dataReceived() { case (16): // Response to control packet handled in udpBase { - control_packet_t in = (control_packet_t)r.constData(); - if (in->type == 0x04 && enableTx) - { - txAudioTimer->start(AUDIO_PERIOD); - } - + //control_packet_t in = (control_packet_t)r.constData(); break; } default: @@ -1024,7 +1007,6 @@ void udpAudio::dataReceived() */ control_packet_t in = (control_packet_t)r.constData(); - if (in->type != 0x01 && in->len >= 0x20) { if (in->seq == 0) diff --git a/udphandler.h b/udphandler.h index 30757d3..3731bc1 100644 --- a/udphandler.h +++ b/udphandler.h @@ -196,6 +196,7 @@ public slots: void setVolume(unsigned char value); void getRxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); void getTxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); + void receiveAudioData(audioPacket audio); private: @@ -211,7 +212,7 @@ private: audioHandler* txaudio = Q_NULLPTR; QThread* txAudioThread = Q_NULLPTR; - QTimer* txAudioTimer=Q_NULLPTR; + QTimer* txAudioTimer = Q_NULLPTR; bool enableTx = true; QMutex audioMutex; diff --git a/udpserver.cpp b/udpserver.cpp index 9c3fd7d..a92f218 100644 --- a/udpserver.cpp +++ b/udpserver.cpp @@ -353,6 +353,7 @@ void udpServer::controlReceived() radio->txAudioSetup.format.setSampleRate(current->txSampleRate); radio->txAudioSetup.isinput = false; radio->txAudioSetup.latency = current->txBufferLen; + outAudio.isinput = false; radio->txaudio = new audioHandler(); @@ -393,6 +394,7 @@ void udpServer::controlReceived() radio->rxAudioSetup.format.setSampleRate(current->rxSampleRate); radio->rxAudioSetup.latency = current->txBufferLen; radio->rxAudioSetup.isinput = true; + memcpy(radio->rxAudioSetup.guid, radio->guid, GUIDLEN); radio->rxaudio = new audioHandler(); @@ -404,6 +406,7 @@ void udpServer::controlReceived() connect(this, SIGNAL(setupRxAudio(audioSetup)), radio->rxaudio, SLOT(init(audioSetup))); 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(5,10,0)) QMetaObject::invokeMethod(radio->rxaudio, [=]() { @@ -414,10 +417,6 @@ void udpServer::controlReceived() setupRxAudio(radio->rxAudioSetup); #endif - radio->rxAudioTimer = new QTimer(); - radio->rxAudioTimer->setTimerType(Qt::PreciseTimer); - connect(radio->rxAudioTimer, &QTimer::timeout, this, std::bind(&udpServer::sendRxAudio, this)); - radio->rxAudioTimer->start(AUDIO_PERIOD); } } @@ -1596,30 +1595,12 @@ void udpServer::dataForServer(QByteArray d) return; } -void udpServer::sendRxAudio() +void udpServer::sendRxAudio(const audioPacket& audio) { - QByteArray audio; + audioHandler* sender = qobject_cast(QObject::sender()); for (RIGCONFIG* rig : config.rigs) { - if (rig->rxaudio != Q_NULLPTR) { - if (audioMutex.try_lock_for(std::chrono::milliseconds(LOCK_PERIOD))) - { - audio.clear(); - rig->rxaudio->getNextAudioChunk(audio); - int len = 0; - while (len < audio.length()) { - audioPacket partial; - memcpy(partial.guid, rig->guid, GUIDLEN); - partial.data = audio.mid(len, 1364); - receiveAudioData(partial); - len = len + partial.data.length(); - } - // Now we have the next audio chunk, we can release the mutex. - audioMutex.unlock(); - } - else { - qInfo(logUdpServer()) << "Unable to lock mutex for rxaudio"; - } + if (sender != Q_NULLPTR && rig->rxaudio == sender) { } } } @@ -1637,52 +1618,58 @@ void udpServer::receiveAudioData(const audioPacket& d) else { memcpy(guid, d.guid, GUIDLEN); } - //qInfo(logUdpServer()) << "Server got:" << d.data.length(); +//qInfo(logUdpServer()) << "Server got:" << d.data.length(); foreach(CLIENT * client, audioClients) { - if (client != Q_NULLPTR && client->connected && !memcmp(client->guid,guid, GUIDLEN)) { - audio_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.len = sizeof(p) + d.data.length(); - p.sentid = client->myId; - p.rcvdid = client->remoteId; - p.ident = 0x0080; // audio is always this? - p.datalen = (quint16)qToBigEndian((quint16)d.data.length()); - p.sendseq = (quint16)qToBigEndian((quint16)client->sendAudioSeq); // THIS IS BIG ENDIAN! - p.seq = client->txSeq; - QByteArray t = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); - t.append(d.data); + int len = 0; + while (len < d.data.length()) { + QByteArray partial; + partial = d.data.mid(len, 1364); + len = len + partial.length(); + if (client != Q_NULLPTR && client->connected && !memcmp(client->guid, guid, GUIDLEN)) { + audio_packet p; + memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! + p.len = sizeof(p) + partial.length(); + p.sentid = client->myId; + p.rcvdid = client->remoteId; + p.ident = 0x0080; // audio is always this? + p.datalen = (quint16)qToBigEndian((quint16)partial.length()); + p.sendseq = (quint16)qToBigEndian((quint16)client->sendAudioSeq); // THIS IS BIG ENDIAN! + p.seq = client->txSeq; + QByteArray t = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); + t.append(d.data); - SEQBUFENTRY s; - s.seqNum = p.seq; - s.timeSent = QTime::currentTime(); - s.retransmitCount = 0; - s.data = t; - if (client->txMutex.try_lock_for(std::chrono::milliseconds(LOCK_PERIOD))) - { - if (client->txSeqBuf.size() > BUFSIZE) + SEQBUFENTRY s; + s.seqNum = p.seq; + s.timeSent = QTime::currentTime(); + s.retransmitCount = 0; + s.data = t; + if (client->txMutex.try_lock_for(std::chrono::milliseconds(LOCK_PERIOD))) { - client->txSeqBuf.remove(client->txSeqBuf.firstKey()); + if (client->txSeqBuf.size() > BUFSIZE) + { + client->txSeqBuf.remove(client->txSeqBuf.firstKey()); + } + client->txSeqBuf.insert(p.seq, s); + client->txSeq++; + client->sendAudioSeq++; + client->txMutex.unlock(); + } + else { + qInfo(logUdpServer()) << "Unable to lock txMutex()"; } - client->txSeqBuf.insert(p.seq, s); - client->txSeq++; - client->sendAudioSeq++; - client->txMutex.unlock(); - } - else { - qInfo(logUdpServer()) << "Unable to lock txMutex()"; - } - if (udpMutex.try_lock_for(std::chrono::milliseconds(LOCK_PERIOD))) - { - client->socket->writeDatagram(t, client->ipAddress, client->port); - udpMutex.unlock(); - } - else { - qInfo(logUdpServer()) << "Unable to lock udpMutex()"; - } + if (udpMutex.try_lock_for(std::chrono::milliseconds(LOCK_PERIOD))) + { + client->socket->writeDatagram(t, client->ipAddress, client->port); + udpMutex.unlock(); + } + else { + qInfo(logUdpServer()) << "Unable to lock udpMutex()"; + } + } } } @@ -1871,11 +1858,6 @@ void udpServer::deleteConnection(QList* l, CLIENT* c) for (RIGCONFIG* radio : config.rigs) { if (!memcmp(radio->guid, guid, GUIDLEN)) { - if (radio->rxAudioTimer != Q_NULLPTR) { - radio->rxAudioTimer->stop(); - delete radio->rxAudioTimer; - radio->rxAudioTimer = Q_NULLPTR; - } if (radio->rxAudioThread != Q_NULLPTR) { radio->rxAudioThread->quit(); diff --git a/udpserver.h b/udpserver.h index ce5fa88..70ec542 100644 --- a/udpserver.h +++ b/udpserver.h @@ -96,6 +96,7 @@ public slots: void dataForServer(QByteArray); void receiveAudioData(const audioPacket &data); void receiveRigCaps(rigCapabilities caps); + void sendRxAudio(const audioPacket &audio); signals: void haveDataFromServer(QByteArray); @@ -184,7 +185,6 @@ private: void sendStatus(CLIENT* c); void sendRetransmitRequest(CLIENT* c); void watchdog(); - void sendRxAudio(); void deleteConnection(QList *l, CLIENT* c); SERVERCONFIG config;