From 389f661c79ab248596539eb5bd8c5dc84824a218 Mon Sep 17 00:00:00 2001 From: Phil Taylor Date: Sun, 3 Apr 2022 20:16:52 +0100 Subject: [PATCH] Working (in Windows) audio output --- audiohandler.cpp | 262 ++++++++++++++++++++--------------------- audiohandler.h | 226 ++++++++++++++++++++++++++++++++--- rigcommander.cpp | 2 +- tcpserver.cpp | 3 +- tcpserver.h | 3 +- udphandler.cpp | 14 +-- udpserver.cpp | 4 +- wfmain.cpp | 15 +-- wfview.pro | 10 +- wfview.vcxproj | 13 +- wfview.vcxproj.filters | 3 + 11 files changed, 380 insertions(+), 175 deletions(-) diff --git a/audiohandler.cpp b/audiohandler.cpp index 7424b1e..9a10329 100644 --- a/audiohandler.cpp +++ b/audiohandler.cpp @@ -73,28 +73,30 @@ bool audioHandler::init(audioSetup setupIn) */ setup = setupIn; - setup.radioChan = 1; - setup.bits = 8; + setup.format.setChannelCount(1); + setup.format.setSampleSize(8); + setup.format.setSampleType(QAudioFormat::UnSignedInt); qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "audio handler starting:" << setup.name; if (setup.codec == 0x01 || setup.codec == 0x20) { setup.ulaw = true; } if (setup.codec == 0x08 || setup.codec == 0x10 || setup.codec == 0x20 || setup.codec == 0x80) { - setup.radioChan = 2; + setup.format.setChannelCount(2); } if (setup.codec == 0x04 || setup.codec == 0x10 || setup.codec == 0x40 || setup.codec == 0x80) { - setup.bits = 16; + setup.format.setSampleSize(16); + setup.format.setSampleType(QAudioFormat::SignedInt); } qDebug(logAudio()) << "Creating" << (setup.isinput ? "Input" : "Output") << "audio device:" << setup.name << - ", bits" << setup.bits << + ", bits" << setup.format.sampleSize() << ", codec" << setup.codec << ", latency" << setup.latency << ", localAFGain" << setup.localAFgain << - ", radioChan" << setup.radioChan << + ", radioChan" << setup.format.channelCount() << ", resampleQuality" << setup.resampleQuality << - ", samplerate" << setup.samplerate << + ", samplerate" << setup.format.sampleRate() << ", uLaw" << setup.ulaw; @@ -325,11 +327,9 @@ bool audioHandler::init(audioSetup setupIn) else { audioOutput = new QAudioOutput(setup.port, format, this); -#ifdef Q_OS_MAC - audioOutput->setBufferSize(chunkSize*4); -#endif + audioOutput->setBufferSize(getAudioSize(setup.latency, format)); - connect(audioOutput, SIGNAL(notify()), SLOT(notified())); + //connect(audioOutput, SIGNAL(notify()), SLOT(notified())); connect(audioOutput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State))); isInitialized = true; } @@ -339,10 +339,10 @@ bool audioHandler::init(audioSetup setupIn) int resample_error = 0; int opus_err = 0; if (setup.isinput) { - resampler = wf_resampler_init(devChannels, nativeSampleRate, setup.samplerate, setup.resampleQuality, &resample_error); + resampler = wf_resampler_init(devChannels, nativeSampleRate, setup.format.sampleRate(), setup.resampleQuality, &resample_error); if (setup.codec == 0x40 || setup.codec == 0x80) { // Opus codec - encoder = opus_encoder_create(setup.samplerate, setup.radioChan, OPUS_APPLICATION_AUDIO, &opus_err); + encoder = opus_encoder_create(setup.format.sampleRate(), setup.format.channelCount(), OPUS_APPLICATION_AUDIO, &opus_err); opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(16)); opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(1)); opus_encoder_ctl(encoder, OPUS_SET_DTX(1)); @@ -351,10 +351,13 @@ bool audioHandler::init(audioSetup setupIn) } } else { - resampler = wf_resampler_init(devChannels, setup.samplerate, this->nativeSampleRate, setup.resampleQuality, &resample_error); + + //resampBufs = new r8b::CFixedBuffer[format.channelCount()]; + //resamps = new r8b::CPtrKeeper[format.channelCount()]; + resampler = wf_resampler_init(devChannels, setup.format.sampleRate(), this->nativeSampleRate, setup.resampleQuality, &resample_error); if (setup.codec == 0x40 || setup.codec == 0x80) { // Opus codec - decoder = opus_decoder_create(setup.samplerate, setup.radioChan, &opus_err); + decoder = opus_decoder_create(setup.format.sampleRate(), setup.format.sampleRate(), &opus_err); qInfo(logAudio()) << "Creating opus decoder: " << opus_strerror(opus_err); } } @@ -400,9 +403,18 @@ void audioHandler::start() this->open(QIODevice::ReadOnly); #else //this->open(QIODevice::ReadOnly | QIODevice::Unbuffered); - this->open(QIODevice::ReadOnly); + //this->open(QIODevice::ReadOnly); #endif - audioOutput->start(this); + audioDevice = audioOutput->start(); + if (!audioDevice) + { + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Audio device failed to start()"; + return; + } + connect(audioOutput, &QAudioOutput::destroyed, audioDevice, &QIODevice::deleteLater, Qt::UniqueConnection); + connect(audioDevice, &QIODevice::destroyed, this, &QAudioOutput::deleteLater, Qt::UniqueConnection); + audioBuffered = true; + } } #endif @@ -612,45 +624,30 @@ void audioHandler::incomingAudio(audioPacket inPacket) // Regardless of the radio stream format, the buffered audio will ALWAYS be // 16bit sample interleaved stereo 48K (or whatever the native sample rate is) - if (inPacket.time.msecsTo(QTime::currentTime()) > 20) - { - qInfo(logUdp()) << "Audio took" << inPacket.time.msecsTo(QTime::currentTime()) << "ms to arrive!"; - } - - if (!isInitialized && !isReady) - { - qDebug(logAudio()) << "Packet received when stream was not ready"; - return; - } - if (!audioBuffered && ringBuf->size() > ringBuf->capacity() / 2) - { - qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Audio buffering complete, capacity:" << ringBuf->capacity() << ", used:" << ringBuf->size(); - audioBuffered = true; - } - audioPacket livePacket = inPacket; if (setup.codec == 0x40 || setup.codec == 0x80) { + /* Opus data */ unsigned char* in = (unsigned char*)inPacket.data.data(); /* Decode the frame. */ - QByteArray outPacket((setup.samplerate / 50) * sizeof(qint16) * setup.radioChan, (char)0xff); // Preset the output buffer size. + QByteArray outPacket((setup.format.sampleRate() / 50) * sizeof(qint16) * setup.format.channelCount(), (char)0xff); // Preset the output buffer size. qint16* out = (qint16*)outPacket.data(); - int nSamples = opus_packet_get_nb_samples(in, livePacket.data.size(),setup.samplerate); + int nSamples = opus_packet_get_nb_samples(in, livePacket.data.size(),setup.format.sampleRate()); if (nSamples == -1) { // No opus data yet? return; } - else if (nSamples != setup.samplerate / 50) + else if (nSamples != setup.format.sampleRate() / 50) { - qInfo(logAudio()) << "Opus nSamples=" << nSamples << " expected:" << (setup.samplerate / 50); + qInfo(logAudio()) << "Opus nSamples=" << nSamples << " expected:" << (setup.format.sampleRate() / 50); return; } if (livePacket.seq > lastSentSeq + 1) { - nSamples = opus_decode(decoder, in, livePacket.data.size(), out, (setup.samplerate / 50), 1); + nSamples = opus_decode(decoder, in, livePacket.data.size(), out, (setup.format.sampleRate() / 50), 1); } else { - nSamples = opus_decode(decoder, in, livePacket.data.size(), out, (setup.samplerate / 50), 0); + nSamples = opus_decode(decoder, in, livePacket.data.size(), out, (setup.format.sampleRate() / 50), 0); } if (nSamples < 0) { @@ -658,10 +655,10 @@ void audioHandler::incomingAudio(audioPacket inPacket) return; } else { - if (int(nSamples * sizeof(qint16) * setup.radioChan) != outPacket.size()) + if (int(nSamples * sizeof(qint16) * setup.format.channelCount()) != outPacket.size()) { - qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Opus decoder mismatch: nBytes:" << nSamples * sizeof(qint16) * setup.radioChan << "outPacket:" << outPacket.size(); - outPacket.resize(nSamples * sizeof(qint16) * setup.radioChan); + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Opus decoder mismatch: nBytes:" << nSamples * sizeof(qint16) * setup.format.channelCount() << "outPacket:" << outPacket.size(); + outPacket.resize(nSamples * sizeof(qint16) * setup.format.channelCount()); } //qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Opus decoded" << livePacket.data.size() << "bytes, into" << outPacket.length() << "bytes"; livePacket.data.clear(); @@ -669,105 +666,100 @@ void audioHandler::incomingAudio(audioPacket inPacket) } } - //qDebug(logAudio()) << "Got" << setup.bits << "bits, length" << livePacket.data.length(); - // Incoming data is 8bits? - int tempAmplitude=0; - if (setup.bits == 8) + // Process uLaw + if (setup.ulaw) { // Current packet is 8bit so need to create a new buffer that is 16bit - QByteArray outPacket((int)livePacket.data.length() * 2 * (devChannels / setup.radioChan), (char)0xff); + QByteArray outPacket((int)livePacket.data.length() * 2, (char)0xff); qint16* out = (qint16*)outPacket.data(); for (int f = 0; f < livePacket.data.length(); f++) { - int samp = (quint8)livePacket.data[f]; - tempAmplitude = qMax(tempAmplitude, abs(samp)); - for (int g = setup.radioChan; g <= devChannels; g++) - { - if (setup.ulaw) { - *out++ = ulaw_decode[samp] * this->volume; - } - else { - *out++ = (qint16)((samp - 128) << 8) * this->volume; - } - } + *out++ = ulaw_decode[(quint8)livePacket.data[f]]; } livePacket.data.clear(); livePacket.data = outPacket; // Replace incoming data with converted. + setup.format.setSampleSize(16); + setup.format.setSampleType(QAudioFormat::SignedInt); + // Buffer now contains 16bit signed samples. } - else - { - // This is already a 16bit stream, do we need to convert to stereo? - if (setup.radioChan == 1 && devChannels > 1) { - // Yes - QByteArray outPacket(livePacket.data.length() * 2, (char)0xff); // Preset the output buffer size. - qint16* in = (qint16*)livePacket.data.data(); + + + + if (!livePacket.data.isEmpty()) { + + Eigen::VectorXf samplesF; + if (setup.format.sampleSize() == 16) + { + VectorXint16 samplesI = Eigen::Map(reinterpret_cast(livePacket.data.data()), livePacket.data.size() / int(sizeof(qint16))); + samplesF = samplesI.cast(); + } + else + { + VectorXuint8 samplesI = Eigen::Map(reinterpret_cast(livePacket.data.data()), livePacket.data.size() / int(sizeof(quint8))); + samplesF = samplesI.cast() / float(std::numeric_limits::max());; + } + + // Set the max amplitude found in the vector + amplitude = samplesF.array().abs().maxCoeff(); + + // Set the volume + samplesF *= volume; + + // Convert mono to stereo + if (setup.format.channelCount() == 1) { + Eigen::VectorXf samplesTemp(samplesF.size() * 2); + Eigen::Map >(samplesTemp.data(), samplesF.size()) = samplesF; + Eigen::Map >(samplesTemp.data() + 1, samplesF.size()) = samplesF; + samplesF = samplesTemp; + } + + + if (format.sampleType() == QAudioFormat::SignedInt) + { + VectorXint16 samplesI = samplesF.cast(); + livePacket.data = QByteArray(reinterpret_cast(samplesI.data()), int(samplesI.size()) * int(sizeof(qint16))); + } + else + { + livePacket.data = QByteArray(reinterpret_cast(samplesF.data()), int(samplesF.size()) * int(sizeof(float))); + } + + + if (resampleRatio != 1.0) { + + // We need to resample + // We have a stereo 16bit stream. + quint32 outFrames = ((livePacket.data.length() / 2 / devChannels) * resampleRatio); + quint32 inFrames = (livePacket.data.length() / 2 / devChannels); + QByteArray outPacket(outFrames * 4, (char)0xff); // Preset the output buffer size. + + const qint16* in = (qint16*)livePacket.data.constData(); qint16* out = (qint16*)outPacket.data(); - for (int f = 0; f < livePacket.data.length() / 2; f++) - { - tempAmplitude = qMax(tempAmplitude, (int)(abs(*in) / 256)); - *out++ = (qint16)*in * this->volume; - *out++ = (qint16)*in++ * this->volume; + + int err = 0; + err = wf_resampler_process_interleaved_int(resampler, in, &inFrames, out, &outFrames); + if (err) { + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Resampler error " << err << " inFrames:" << inFrames << " outFrames:" << outFrames; } livePacket.data.clear(); livePacket.data = outPacket; // Replace incoming data with converted. } - else - { - // We already have the same number of channels so just update volume. - qint16* in = (qint16*)livePacket.data.data(); - for (int f = 0; f < livePacket.data.length() / 2; f++) - { - tempAmplitude = qMax(tempAmplitude, (int)(abs(*in) / 256)); - *in = *in * this->volume; - in++; - } + + + //qDebug(logAudio()) << "Adding packet to buffer:" << livePacket.seq << ": " << livePacket.data.length(); + + currentLatency = livePacket.time.msecsTo(QTime::currentTime()); + + audioDevice->write(livePacket.data); + + if ((inPacket.seq > lastSentSeq + 1) && (setup.codec == 0x40 || setup.codec == 0x80)) { + qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Attempting FEC on packet" << inPacket.seq << "as last is" << lastSentSeq; + lastSentSeq = inPacket.seq; + incomingAudio(inPacket); // Call myself again to run the packet a second time (FEC) } - } - amplitude = tempAmplitude; - /* We now have an array of 16bit samples in the NATIVE samplerate of the radio - If the radio sample rate is below 48000, we need to resample. - */ - //qDebug(logAudio()) << "Now 16 bit stereo, length" << livePacket.data.length(); - - if (resampleRatio != 1.0) { - - // We need to resample - // We have a stereo 16bit stream. - quint32 outFrames = ((livePacket.data.length() / 2 / devChannels) * resampleRatio); - quint32 inFrames = (livePacket.data.length() / 2 / devChannels); - QByteArray outPacket(outFrames * 4, (char)0xff); // Preset the output buffer size. - - const qint16* in = (qint16*)livePacket.data.constData(); - qint16* out = (qint16*)outPacket.data(); - - int err = 0; - err = wf_resampler_process_interleaved_int(resampler, in, &inFrames, out, &outFrames); - if (err) { - qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Resampler error " << err << " inFrames:" << inFrames << " outFrames:" << outFrames; - } - livePacket.data.clear(); - livePacket.data = outPacket; // Replace incoming data with converted. - } - - //qDebug(logAudio()) << "Adding packet to buffer:" << livePacket.seq << ": " << livePacket.data.length(); - - if (!ringBuf->try_write(livePacket)) - { - qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Buffer full! capacity:" << ringBuf->capacity() << "length" << ringBuf->size(); - while (ringBuf->try_read(inPacket)); // Empty buffer - return; - } - if ((inPacket.seq > lastSentSeq + 1) && (setup.codec == 0x40 || setup.codec == 0x80)) { - qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Attempting FEC on packet" << inPacket.seq << "as last is" << lastSentSeq; lastSentSeq = inPacket.seq; - incomingAudio(inPacket); // Call myself again to run the packet a second time (FEC) } - lastSentSeq = inPacket.seq; - //if (inPacket.time.msecsTo(QTime::currentTime()) > 20) - //{ - //qDebug(logAudio()) << "After processing, audio took" << inPacket.time.msecsTo(QTime::currentTime()) << "ms to arrive!"; - //} return; } @@ -776,9 +768,18 @@ void audioHandler::changeLatency(const quint16 newSize) { qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Changing latency to: " << newSize << " from " << setup.latency; setup.latency = newSize; - delete ringBuf; - audioBuffered = false; - ringBuf = new wilt::Ring(setup.latency + 1); // Should be customizable. + //delete ringBuf; + //audioBuffered = false; + //ringBuf = new wilt::Ring(setup.latency + 1); // Should be customizable. + if (!setup.isinput) { + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Current buffer size is" << audioOutput->bufferSize() << " " << getAudioDuration(audioOutput->bufferSize(), format) << "ms)"; + audioOutput->stop(); + audioOutput->setBufferSize(getAudioSize(setup.latency, format)); + audioDevice = audioOutput->start(); + connect(audioOutput, &QAudioOutput::destroyed, audioDevice, &QIODevice::deleteLater, Qt::UniqueConnection); + connect(audioDevice, &QIODevice::destroyed, this, &QAudioOutput::deleteLater, Qt::UniqueConnection); + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "New buffer size is" << audioOutput->bufferSize() << " " << getAudioDuration(audioOutput->bufferSize(), format) << "ms)"; + } } int audioHandler::getLatency() @@ -830,7 +831,7 @@ void audioHandler::getNextAudioChunk(QByteArray& ret) int tempAmplitude = 0; // Do we need to convert mono to stereo? - if (setup.radioChan == 1 && devChannels > 1) + if (setup.format.channelCount() == 1 && devChannels > 1) { // Strip out right channel? QByteArray outPacket(packet.data.length()/2, (char)0xff); @@ -857,7 +858,7 @@ void audioHandler::getNextAudioChunk(QByteArray& ret) QByteArray outPacket(1275, (char)0xff); // Preset the output buffer size to MAXIMUM possible Opus frame size unsigned char* out = (unsigned char*)outPacket.data(); - int nbBytes = opus_encode(encoder, in, (setup.samplerate / 50), out, outPacket.length()); + int nbBytes = opus_encode(encoder, in, (setup.format.sampleRate() / 50), out, outPacket.length()); if (nbBytes < 0) { qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Opus encode failed:" << opus_strerror(nbBytes); @@ -870,7 +871,7 @@ void audioHandler::getNextAudioChunk(QByteArray& ret) } } - else if (setup.bits == 8) + else if (setup.format.sampleSize() == 8) { // Do we need to convert 16-bit to 8-bit? @@ -908,7 +909,6 @@ void audioHandler::getNextAudioChunk(QByteArray& ret) qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Too many delayed packets, flushing buffer"; while (ringBuf->try_read(packet)); // Empty buffer delayedPackets = 0; - audioBuffered = false; } } @@ -997,6 +997,6 @@ void audioHandler::stop() quint16 audioHandler::getAmplitude() { - return amplitude; + return *reinterpret_cast(&litude); } diff --git a/audiohandler.h b/audiohandler.h index 7064d5e..d1bfa01 100644 --- a/audiohandler.h +++ b/audiohandler.h @@ -25,20 +25,10 @@ #include #endif -#include - #include "packettypes.h" typedef signed short MY_TYPE; #define FORMAT RTAUDIO_SINT16 -#define SCALE 32767.0 - -#define LOG100 4.60517018599 - -typedef Eigen::Matrix VectorXuint8; -typedef Eigen::Matrix VectorXint8; -typedef Eigen::Matrix VectorXint16; -typedef Eigen::Matrix VectorXint32; #include #include @@ -55,6 +45,10 @@ typedef Eigen::Matrix VectorXint32; #endif #include "audiotaper.h" +#include + +//#include +//#include #include @@ -75,13 +69,14 @@ struct audioPacket { struct audioSetup { QString name; - quint8 bits; - quint8 radioChan; - quint16 samplerate; +// quint8 bits; +// quint8 radioChan; +// quint16 samplerate; quint16 latency; quint8 codec; - bool ulaw; + bool ulaw = false; bool isinput; + QAudioFormat format; // Use this for all audio APIs #if defined(RTAUDIO) || defined(PORTAUDIO) int port; #else @@ -190,17 +185,22 @@ private: #else QAudioOutput* audioOutput=Q_NULLPTR; QAudioInput* audioInput=Q_NULLPTR; + QIODevice* audioDevice=Q_NULLPTR; QAudioFormat format; QAudioDeviceInfo deviceInfo; #endif SpeexResamplerState* resampler = Q_NULLPTR; + //r8b::CFixedBuffer* resampBufs; + //r8b::CPtrKeeper* resamps; + quint16 audioLatency; unsigned int chunkSize; bool chunkAvailable; quint32 lastSeq; quint32 lastSentSeq=0; + qint64 elapsedMs = 0; quint16 nativeSampleRate=0; quint8 radioSampleBits; @@ -217,7 +217,7 @@ private: volatile bool ready = false; audioPacket tempBuf; quint16 currentLatency; - quint16 amplitude = 0; + float amplitude; qreal volume=1.0; int devChannels; audioSetup setup; @@ -225,4 +225,200 @@ private: OpusDecoder* decoder=Q_NULLPTR; }; + +// Various audio handling functions declared inline + +static inline qint64 getAudioSize(qint64 timeInMs, const QAudioFormat& format) +{ + qint64 value = qint64(qCeil(format.channelCount() * (format.sampleSize() / 8) * format.sampleRate() / qreal(10000) * timeInMs)); + + if (value % (format.channelCount() * (format.sampleSize() / 8)) != 0) + value += (format.channelCount() * (format.sampleSize() / 8) - value % (format.channelCount() * (format.sampleSize() / 8))); + + return value; +} + +static inline qint64 getAudioDuration(qint64 bytes, const QAudioFormat& format) +{ + return qint64(qFloor(bytes / (format.channelCount() * (format.sampleSize() / 8) * format.sampleRate() / qreal(1000)))); +} + +typedef Eigen::Matrix VectorXuint8; +typedef Eigen::Matrix VectorXint8; +typedef Eigen::Matrix VectorXint16; +typedef Eigen::Matrix VectorXint32; + +static inline QByteArray samplesToInt(const QByteArray& data, const QAudioFormat& supported_format) +{ + QByteArray input = data; + + switch (supported_format.sampleSize()) + { + case 8: + { + switch (supported_format.sampleType()) + { + case QAudioFormat::UnSignedInt: + { + Eigen::Ref samples_float = Eigen::Map(reinterpret_cast(input.data()), input.size() / int(sizeof(float))); + + Eigen::VectorXf samples_int_tmp = samples_float * float(std::numeric_limits::max()); + + VectorXuint8 samples_int = samples_int_tmp.cast(); + + QByteArray raw = QByteArray(reinterpret_cast(samples_int.data()), int(samples_int.size()) * int(sizeof(quint8))); + + return raw; + } + case QAudioFormat::SignedInt: + { + Eigen::Ref samples_float = Eigen::Map(reinterpret_cast(input.data()), input.size() / int(sizeof(float))); + + Eigen::VectorXf samples_int_tmp = samples_float * float(std::numeric_limits::max()); + + VectorXint8 samples_int = samples_int_tmp.cast(); + + QByteArray raw = QByteArray(reinterpret_cast(samples_int.data()), int(samples_int.size()) * int(sizeof(qint8))); + + return raw; + } + default: + break; + } + + break; + } + case 16: + { + switch (supported_format.sampleType()) + { + case QAudioFormat::SignedInt: + { + Eigen::Ref samples_float = Eigen::Map(reinterpret_cast(input.data()), input.size() / int(sizeof(float))); + + Eigen::VectorXf samples_int_tmp = samples_float * float(std::numeric_limits::max()); + + VectorXint16 samples_int = samples_int_tmp.cast(); + + QByteArray raw = QByteArray(reinterpret_cast(samples_int.data()), int(samples_int.size()) * int(sizeof(qint16))); + + return raw; + } + default: + break; + } + + break; + } + case 32: + { + switch (supported_format.sampleType()) + { + case QAudioFormat::SignedInt: + { + Eigen::Ref samples_float = Eigen::Map(reinterpret_cast(input.data()), input.size() / int(sizeof(float))); + + Eigen::VectorXf samples_int_tmp = samples_float * float(std::numeric_limits::max()); + + VectorXint32 samples_int = samples_int_tmp.cast(); + + QByteArray raw = QByteArray(reinterpret_cast(samples_int.data()), int(samples_int.size()) * int(sizeof(qint32))); + + return raw; + } + default: + break; + } + + break; + } + default: + break; + } + + return QByteArray(); +} + +static inline QByteArray samplesToFloat(const QByteArray& data, const QAudioFormat& supported_format) +{ + QByteArray input = data; + + switch (supported_format.sampleSize()) + { + case 8: + { + switch (supported_format.sampleType()) + { + case QAudioFormat::UnSignedInt: + { + QByteArray raw = input; + + Eigen::Ref samples_int = Eigen::Map(reinterpret_cast(raw.data()), raw.size() / int(sizeof(quint8))); + + Eigen::VectorXf samples_float = samples_int.cast() / float(std::numeric_limits::max()); + + return QByteArray(reinterpret_cast(samples_float.data()), int(samples_float.size()) * int(sizeof(float))); + } + case QAudioFormat::SignedInt: + { + QByteArray raw = input; + + Eigen::Ref samples_int = Eigen::Map(reinterpret_cast(raw.data()), raw.size() / int(sizeof(qint8))); + + Eigen::VectorXf samples_float = samples_int.cast() / float(std::numeric_limits::max()); + + return QByteArray(reinterpret_cast(samples_float.data()), int(samples_float.size()) * int(sizeof(float))); + } + default: + break; + } + + break; + } + case 16: + { + switch (supported_format.sampleType()) + { + case QAudioFormat::SignedInt: + { + QByteArray raw = input; + + Eigen::Ref samples_int = Eigen::Map(reinterpret_cast(raw.data()), raw.size() / int(sizeof(qint16))); + + Eigen::VectorXf samples_float = samples_int.cast() / float(std::numeric_limits::max()); + + return QByteArray(reinterpret_cast(samples_float.data()), int(samples_float.size()) * int(sizeof(float))); + } + default: + break; + } + + break; + } + case 32: + { + switch (supported_format.sampleType()) + { + case QAudioFormat::SignedInt: + { + QByteArray raw = input; + + Eigen::Ref samples_int = Eigen::Map(reinterpret_cast(raw.data()), raw.size() / int(sizeof(qint32))); + + Eigen::VectorXf samples_float = samples_int.cast() / float(std::numeric_limits::max()); + + return QByteArray(reinterpret_cast(samples_float.data()), int(samples_float.size()) * int(sizeof(float))); + } + default: + break; + } + + break; + } + default: + break; + } + + return QByteArray(); +} #endif // AUDIOHANDLER_H diff --git a/rigcommander.cpp b/rigcommander.cpp index 082b4f5..f0380dd 100644 --- a/rigcommander.cpp +++ b/rigcommander.cpp @@ -151,7 +151,7 @@ void rigCommander::commSetup(unsigned char rigCivAddr, udpPreferences prefs, aud connect(ptty, SIGNAL(haveDataFromPort(QByteArray)), udp, SLOT(receiveDataFromUserToRig(QByteArray))); // data from the tcp port to the Rig: - connect(tcp, SIGNAL(haveData(QByteArray)), udp, SLOT(receiveDataFromUserToRig(QByteArray))); + connect(tcp, SIGNAL(receiveData(QByteArray)), udp, SLOT(receiveDataFromUserToRig(QByteArray))); connect(this, SIGNAL(haveChangeLatency(quint16)), udp, SLOT(changeLatency(quint16))); connect(this, SIGNAL(haveSetVolume(unsigned char)), udp, SLOT(setVolume(unsigned char))); diff --git a/tcpserver.cpp b/tcpserver.cpp index d1fee65..8dea503 100644 --- a/tcpserver.cpp +++ b/tcpserver.cpp @@ -28,6 +28,7 @@ int tcpServer::startServer(qint16 port) { void tcpServer::incomingConnection(qintptr socket) { tcpServerClient* client = new tcpServerClient(socket, this); connect(this, SIGNAL(onStopped()), client, SLOT(closeSocket())); + emit newClient(socket); // Signal par } void tcpServer::stopServer() @@ -39,7 +40,7 @@ void tcpServer::stopServer() void tcpServer::receiveDataFromClient(QByteArray data) { - emit haveData(data); + emit receiveData(data); } void tcpServer::sendData(QByteArray data) { diff --git a/tcpserver.h b/tcpserver.h index 0fb8fda..e699a90 100644 --- a/tcpserver.h +++ b/tcpserver.h @@ -29,8 +29,9 @@ public slots: signals: void onStarted(); void onStopped(); - void haveData(QByteArray data); // emit this when we have data from tcp client, connect to rigcommander + void receiveData(QByteArray data); // emit this when we have data from tcp client, connect to rigcommander void sendDataToClient(QByteArray data); + void newClient(int socketId); private: QTcpServer* server; diff --git a/udphandler.cpp b/udphandler.cpp index 9ed326d..3fbfd41 100644 --- a/udphandler.cpp +++ b/udphandler.cpp @@ -20,8 +20,8 @@ udpHandler::udpHandler(udpPreferences prefs, audioSetup rx, audioSetup tx) : this->password = prefs.password; this->compName = prefs.clientName.mid(0,8) + "-wfview"; - qInfo(logUdp()) << "Starting udpHandler user:" << username << " rx latency:" << rxSetup.latency << " tx latency:" << txSetup.latency << " rx sample rate: " << rxSetup.samplerate << - " rx codec: " << rxSetup.codec << " tx sample rate: " << txSetup.samplerate << " tx codec: " << txSetup.codec; + qInfo(logUdp()) << "Starting udpHandler user:" << username << " rx latency:" << rxSetup.latency << " tx latency:" << txSetup.latency << " rx sample rate: " << rxSetup.format.sampleRate() << + " rx codec: " << rxSetup.codec << " tx sample rate: " << txSetup.format.sampleRate() << " tx codec: " << txSetup.codec; // Try to set the IP address, if it is a hostname then perform a DNS lookup. if (!radioIP.setAddress(prefs.ipAddress)) @@ -280,7 +280,7 @@ void udpHandler::dataReceived() // TX is not supported if (txSampleRates < 2) { - txSetup.samplerate = 0; + txSetup.format.setSampleRate(0); txSetup.codec = 0; } audio = new udpAudio(localIP, radioIP, audioPort, audioLocalPort, rxSetup, txSetup); @@ -512,8 +512,8 @@ void udpHandler::sendRequestStream() } p.rxcodec = rxSetup.codec; memcpy(&p.username, usernameEncoded.constData(), usernameEncoded.length()); - p.rxsample = qToBigEndian((quint32)rxSetup.samplerate); - p.txsample = qToBigEndian((quint32)txSetup.samplerate); + p.rxsample = qToBigEndian((quint32)rxSetup.format.sampleRate()); + p.txsample = qToBigEndian((quint32)txSetup.format.sampleRate()); p.civport = qToBigEndian((quint32)civLocalPort); p.audioport = qToBigEndian((quint32)audioLocalPort); p.txbuffer = qToBigEndian((quint32)txSetup.latency); @@ -789,7 +789,7 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint this->port = audioPort; this->radioIP = ip; - if (txSetup.samplerate == 0) { + if (txSetup.format.sampleRate() == 0) { enableTx = false; } @@ -812,7 +812,7 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint connect(this, SIGNAL(haveSetVolume(unsigned char)), rxaudio, SLOT(setVolume(unsigned char))); connect(rxAudioThread, SIGNAL(finished()), rxaudio, SLOT(deleteLater())); - txSetup.radioChan = 1; + txSetup.format.setChannelCount(1); // TX Audio is always single channel. txaudio = new audioHandler(); txAudioThread = new QThread(this); diff --git a/udpserver.cpp b/udpserver.cpp index 03fd882..56fee68 100644 --- a/udpserver.cpp +++ b/udpserver.cpp @@ -350,7 +350,7 @@ void udpServer::controlReceived() if (!memcmp(radio->guid, current->guid, GUIDLEN) && radio->txaudio == Q_NULLPTR) { radio->txAudioSetup.codec = current->txCodec; - radio->txAudioSetup.samplerate = current->txSampleRate; + radio->txAudioSetup.format.setSampleRate(current->txSampleRate); radio->txAudioSetup.isinput = false; radio->txAudioSetup.latency = current->txBufferLen; outAudio.isinput = false; @@ -390,7 +390,7 @@ void udpServer::controlReceived() #endif radio->rxAudioSetup.codec = current->rxCodec; - radio->rxAudioSetup.samplerate = current->rxSampleRate; + radio->rxAudioSetup.format.setSampleRate(current->rxSampleRate); radio->rxAudioSetup.latency = current->txBufferLen; radio->rxAudioSetup.isinput = true; diff --git a/wfmain.cpp b/wfmain.cpp index 3ceb5db..3bcca57 100644 --- a/wfmain.cpp +++ b/wfmain.cpp @@ -1552,10 +1552,11 @@ void wfmain::loadSettings() ui->txLatencySlider->setTracking(false); // Stop it sending value on every change. ui->audioSampleRateCombo->blockSignals(true); - rxSetup.samplerate = settings->value("AudioRXSampleRate", "48000").toInt(); - txSetup.samplerate = rxSetup.samplerate; + rxSetup.format.setSampleRate(settings->value("AudioRXSampleRate", "48000").toInt()); + txSetup.format.setSampleRate(rxSetup.format.sampleRate()); + ui->audioSampleRateCombo->setEnabled(ui->lanEnableBtn->isChecked()); - int audioSampleRateIndex = ui->audioSampleRateCombo->findText(QString::number(rxSetup.samplerate)); + int audioSampleRateIndex = ui->audioSampleRateCombo->findText(QString::number(rxSetup.format.sampleRate())); if (audioSampleRateIndex != -1) { ui->audioSampleRateCombo->setCurrentIndex(audioSampleRateIndex); } @@ -1974,9 +1975,9 @@ void wfmain::saveSettings() settings->setValue("Password", udpPrefs.password); settings->setValue("AudioRXLatency", rxSetup.latency); settings->setValue("AudioTXLatency", txSetup.latency); - settings->setValue("AudioRXSampleRate", rxSetup.samplerate); + settings->setValue("AudioRXSampleRate", rxSetup.format.sampleRate()); settings->setValue("AudioRXCodec", rxSetup.codec); - settings->setValue("AudioTXSampleRate", txSetup.samplerate); + settings->setValue("AudioTXSampleRate", txSetup.format.sampleRate()); settings->setValue("AudioTXCodec", txSetup.codec); settings->setValue("AudioOutput", rxSetup.name); settings->setValue("AudioInput", txSetup.name); @@ -4721,8 +4722,8 @@ void wfmain::on_audioSampleRateCombo_currentIndexChanged(QString text) { //udpPrefs.audioRXSampleRate = text.toInt(); //udpPrefs.audioTXSampleRate = text.toInt(); - rxSetup.samplerate = text.toInt(); - txSetup.samplerate = text.toInt(); + rxSetup.format.setSampleRate(text.toInt()); + txSetup.format.setSampleRate(text.toInt()); } void wfmain::on_audioRXCodecCombo_currentIndexChanged(int value) diff --git a/wfview.pro b/wfview.pro index 31370d5..48ef479 100644 --- a/wfview.pro +++ b/wfview.pro @@ -150,7 +150,8 @@ macx:LIBS += -framework CoreAudio -framework CoreFoundation -lpthread -lopus !linux:INCLUDEPATH += ../opus/include -!linux:INCLUDEPATH += ../eigen/Eigen +!linux:INCLUDEPATH += ../eigen +!linux:INCLUDEPATH += ../r8brain-free-src INCLUDEPATH += resampler @@ -175,8 +176,8 @@ SOURCES += main.cpp\ ring/ring.cpp \ transceiveradjustments.cpp \ selectradio.cpp \ - tcpserver.cpp \ - aboutbox.cpp + tcpserver.cpp \ + aboutbox.cpp HEADERS += wfmain.h \ commhandler.h \ @@ -204,7 +205,8 @@ HEADERS += wfmain.h \ transceiveradjustments.h \ audiotaper.h \ selectradio.h \ - tcpserver.h \ + tcpserver.h \ + audiocommon.h \ aboutbox.h diff --git a/wfview.vcxproj b/wfview.vcxproj index 0868b4b..612592e 100644 --- a/wfview.vcxproj +++ b/wfview.vcxproj @@ -48,7 +48,7 @@ - .;..\qcustomplot;..\opus\include;..\eigen\Eigen;resampler;release;/include;%(AdditionalIncludeDirectories) + .;..\qcustomplot;..\opus\include;..\eigen;..\r8brain-free-src;resampler;release;/include;%(AdditionalIncludeDirectories) -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) release\ false @@ -57,7 +57,7 @@ Sync release\ MaxSpeed - _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION="1.2d";BUILD_WFVIEW;QT_DEPRECATED_WARNINGS;QCUSTOMPLOT_COMPILE_LIBRARY;USE_SSE;USE_SSE2;OUTSIDE_SPEEX;RANDOM_PREFIX=wf;EIGEN_MPL2_ONLY;EIGEN_DONT_VECTORIZE;EIGEN_VECTORIZE_SSE3;PREFIX="/usr/local";GITSHORT="c5cf0fd";HOST="wfview.org";UNAME="build";NDEBUG;QT_NO_DEBUG;%(PreprocessorDefinitions) + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION="1.2d";BUILD_WFVIEW;QT_DEPRECATED_WARNINGS;QCUSTOMPLOT_COMPILE_LIBRARY;USE_SSE;USE_SSE2;OUTSIDE_SPEEX;RANDOM_PREFIX=wf;EIGEN_MPL2_ONLY;EIGEN_DONT_VECTORIZE;EIGEN_VECTORIZE_SSE3;PREFIX="/usr/local";GITSHORT="8ec62fe";HOST="wfview.org";UNAME="build";NDEBUG;QT_NO_DEBUG;%(PreprocessorDefinitions) false MultiThreadedDLL @@ -85,12 +85,12 @@ 0 - _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION=\"1.2d\";BUILD_WFVIEW;QT_DEPRECATED_WARNINGS;QCUSTOMPLOT_COMPILE_LIBRARY;USE_SSE;USE_SSE2;OUTSIDE_SPEEX;RANDOM_PREFIX=wf;EIGEN_MPL2_ONLY;EIGEN_DONT_VECTORIZE;EIGEN_VECTORIZE_SSE3;PREFIX=\"/usr/local\";GITSHORT=\"c5cf0fd\";HOST=\"wfview.org\";UNAME=\"build\";NDEBUG;QT_NO_DEBUG;QT_MULTIMEDIA_LIB;QT_PRINTSUPPORT_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_SERIALPORT_LIB;QT_NETWORK_LIB;QT_CORE_LIB;%(PreprocessorDefinitions) + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION=\"1.2d\";BUILD_WFVIEW;QT_DEPRECATED_WARNINGS;QCUSTOMPLOT_COMPILE_LIBRARY;USE_SSE;USE_SSE2;OUTSIDE_SPEEX;RANDOM_PREFIX=wf;EIGEN_MPL2_ONLY;EIGEN_DONT_VECTORIZE;EIGEN_VECTORIZE_SSE3;PREFIX=\"/usr/local\";GITSHORT=\"8ec62fe\";HOST=\"wfview.org\";UNAME=\"build\";NDEBUG;QT_NO_DEBUG;QT_MULTIMEDIA_LIB;QT_PRINTSUPPORT_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_SERIALPORT_LIB;QT_NETWORK_LIB;QT_CORE_LIB;%(PreprocessorDefinitions) msvc./$(Configuration)/moc_predefs.hMoc'ing %(Identity)...output$(Configuration)moc_%(Filename).cppdefaultRcc'ing %(Identity)...$(Configuration)qrc_%(Filename).cppUic'ing %(Identity)...$(ProjectDir)ui_%(Filename).h - .;..\qcustomplot;..\opus\include;..\eigen\Eigen;resampler;debug;/include;%(AdditionalIncludeDirectories) + .;..\qcustomplot;..\opus\include;..\eigen;..\r8brain-free-src;resampler;debug;/include;%(AdditionalIncludeDirectories) -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) debug\ false @@ -99,7 +99,7 @@ Sync debug\ Disabled - _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION="1.2d";BUILD_WFVIEW;QT_DEPRECATED_WARNINGS;QCUSTOMPLOT_COMPILE_LIBRARY;USE_SSE;USE_SSE2;OUTSIDE_SPEEX;RANDOM_PREFIX=wf;EIGEN_MPL2_ONLY;EIGEN_DONT_VECTORIZE;EIGEN_VECTORIZE_SSE3;PREFIX="/usr/local";GITSHORT="c5cf0fd";HOST="wfview.org";UNAME="build";%(PreprocessorDefinitions) + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION="1.2d";BUILD_WFVIEW;QT_DEPRECATED_WARNINGS;QCUSTOMPLOT_COMPILE_LIBRARY;USE_SSE;USE_SSE2;OUTSIDE_SPEEX;RANDOM_PREFIX=wf;EIGEN_MPL2_ONLY;EIGEN_DONT_VECTORIZE;EIGEN_VECTORIZE_SSE3;PREFIX="/usr/local";GITSHORT="8ec62fe";HOST="wfview.org";UNAME="build";%(PreprocessorDefinitions) false MultiThreadedDebugDLL true @@ -124,7 +124,7 @@ 0 - _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION=\"1.2d\";BUILD_WFVIEW;QT_DEPRECATED_WARNINGS;QCUSTOMPLOT_COMPILE_LIBRARY;USE_SSE;USE_SSE2;OUTSIDE_SPEEX;RANDOM_PREFIX=wf;EIGEN_MPL2_ONLY;EIGEN_DONT_VECTORIZE;EIGEN_VECTORIZE_SSE3;PREFIX=\"/usr/local\";GITSHORT=\"c5cf0fd\";HOST=\"wfview.org\";UNAME=\"build\";QT_MULTIMEDIA_LIB;QT_PRINTSUPPORT_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_SERIALPORT_LIB;QT_NETWORK_LIB;QT_CORE_LIB;_DEBUG;%(PreprocessorDefinitions) + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION=\"1.2d\";BUILD_WFVIEW;QT_DEPRECATED_WARNINGS;QCUSTOMPLOT_COMPILE_LIBRARY;USE_SSE;USE_SSE2;OUTSIDE_SPEEX;RANDOM_PREFIX=wf;EIGEN_MPL2_ONLY;EIGEN_DONT_VECTORIZE;EIGEN_VECTORIZE_SSE3;PREFIX=\"/usr/local\";GITSHORT=\"8ec62fe\";HOST=\"wfview.org\";UNAME=\"build\";QT_MULTIMEDIA_LIB;QT_PRINTSUPPORT_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_SERIALPORT_LIB;QT_NETWORK_LIB;QT_CORE_LIB;_DEBUG;%(PreprocessorDefinitions) msvc./$(Configuration)/moc_predefs.hMoc'ing %(Identity)...output$(Configuration)moc_%(Filename).cppdefaultRcc'ing %(Identity)...$(Configuration)qrc_%(Filename).cppUic'ing %(Identity)...$(ProjectDir)ui_%(Filename).h @@ -165,6 +165,7 @@ + diff --git a/wfview.vcxproj.filters b/wfview.vcxproj.filters index bc556d8..d21b5b3 100644 --- a/wfview.vcxproj.filters +++ b/wfview.vcxproj.filters @@ -127,6 +127,9 @@ Header Files + + Header Files + Header Files