Change TX audio to use timed buffer.

merge-requests/2/head
Phil Taylor 2021-02-27 09:34:56 +00:00
rodzic 5a547440b3
commit e593e5e90a
4 zmienionych plików z 82 dodań i 61 usunięć

Wyświetl plik

@ -844,7 +844,6 @@ void audioHandler::reinit()
delete audioOutput; delete audioOutput;
audioOutput = Q_NULLPTR; audioOutput = Q_NULLPTR;
audioOutput = new QAudioOutput(deviceInfo, format, this); audioOutput = new QAudioOutput(deviceInfo, format, this);
//audioOutput->setLatency(audioBuffer);
connect(audioOutput, SIGNAL(notify()), SLOT(notified())); connect(audioOutput, SIGNAL(notify()), SLOT(notified()));
connect(audioOutput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State))); connect(audioOutput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State)));
} }
@ -869,7 +868,8 @@ void audioHandler::start()
else { else {
this->open(QIODevice::ReadOnly | QIODevice::Unbuffered); this->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
audioOutput->start(this); audioOutput->start(this);
} audioOutput->suspend(); // Suspend until data has arrived to "kick-start" playback
}
} }
void audioHandler::setVolume(float volume) void audioHandler::setVolume(float volume)
@ -888,26 +888,23 @@ void audioHandler::flush()
else { else {
audioOutput->reset(); audioOutput->reset();
} }
buffer.clear();
QMutexLocker locker(&mutex);
audioBuffer.clear();
this->start(); this->start();
} }
void audioHandler::stop() void audioHandler::stop()
{ {
//QMutexLocker locker(&mutex);
if (audioOutput && audioOutput->state() != QAudio::StoppedState) { if (audioOutput && audioOutput->state() != QAudio::StoppedState) {
// Stop audio output // Stop audio output
audioOutput->stop(); audioOutput->stop();
QByteArray ret;
audioBuffer.clear();
//buffer.clear();
this->close(); this->close();
} }
if (audioInput && audioInput->state() != QAudio::StoppedState) { if (audioInput && audioInput->state() != QAudio::StoppedState) {
// Stop audio output // Stop audio output
audioInput->stop(); audioInput->stop();
buffer.clear();
this->close(); this->close();
} }
} }
@ -921,7 +918,10 @@ qint64 audioHandler::readData(char* data, qint64 maxlen)
if (!audioBuffer.isEmpty()) if (!audioBuffer.isEmpty())
{ {
// Sort the buffer by seq number. // We must lock the mutex for the entire time that the buffer may be modified.
QMutexLocker locker(&mutex);
// Sort the buffer by seq number. This is important and audio packets may have arrived out-of-order
std::sort(audioBuffer.begin(), audioBuffer.end(), std::sort(audioBuffer.begin(), audioBuffer.end(),
[](const AUDIOPACKET& a, const AUDIOPACKET& b) -> bool [](const AUDIOPACKET& a, const AUDIOPACKET& b) -> bool
{ {
@ -932,12 +932,11 @@ qint64 audioHandler::readData(char* data, qint64 maxlen)
int divisor = 16 / radioSampleBits; int divisor = 16 / radioSampleBits;
auto packet = audioBuffer.begin(); auto packet = audioBuffer.begin();
while (packet != audioBuffer.end() && sentlen<maxlen) while (packet != audioBuffer.end() && sentlen < maxlen)
{ {
if (packet->time.msecsTo(QTime::currentTime()) > latency) { if (packet->time.msecsTo(QTime::currentTime()) > latency) {
//qDebug(logAudio()) << "Packet " << hex << packet->seq << " arrived too late (increase rx buffer size!) " << dec << packet->time.msecsTo(QTime::currentTime()) << "ms"; //qDebug(logAudio()) << "Packet " << hex << packet->seq << " arrived too late (increase rx buffer size!) " << dec << packet->time.msecsTo(QTime::currentTime()) << "ms";
QMutexLocker locker(&mutex); packet = audioBuffer.erase(packet); // returns next packet
packet=audioBuffer.erase(packet); // returns next packet
} }
else else
{ {
@ -971,7 +970,6 @@ qint64 audioHandler::readData(char* data, qint64 maxlen)
if (send == packet->data.length()) if (send == packet->data.length())
{ {
QMutexLocker locker(&mutex);
packet = audioBuffer.erase(packet); // returns next packet packet = audioBuffer.erase(packet); // returns next packet
if (maxlen - sentlen == 0) if (maxlen - sentlen == 0)
{ {
@ -992,7 +990,6 @@ qint64 audioHandler::readData(char* data, qint64 maxlen)
} }
} }
} }
//qDebug(logAudio()) << "Returning: " << sentlen << " max: " << maxlen;
return sentlen; return sentlen;
} }
@ -1000,39 +997,58 @@ qint64 audioHandler::readData(char* data, qint64 maxlen)
qint64 audioHandler::writeData(const char* data, qint64 len) qint64 audioHandler::writeData(const char* data, qint64 len)
{ {
QMutexLocker locker(&mutex);
if (isUlaw) { // The radio expects packets in radioSampleBits * 120 size packets.
for (int f = 0; f < len / 2; f++) int chunkSize = radioSampleBits * 120;
{ int multiplier = 16 / radioSampleBits;
qint16 enc = qFromLittleEndian<quint16>(data + f * 2); int sentlen = 0;
if (enc >=0)
buffer.append((quint8)ulaw_encode[enc]); while (sentlen < len) {
else QByteArray buffer;
buffer.append((quint8) 0x7f & ulaw_encode[-enc]); if (radioSampleBits == 8) {
int f = 0;
while (f < chunkSize && f * multiplier + sentlen < len)
{
if (isUlaw) {
qint16 enc = qFromLittleEndian<quint16>(data + (f * multiplier + sentlen));
if (enc >= 0)
buffer.append((quint8)ulaw_encode[enc]);
else
buffer.append((quint8)0x7f & ulaw_encode[-enc]);
}
else {
buffer.append((quint8)(((qFromLittleEndian<qint16>(data + (f * multiplier + sentlen)) >> 8) ^ 0x80) & 0xff));
}
f++;
}
} }
} else if (radioSampleBits == 16)
else if (radioSampleBits == 8) { {
for (int f = 0; f < len / 2; f++) buffer.append(QByteArray::fromRawData(data+sentlen, chunkSize*multiplier));
{ }
buffer.append((quint8)(((qFromLittleEndian<qint16>(data + f * 2) >> 8) ^ 0x80) & 0xff));
} sentlen = sentlen + (chunkSize*multiplier);
} AUDIOPACKET tempAudio;
else if (radioSampleBits == 16) { tempAudio.seq = 0; // Not used in TX
buffer.append(QByteArray::fromRawData(data, len)); tempAudio.time = QTime::currentTime();
} tempAudio.sent = 0;
else { tempAudio.data = buffer;
qWarning() << "Unsupported number of bits! :" << radioSampleBits; QMutexLocker locker(&mutex);
} audioBuffer.append(tempAudio);
if (buffer.size() >= radioSampleBits * 120) { if (buffer.length() < chunkSize)
chunkAvailable = true; {
} // We don't have enough for a full packet yet, what to do?
return (len); // Always return the same number as we received qDebug(logAudio) << "*** Small Packet *** " << buffer.length() << " less than " << chunkSize;
}
}
return (sentlen); // Always return the same number as we received
} }
qint64 audioHandler::bytesAvailable() const qint64 audioHandler::bytesAvailable() const
{ {
return buffer.length() + QIODevice::bytesAvailable(); return 0;
} }
bool audioHandler::isSequential() const bool audioHandler::isSequential() const
@ -1049,10 +1065,7 @@ void audioHandler::stateChanged(QAudio::State state)
{ {
if (state == QAudio::IdleState && audioOutput->error() == QAudio::UnderrunError) { if (state == QAudio::IdleState && audioOutput->error() == QAudio::UnderrunError) {
qDebug(logAudio()) << this->metaObject()->className() << "RX:Buffer underrun"; qDebug(logAudio()) << this->metaObject()->className() << "RX:Buffer underrun";
#ifdef Q_OS_WIN
QMutexLocker locker(&mutex);
audioOutput->suspend(); audioOutput->suspend();
#endif
} }
//qDebug(logAudio()) << this->metaObject()->className() << ": state = " << state; //qDebug(logAudio()) << this->metaObject()->className() << ": state = " << state;
} }
@ -1064,6 +1077,7 @@ void audioHandler::incomingAudio(const AUDIOPACKET data)
if (audioOutput != Q_NULLPTR && audioOutput->state() != QAudio::StoppedState) { if (audioOutput != Q_NULLPTR && audioOutput->state() != QAudio::StoppedState) {
QMutexLocker locker(&mutex); QMutexLocker locker(&mutex);
audioBuffer.push_back(data); audioBuffer.push_back(data);
// Restart playback // Restart playback
if (audioOutput->state() == QAudio::SuspendedState) if (audioOutput->state() == QAudio::SuspendedState)
{ {
@ -1086,21 +1100,28 @@ void audioHandler::getLatency()
bool audioHandler::isChunkAvailable() bool audioHandler::isChunkAvailable()
{ {
return chunkAvailable; return (!audioBuffer.isEmpty());
} }
void audioHandler::getNextAudioChunk(QByteArray& ret) void audioHandler::getNextAudioChunk(QByteArray& ret)
{ {
quint16 numSamples = radioSampleBits * 120; if (!audioBuffer.isEmpty())
if (buffer.size() >= numSamples) { {
ret.append(buffer.mid(0, numSamples)); QMutexLocker locker(&mutex);
QMutexLocker locker(&mutex); auto packet = audioBuffer.begin();
buffer.remove(0, numSamples); while (packet != audioBuffer.end())
} {
if (buffer.size() < numSamples) if (packet->time.msecsTo(QTime::currentTime()) > 40) {
{ qDebug(logAudio()) << "TX Packet arrived too late " << dec << packet->time.msecsTo(QTime::currentTime()) << "ms";
chunkAvailable = false; packet = audioBuffer.erase(packet); // returns next packet
} }
else {
ret.append(packet->data);
packet = audioBuffer.erase(packet); // returns next packet
break; // We only want one packet.
}
}
}
return; return;
} }

Wyświetl plik

@ -82,7 +82,6 @@ private:
bool isInput; // Used to determine whether input or output audio bool isInput; // Used to determine whether input or output audio
float volume; float volume;
QByteArray buffer;
QAudioFormat format; QAudioFormat format;
QAudioDeviceInfo deviceInfo; QAudioDeviceInfo deviceInfo;
quint16 radioSampleRate; quint16 radioSampleRate;

Wyświetl plik

@ -452,7 +452,7 @@ void udpHandler::sendToken(uint8_t magic)
authInnerSendSeq++; authInnerSendSeq++;
sendTrackedPacket(QByteArray::fromRawData((const char *)p.packet, sizeof(p))); sendTrackedPacket(QByteArray::fromRawData((const char *)p.packet, sizeof(p)));
// The radio should request a repeat of the token renewal! // The radio should request a repeat of the token renewal packet via retransmission!
//tokenTimer->start(100); // Set 100ms timer for retry (this will be cancelled if a response is received) //tokenTimer->start(100); // Set 100ms timer for retry (this will be cancelled if a response is received)
return; return;
} }
@ -839,9 +839,10 @@ void udpAudio::dataReceived()
tempAudio.time = lastReceived; tempAudio.time = lastReceived;
tempAudio.sent = 0; tempAudio.sent = 0;
tempAudio.data = r.mid(24); tempAudio.data = r.mid(24);
// Prefer signal/slot to forward audio as it is thread/safe
// Need to do more testing but latency appears fine.
emit haveAudioData(tempAudio); emit haveAudioData(tempAudio);
//rxaudio->incomingAudio(tempAudio); //rxaudio->incomingAudio(tempAudio);
//rxaudio->incomingAudio(r.mid(24));
} }
} }
break; break;

Wyświetl plik

@ -776,9 +776,9 @@ void wfmain::loadSettings()
ui->rxLatencySlider->setTracking(false); // Stop it sending value on every change. ui->rxLatencySlider->setTracking(false); // Stop it sending value on every change.
prefs.audioTXLatency = settings.value("AudioTXLatency", defPrefs.audioTXLatency).toInt(); prefs.audioTXLatency = settings.value("AudioTXLatency", defPrefs.audioTXLatency).toInt();
ui->rxLatencySlider->setEnabled(ui->lanEnableChk->isChecked()); ui->txLatencySlider->setEnabled(ui->lanEnableChk->isChecked());
ui->rxLatencySlider->setValue(prefs.audioTXLatency); ui->txLatencySlider->setValue(prefs.audioTXLatency);
ui->rxLatencySlider->setTracking(false); // Stop it sending value on every change. ui->txLatencySlider->setTracking(false); // Stop it sending value on every change.
prefs.audioRXSampleRate = settings.value("AudioRXSampleRate", defPrefs.audioRXSampleRate).toInt(); prefs.audioRXSampleRate = settings.value("AudioRXSampleRate", defPrefs.audioRXSampleRate).toInt();
prefs.audioTXSampleRate = settings.value("AudioTXSampleRate", defPrefs.audioTXSampleRate).toInt(); prefs.audioTXSampleRate = settings.value("AudioTXSampleRate", defPrefs.audioTXSampleRate).toInt();