Change audio output to use single/slot

merge-requests/9/merge
Phil Taylor 2022-04-10 23:13:51 +01:00
rodzic a108f1ca99
commit 50c8b4e545
6 zmienionych plików z 127 dodań i 150 usunięć

Wyświetl plik

@ -31,6 +31,12 @@ audioHandler::~audioHandler()
audioOutput = Q_NULLPTR; audioOutput = Q_NULLPTR;
} }
if (audioTimer != Q_NULLPTR) {
audioTimer->stop();
delete audioTimer;
audioTimer = Q_NULLPTR;
}
if (resampler != Q_NULLPTR) { if (resampler != Q_NULLPTR) {
speex_resampler_destroy(resampler); speex_resampler_destroy(resampler);
@ -128,6 +134,11 @@ bool audioHandler::init(audioSetup setupIn)
if (setup.isinput) { if (setup.isinput) {
audioInput = new QAudioInput(setup.port, format, this); 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))); connect(audioInput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State)));
} }
else { else {
@ -186,6 +197,7 @@ void audioHandler::start()
if (setup.isinput) { if (setup.isinput) {
audioDevice = audioInput->start(); audioDevice = audioInput->start();
connect(audioInput, &QAudioOutput::destroyed, audioDevice, &QIODevice::deleteLater, Qt::UniqueConnection); connect(audioInput, &QAudioOutput::destroyed, audioDevice, &QIODevice::deleteLater, Qt::UniqueConnection);
audioTimer->start(setup.blockSize);
} }
else { else {
// Buffer size must be set before audio is started. // 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; audioPacket livePacket;
livePacket.time= QTime::currentTime();
livePacket.sent = 0; livePacket.sent = 0;
// Don't start sending until we have at least 1/2 of setup.latency of audio buffered memcpy(&livePacket.guid, setup.guid, GUIDLEN);
// For some reason the audioDevice->bytesAvailable() function always returns 0?
if (audioInput != Q_NULLPTR && audioDevice != Q_NULLPTR && audioInput->bytesReady() > format.bytesForDuration(setup.latency)) { if (audioInput != Q_NULLPTR && audioDevice != Q_NULLPTR) {
if (setup.codec == 0x40 || setup.codec == 0x80) {
livePacket.data = audioDevice->read(format.bytesForDuration(20000)); // 20000uS is 20ms in NATIVE format. livePacket.data = audioDevice->readAll();
}
else {
livePacket.data = audioDevice->readAll(); // 20000uS is 20ms in NATIVE format.
}
if (livePacket.data.length() > 0) if (livePacket.data.length() > 0)
{ {
Eigen::VectorXf samplesF; Eigen::VectorXf samplesF;
@ -555,12 +565,13 @@ void audioHandler::getNextAudioChunk(QByteArray& ret)
livePacket.data.clear(); livePacket.data.clear();
livePacket.data = outPacket; // Copy output packet back to input buffer. 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; return;

Wyświetl plik

@ -69,6 +69,8 @@ struct audioSetup {
QAudioDeviceInfo port; QAudioDeviceInfo port;
quint8 resampleQuality; quint8 resampleQuality;
unsigned char localAFgain; unsigned char localAFgain;
quint16 blockSize=20; // Each 'block' of audio is 20ms long by default.
quint8 guid[GUIDLEN];
}; };
// For QtMultimedia, use a native QIODevice // For QtMultimedia, use a native QIODevice
@ -85,7 +87,6 @@ public:
void start(); void start();
void stop(); void stop();
void getNextAudioChunk(QByteArray &data);
quint16 getAmplitude(); quint16 getAmplitude();
public slots: public slots:
@ -96,14 +97,14 @@ public slots:
private slots: private slots:
void stateChanged(QAudio::State state); void stateChanged(QAudio::State state);
void clearUnderrun(); void clearUnderrun();
void getNextAudioChunk();
signals: signals:
void audioMessage(QString message); void audioMessage(QString message);
void sendLatency(quint16 newSize); void sendLatency(quint16 newSize);
void haveAudioData(const QByteArray& data); void haveAudioData(const audioPacket& data);
void haveLevels(quint16 amplitude,quint16 latency,quint16 current,bool under); void haveLevels(quint16 amplitude,quint16 latency,quint16 current,bool under);
private: private:
bool isUnderrun = false; bool isUnderrun = false;
@ -114,9 +115,9 @@ private:
QAudioOutput* audioOutput=Q_NULLPTR; QAudioOutput* audioOutput=Q_NULLPTR;
QAudioInput* audioInput=Q_NULLPTR; QAudioInput* audioInput=Q_NULLPTR;
QIODevice* audioDevice=Q_NULLPTR; QIODevice* audioDevice=Q_NULLPTR;
QTimer* audioTimer = Q_NULLPTR;
QAudioFormat format; QAudioFormat format;
QAudioDeviceInfo deviceInfo; QAudioDeviceInfo deviceInfo;
SpeexResamplerState* resampler = Q_NULLPTR; SpeexResamplerState* resampler = Q_NULLPTR;
//r8b::CFixedBuffer<double>* resampBufs; //r8b::CFixedBuffer<double>* resampBufs;

Wyświetl plik

@ -826,17 +826,7 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint
txSetup.format.setChannelCount(1); // TX Audio is always single channel. 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 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 pingTimer->start(PING_PERIOD); // send ping packets every 100ms
if (enableTx) { 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); emit setupTxAudio(txSetup);
} }
@ -854,10 +856,6 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint
connect(watchdogTimer, &QTimer::timeout, this, &udpAudio::watchdog); connect(watchdogTimer, &QTimer::timeout, this, &udpAudio::watchdog);
watchdogTimer->start(WATCHDOG_PERIOD); watchdogTimer->start(WATCHDOG_PERIOD);
txAudioTimer = new QTimer();
txAudioTimer->setTimerType(Qt::PreciseTimer);
connect(txAudioTimer, &QTimer::timeout, this, &udpAudio::sendTxAudio);
areYouThereTimer = new QTimer(); areYouThereTimer = new QTimer();
connect(areYouThereTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, false, 0x03, 0)); connect(areYouThereTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, false, 0x03, 0));
areYouThereTimer->start(AREYOUTHERE_PERIOD); areYouThereTimer->start(AREYOUTHERE_PERIOD);
@ -889,13 +887,6 @@ udpAudio::~udpAudio()
watchdogTimer = Q_NULLPTR; watchdogTimer = Q_NULLPTR;
} }
if (txAudioTimer != Q_NULLPTR)
{
qDebug(logUdp()) << "Stopping txaudio timer";
txAudioTimer->stop();
delete txAudioTimer;
}
if (rxAudioThread != Q_NULLPTR) { if (rxAudioThread != Q_NULLPTR) {
qDebug(logUdp()) << "Stopping rxaudio thread"; qDebug(logUdp()) << "Stopping rxaudio thread";
rxAudioThread->quit(); rxAudioThread->quit();
@ -935,44 +926,41 @@ void udpAudio::sendTxAudio()
if (txaudio == Q_NULLPTR) { if (txaudio == Q_NULLPTR) {
return; 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()) { void udpAudio::receiveAudioData(audioPacket audio) {
QByteArray partial = audio.mid(len, 1364); // I really can't see how this could be possible but a quick sanity check!
audio_packet p; if (txaudio == Q_NULLPTR) {
memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! return;
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++;
}
}
} }
else { if (audio.data.length() > 0) {
qInfo(logUdpServer()) << "Unable to lock mutex for rxaudio"; 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 case (16): // Response to control packet handled in udpBase
{ {
control_packet_t in = (control_packet_t)r.constData(); //control_packet_t in = (control_packet_t)r.constData();
if (in->type == 0x04 && enableTx)
{
txAudioTimer->start(AUDIO_PERIOD);
}
break; break;
} }
default: default:
@ -1024,7 +1007,6 @@ void udpAudio::dataReceived()
*/ */
control_packet_t in = (control_packet_t)r.constData(); control_packet_t in = (control_packet_t)r.constData();
if (in->type != 0x01 && in->len >= 0x20) { if (in->type != 0x01 && in->len >= 0x20) {
if (in->seq == 0) if (in->seq == 0)

Wyświetl plik

@ -196,6 +196,7 @@ public slots:
void setVolume(unsigned char value); void setVolume(unsigned char value);
void getRxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); void getRxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under);
void getTxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); void getTxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under);
void receiveAudioData(audioPacket audio);
private: private:
@ -211,7 +212,7 @@ private:
audioHandler* txaudio = Q_NULLPTR; audioHandler* txaudio = Q_NULLPTR;
QThread* txAudioThread = Q_NULLPTR; QThread* txAudioThread = Q_NULLPTR;
QTimer* txAudioTimer=Q_NULLPTR; QTimer* txAudioTimer = Q_NULLPTR;
bool enableTx = true; bool enableTx = true;
QMutex audioMutex; QMutex audioMutex;

Wyświetl plik

@ -353,6 +353,7 @@ void udpServer::controlReceived()
radio->txAudioSetup.format.setSampleRate(current->txSampleRate); radio->txAudioSetup.format.setSampleRate(current->txSampleRate);
radio->txAudioSetup.isinput = false; radio->txAudioSetup.isinput = false;
radio->txAudioSetup.latency = current->txBufferLen; radio->txAudioSetup.latency = current->txBufferLen;
outAudio.isinput = false; outAudio.isinput = false;
radio->txaudio = new audioHandler(); radio->txaudio = new audioHandler();
@ -393,6 +394,7 @@ void udpServer::controlReceived()
radio->rxAudioSetup.format.setSampleRate(current->rxSampleRate); radio->rxAudioSetup.format.setSampleRate(current->rxSampleRate);
radio->rxAudioSetup.latency = current->txBufferLen; radio->rxAudioSetup.latency = current->txBufferLen;
radio->rxAudioSetup.isinput = true; radio->rxAudioSetup.isinput = true;
memcpy(radio->rxAudioSetup.guid, radio->guid, GUIDLEN);
radio->rxaudio = new audioHandler(); radio->rxaudio = new audioHandler();
@ -404,6 +406,7 @@ void udpServer::controlReceived()
connect(this, SIGNAL(setupRxAudio(audioSetup)), radio->rxaudio, SLOT(init(audioSetup))); connect(this, SIGNAL(setupRxAudio(audioSetup)), radio->rxaudio, SLOT(init(audioSetup)));
connect(radio->rxAudioThread, SIGNAL(finished()), radio->rxaudio, SLOT(deleteLater())); 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)) #if (QT_VERSION >= QT_VERSION_CHECK(5,10,0))
QMetaObject::invokeMethod(radio->rxaudio, [=]() { QMetaObject::invokeMethod(radio->rxaudio, [=]() {
@ -414,10 +417,6 @@ void udpServer::controlReceived()
setupRxAudio(radio->rxAudioSetup); setupRxAudio(radio->rxAudioSetup);
#endif #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; return;
} }
void udpServer::sendRxAudio() void udpServer::sendRxAudio(const audioPacket& audio)
{ {
QByteArray audio; audioHandler* sender = qobject_cast<audioHandler*>(QObject::sender());
for (RIGCONFIG* rig : config.rigs) { for (RIGCONFIG* rig : config.rigs) {
if (rig->rxaudio != Q_NULLPTR) { if (sender != Q_NULLPTR && rig->rxaudio == sender) {
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";
}
} }
} }
} }
@ -1637,52 +1618,58 @@ void udpServer::receiveAudioData(const audioPacket& d)
else { else {
memcpy(guid, d.guid, GUIDLEN); memcpy(guid, d.guid, GUIDLEN);
} }
//qInfo(logUdpServer()) << "Server got:" << d.data.length(); //qInfo(logUdpServer()) << "Server got:" << d.data.length();
foreach(CLIENT * client, audioClients) foreach(CLIENT * client, audioClients)
{ {
if (client != Q_NULLPTR && client->connected && !memcmp(client->guid,guid, GUIDLEN)) { int len = 0;
audio_packet p; while (len < d.data.length()) {
memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! QByteArray partial;
p.len = sizeof(p) + d.data.length(); partial = d.data.mid(len, 1364);
p.sentid = client->myId; len = len + partial.length();
p.rcvdid = client->remoteId; if (client != Q_NULLPTR && client->connected && !memcmp(client->guid, guid, GUIDLEN)) {
p.ident = 0x0080; // audio is always this? audio_packet p;
p.datalen = (quint16)qToBigEndian((quint16)d.data.length()); memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00!
p.sendseq = (quint16)qToBigEndian((quint16)client->sendAudioSeq); // THIS IS BIG ENDIAN! p.len = sizeof(p) + partial.length();
p.seq = client->txSeq; p.sentid = client->myId;
QByteArray t = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); p.rcvdid = client->remoteId;
t.append(d.data); 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; SEQBUFENTRY s;
s.seqNum = p.seq; s.seqNum = p.seq;
s.timeSent = QTime::currentTime(); s.timeSent = QTime::currentTime();
s.retransmitCount = 0; s.retransmitCount = 0;
s.data = t; s.data = t;
if (client->txMutex.try_lock_for(std::chrono::milliseconds(LOCK_PERIOD))) if (client->txMutex.try_lock_for(std::chrono::milliseconds(LOCK_PERIOD)))
{
if (client->txSeqBuf.size() > BUFSIZE)
{ {
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))) if (udpMutex.try_lock_for(std::chrono::milliseconds(LOCK_PERIOD)))
{ {
client->socket->writeDatagram(t, client->ipAddress, client->port); client->socket->writeDatagram(t, client->ipAddress, client->port);
udpMutex.unlock(); udpMutex.unlock();
} }
else { else {
qInfo(logUdpServer()) << "Unable to lock udpMutex()"; qInfo(logUdpServer()) << "Unable to lock udpMutex()";
} }
}
} }
} }
@ -1871,11 +1858,6 @@ void udpServer::deleteConnection(QList<CLIENT*>* l, CLIENT* c)
for (RIGCONFIG* radio : config.rigs) { for (RIGCONFIG* radio : config.rigs) {
if (!memcmp(radio->guid, guid, GUIDLEN)) 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) { if (radio->rxAudioThread != Q_NULLPTR) {
radio->rxAudioThread->quit(); radio->rxAudioThread->quit();

Wyświetl plik

@ -96,6 +96,7 @@ public slots:
void dataForServer(QByteArray); void dataForServer(QByteArray);
void receiveAudioData(const audioPacket &data); void receiveAudioData(const audioPacket &data);
void receiveRigCaps(rigCapabilities caps); void receiveRigCaps(rigCapabilities caps);
void sendRxAudio(const audioPacket &audio);
signals: signals:
void haveDataFromServer(QByteArray); void haveDataFromServer(QByteArray);
@ -184,7 +185,6 @@ private:
void sendStatus(CLIENT* c); void sendStatus(CLIENT* c);
void sendRetransmitRequest(CLIENT* c); void sendRetransmitRequest(CLIENT* c);
void watchdog(); void watchdog();
void sendRxAudio();
void deleteConnection(QList<CLIENT*> *l, CLIENT* c); void deleteConnection(QList<CLIENT*> *l, CLIENT* c);
SERVERCONFIG config; SERVERCONFIG config;