kopia lustrzana https://gitlab.com/eliggett/wfview
Change TX audio to use timed buffer.
rodzic
5a547440b3
commit
e593e5e90a
131
audiohandler.cpp
131
audiohandler.cpp
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Ładowanie…
Reference in New Issue