From 44f6ec27402555c2c3cde7c6a59bf9d50b725286 Mon Sep 17 00:00:00 2001 From: Phil Taylor Date: Thu, 12 May 2022 01:46:20 +0100 Subject: [PATCH] Add back support for portaudo and rtaudio APIs --- audioconverter.h | 23 +- audiohandler.cpp | 1 - audiohandler.h | 35 --- pahandler.cpp | 275 ++++++++++++++++++++++++ pahandler.h | 93 ++++++++ rthandler.cpp | 334 +++++++++++++++++++++++++++++ rthandler.h | 111 ++++++++++ udpaudio.cpp | 31 ++- udpaudio.h | 6 +- wfmain.cpp | 370 +++++++++++++++++++++++--------- wfmain.h | 11 + wfmain.ui | 28 ++- wfserver.pro | 43 +++- wfview.pro | 45 ++-- wfview.vcxproj | 467 +++++++++++++++++++++++++++++++---------- wfview.vcxproj.filters | 127 ++++++++--- 16 files changed, 1698 insertions(+), 302 deletions(-) create mode 100644 pahandler.cpp create mode 100644 pahandler.h create mode 100644 rthandler.cpp create mode 100644 rthandler.h diff --git a/audioconverter.h b/audioconverter.h index 00d75a1..a0d2953 100644 --- a/audioconverter.h +++ b/audioconverter.h @@ -6,6 +6,7 @@ #include #include #include +#include /* Opus and Eigen */ #ifdef Q_OS_WIN @@ -16,6 +17,8 @@ #include #endif +enum audioType {qtAudio,portAudio,rtAudio}; + #include "resampler/speex_resampler.h" #include "packettypes.h" @@ -30,6 +33,22 @@ struct audioPacket { qreal volume = 1.0; }; +struct audioSetup { + audioType type; + QString name; + quint16 latency; + quint8 codec; + bool ulaw = false; + bool isinput; + quint32 sampleRate; + QAudioDeviceInfo port; + int portInt; + quint8 resampleQuality; + unsigned char localAFgain; + quint16 blockSize = 20; // Each 'block' of audio is 20ms long by default. + quint8 guid[GUIDLEN]; +}; + class audioConverter : public QObject { Q_OBJECT @@ -80,8 +99,8 @@ static inline QAudioFormat toQAudioFormat(quint8 codec, quint32 sampleRate) 0x80 Opus 2ch */ - format.setByteOrder(QAudioFormat::LittleEndian); - format.setCodec("audio/pcm"); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setCodec("audio/pcm"); format.setSampleRate(sampleRate); if (codec == 0x01 || codec == 0x20) { diff --git a/audiohandler.cpp b/audiohandler.cpp index 51d015d..d05b102 100644 --- a/audiohandler.cpp +++ b/audiohandler.cpp @@ -330,7 +330,6 @@ void audioHandler::changeLatency(const quint16 newSize) qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Configured latency: " << setup.latency << "Buffer Duration:" << outFormat.durationForBytes(audioOutput->bufferSize())/1000 << "ms"; } - int audioHandler::getLatency() { return currentLatency; diff --git a/audiohandler.h b/audiohandler.h index 8bc8103..47e6412 100644 --- a/audiohandler.h +++ b/audiohandler.h @@ -20,27 +20,6 @@ #include #include -/* Current resampler code */ -#include "resampler/speex_resampler.h" - -/* Potential new resampler */ -//#include -//#include - - -/* Opus */ -#ifdef Q_OS_WIN -#include "opus.h" -#else -#include "opus/opus.h" -#endif - -/* Eigen */ -#ifndef Q_OS_WIN -#include -#else -#include -#endif /* wfview Packet types */ #include "packettypes.h" @@ -57,20 +36,6 @@ #define MULAW_MAX 0x1fff -struct audioSetup { - QString name; - quint16 latency; - quint8 codec; - bool ulaw = false; - bool isinput; - quint32 sampleRate; - 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 //class audioHandler : public QIODevice class audioHandler : public QObject diff --git a/pahandler.cpp b/pahandler.cpp new file mode 100644 index 0000000..7014ce2 --- /dev/null +++ b/pahandler.cpp @@ -0,0 +1,275 @@ +#include "pahandler.h" + +#include "logcategories.h" + +#if defined(Q_OS_WIN) +#include +#endif + + +paHandler::paHandler(QObject* parent) +{ + Q_UNUSED(parent) +} + +paHandler::~paHandler() +{ + + if (isInitialized) { + Pa_StopStream(audio); + Pa_CloseStream(audio); + } + + if (converterThread != Q_NULLPTR) { + converterThread->quit(); + converterThread->wait(); + } + + //Pa_Terminate(); + +} + +bool paHandler::init(audioSetup setup) +{ + if (isInitialized) { + return false; + } + + this->setup = setup; + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "PortAudio handler starting:" << setup.name; + + if (setup.portInt==-1) + { + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "No audio device was found."; + return false; + } + + inFormat = toQAudioFormat(setup.codec, setup.sampleRate); + + qDebug(logAudio()) << "Creating" << (setup.isinput ? "Input" : "Output") << "audio device:" << setup.name << + ", bits" << inFormat.sampleSize() << + ", codec" << setup.codec << + ", latency" << setup.latency << + ", localAFGain" << setup.localAFgain << + ", radioChan" << inFormat.channelCount() << + ", resampleQuality" << setup.resampleQuality << + ", samplerate" << inFormat.sampleRate() << + ", uLaw" << setup.ulaw; + + PaError err; +#ifdef Q_OS_WIN + CoInitialize(0); +#endif + + //err = Pa_Initialize(); + //if (err != paNoError) + //{ + // qDebug(logAudio()) << "Portaudio initialized"; + //} + + memset(&aParams, 0, sizeof(PaStreamParameters)); + + aParams.device = setup.portInt; + info = Pa_GetDeviceInfo(aParams.device); + + qDebug(logAudio()) << "PortAudio" << (setup.isinput ? "Input" : "Output") << setup.portInt << "Input Channels" << info->maxInputChannels << "Output Channels" << info->maxOutputChannels; + + + if (setup.isinput) { + outFormat.setChannelCount(info->maxInputChannels); + } + else { + outFormat.setChannelCount(info->maxOutputChannels); + } + + aParams.suggestedLatency = (float)setup.latency/1000.0f; + outFormat.setSampleRate(info->defaultSampleRate); + aParams.sampleFormat = paFloat32; + outFormat.setSampleSize(32); + outFormat.setSampleType(QAudioFormat::Float); + outFormat.setByteOrder(QAudioFormat::LittleEndian); + outFormat.setCodec("audio/pcm"); + + + if (!setup.isinput) + { + this->setVolume(setup.localAFgain); + } + + if (outFormat.channelCount() > 2) { + outFormat.setChannelCount(2); + } + else if (outFormat.channelCount() < 1) + { + qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "No channels found, aborting setup."; + return false; + } + + if (inFormat.channelCount() < outFormat.channelCount()) { + outFormat.setChannelCount(inFormat.channelCount()); + } + + aParams.channelCount = outFormat.channelCount(); + + if (outFormat.sampleRate() < 44100) { + outFormat.setSampleRate(48000); + } + + + qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Selected format: SampleSize" << outFormat.sampleSize() << "Channel Count" << outFormat.channelCount() << + "Sample Rate" << outFormat.sampleRate() << "Codec" << outFormat.codec() << "Sample Type" << outFormat.sampleType(); + + // We "hopefully" now have a valid format that is supported so try connecting + + converter = new audioConverter(); + converterThread = new QThread(this); + if (setup.isinput) { + converterThread->setObjectName("audioConvIn()"); + } + else { + converterThread->setObjectName("audioConvOut()"); + } + converter->moveToThread(converterThread); + + connect(this, SIGNAL(setupConverter(QAudioFormat, QAudioFormat, quint8, quint8)), converter, SLOT(init(QAudioFormat, QAudioFormat, quint8, quint8))); + connect(converterThread, SIGNAL(finished()), converter, SLOT(deleteLater())); + connect(this, SIGNAL(sendToConverter(audioPacket)), converter, SLOT(convert(audioPacket))); + converterThread->start(QThread::TimeCriticalPriority); + + aParams.hostApiSpecificStreamInfo = NULL; + + // Per channel chunk size. + this->chunkSize = (outFormat.bytesForDuration(setup.blockSize*1000)/sizeof(float))*outFormat.channelCount(); + + + if (setup.isinput) { + err = Pa_OpenStream(&audio, &aParams, 0, outFormat.sampleRate(), this->chunkSize, paNoFlag, &paHandler::staticWrite, (void*)this); + emit setupConverter(outFormat, inFormat, 7, setup.resampleQuality); + connect(converter, SIGNAL(converted(audioPacket)), this, SLOT(convertedInput(audioPacket))); + } + else { + err = Pa_OpenStream(&audio, 0, &aParams, outFormat.sampleRate(), this->chunkSize, paNoFlag, NULL, NULL); + emit setupConverter(inFormat, outFormat, 7, setup.resampleQuality); + connect(converter, SIGNAL(converted(audioPacket)), this, SLOT(convertedOutput(audioPacket))); + } + + if (err == paNoError) { + err = Pa_StartStream(audio); + } + if (err == paNoError) { + isInitialized = true; + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "device successfully opened"; + } + else { + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "failed to open device" << Pa_GetErrorText(err); + } + + return isInitialized; +} + + +void paHandler::setVolume(unsigned char volume) +{ + + this->volume = audiopot[volume]; + + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "setVolume: " << volume << "(" << this->volume << ")"; +} + +void paHandler::incomingAudio(audioPacket packet) +{ + packet.volume = volume; + emit sendToConverter(packet); + return; +} + + + +int paHandler::writeData(const void* inputBuffer, void* outputBuffer, + unsigned long nFrames, const PaStreamCallbackTimeInfo * streamTime, + PaStreamCallbackFlags status) +{ + Q_UNUSED(outputBuffer); + Q_UNUSED(streamTime); + Q_UNUSED(status); + audioPacket packet; + packet.time = QTime::currentTime(); + packet.sent = 0; + packet.volume = volume; + memcpy(&packet.guid, setup.guid, GUIDLEN); + packet.data.append((char*)inputBuffer, nFrames*inFormat.channelCount()*sizeof(float)); + emit sendToConverter(packet); + + return paContinue; +} + + +void paHandler::convertedOutput(audioPacket packet) { + + if (packet.data.size() > 0) { + + if (Pa_IsStreamActive(audio) == 1) { + PaError err = Pa_WriteStream(audio, (char*)packet.data.data(), packet.data.size() / sizeof(float) / outFormat.channelCount()); + + if (err != paNoError) { + qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Error writing audio!"; + } + const PaStreamInfo* info = Pa_GetStreamInfo(audio); + + //currentLatency = packet.time.msecsTo(QTime::currentTime()) + (info->outputLatency * 1000); + currentLatency = (info->outputLatency * 1000); + + } + /* + currentLatency = packet.time.msecsTo(QTime::currentTime()) + (outFormat.durationForBytes(audioOutput->bufferSize() - audioOutput->bytesFree()) / 1000); + + if (audioDevice != Q_NULLPTR) { + if (audioDevice->write(packet.data) < packet.data.size()) { + qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Buffer full!"; + isOverrun = true; + } + else { + isOverrun = false; + } + if (lastReceived.msecsTo(QTime::currentTime()) > 100) { + qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Time since last audio packet" << lastReceived.msecsTo(QTime::currentTime()) << "Expected around" << setup.blockSize; + } + lastReceived = QTime::currentTime(); + } + + lastSentSeq = packet.seq; + + */ + amplitude = packet.amplitude; + emit haveLevels(getAmplitude(), setup.latency, currentLatency, false, false); + } +} + + + +void paHandler::convertedInput(audioPacket audio) +{ + if (audio.data.size() > 0) { + emit haveAudioData(audio); + amplitude = audio.amplitude; + emit haveLevels(getAmplitude(), setup.latency, currentLatency, false,false); + } +} + + + +void paHandler::changeLatency(const quint16 newSize) +{ + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Changing latency to: " << newSize << " from " << setup.latency; +} + +int paHandler::getLatency() +{ + return currentLatency; +} + + +quint16 paHandler::getAmplitude() +{ + return amplitude; +} diff --git a/pahandler.h b/pahandler.h new file mode 100644 index 0000000..5e311aa --- /dev/null +++ b/pahandler.h @@ -0,0 +1,93 @@ +#ifndef PAHANDLER_H +#define PAHANDLER_H + +#include +#include +#include + +#include "portaudio.h" + +#include +#include +#include + + +/* wfview Packet types */ +#include "packettypes.h" + +/* Logarithmic taper for volume control */ +#include "audiotaper.h" + + +/* Audio converter class*/ +#include "audioconverter.h" + +#include + + +class paHandler : public QObject +{ + Q_OBJECT + +public: + paHandler(QObject* parent = 0); + ~paHandler(); + + int getLatency(); + + + void getNextAudioChunk(QByteArray& data); + quint16 getAmplitude(); + +public slots: + bool init(audioSetup setup); + void changeLatency(const quint16 newSize); + void setVolume(unsigned char volume); + void convertedInput(audioPacket audio); + void convertedOutput(audioPacket audio); + void incomingAudio(const audioPacket data); + + +private slots: + +signals: + void audioMessage(QString message); + void sendLatency(quint16 newSize); + void haveAudioData(const audioPacket& data); + void haveLevels(quint16 amplitude, quint16 latency, quint16 current, bool under, bool over); + void setupConverter(QAudioFormat in, QAudioFormat out, quint8 opus, quint8 resamp); + void sendToConverter(audioPacket audio); + +private: + + int writeData(const void* inputBuffer, void* outputBuffer, + unsigned long nFrames, + const PaStreamCallbackTimeInfo* streamTime, + PaStreamCallbackFlags status); + static int staticWrite(const void* inputBuffer, void* outputBuffer, unsigned long nFrames, const PaStreamCallbackTimeInfo* streamTime, PaStreamCallbackFlags status, void* userData) { + return ((paHandler*)userData)->writeData(inputBuffer, outputBuffer, nFrames, streamTime, status); + } + + bool isInitialized = false; + PaStream* audio = Q_NULLPTR; + PaStreamParameters aParams; + const PaDeviceInfo* info; + + quint16 audioLatency; + unsigned int chunkSize; + + quint32 lastSeq; + quint32 lastSentSeq = 0; + + quint16 currentLatency; + quint16 amplitude = 0; + qreal volume = 1.0; + + audioSetup setup; + QAudioFormat inFormat; + QAudioFormat outFormat; + audioConverter* converter = Q_NULLPTR; + QThread* converterThread = Q_NULLPTR; +}; + +#endif // PAHANDLER_H diff --git a/rthandler.cpp b/rthandler.cpp new file mode 100644 index 0000000..44674c6 --- /dev/null +++ b/rthandler.cpp @@ -0,0 +1,334 @@ +#include "rthandler.h" + +#include "logcategories.h" + +#if defined(Q_OS_WIN) +#include +#endif + + +rtHandler::rtHandler(QObject* parent) +{ + Q_UNUSED(parent) +} + +rtHandler::~rtHandler() +{ + + if (isInitialized) { + try { + audio->abortStream(); + audio->closeStream(); + } + catch (RtAudioError& e) { + qInfo(logAudio()) << "Error closing stream:" << aParams.deviceId << ":" << QString::fromStdString(e.getMessage()); + } + delete audio; + + } + + if (converterThread != Q_NULLPTR) { + converterThread->quit(); + converterThread->wait(); + } + +} + +bool rtHandler::init(audioSetup setup) +{ + if (isInitialized) { + return false; + } + + this->setup = setup; + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "PortAudio handler starting:" << setup.name; + + if (setup.portInt==-1) + { + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "No audio device was found."; + return false; + } + + inFormat = toQAudioFormat(setup.codec, setup.sampleRate); + + qDebug(logAudio()) << "Creating" << (setup.isinput ? "Input" : "Output") << "audio device:" << setup.name << + ", bits" << inFormat.sampleSize() << + ", codec" << setup.codec << + ", latency" << setup.latency << + ", localAFGain" << setup.localAFgain << + ", radioChan" << inFormat.channelCount() << + ", resampleQuality" << setup.resampleQuality << + ", samplerate" << inFormat.sampleRate() << + ", uLaw" << setup.ulaw; + +#if !defined(Q_OS_MACX) + //options.flags = !RTAUDIO_HOG_DEVICE | RTAUDIO_MINIMIZE_LATENCY; + //options.flags = RTAUDIO_MINIMIZE_LATENCY; +#endif + +#if defined(Q_OS_LINUX) + audio = new RtAudio(RtAudio::Api::LINUX_ALSA); +#elif defined(Q_OS_WIN) + audio = new RtAudio(RtAudio::Api::WINDOWS_WASAPI); +#elif defined(Q_OS_MACX) + audio = new RtAudio(RtAudio::Api::MACOSX_CORE); +#endif + + options.numberOfBuffers = setup.latency/setup.blockSize; + + if (setup.portInt > 0) { + aParams.deviceId = setup.portInt; + } + else if (setup.isinput) { + aParams.deviceId = audio->getDefaultInputDevice(); + } + else { + aParams.deviceId = audio->getDefaultOutputDevice(); + } + aParams.firstChannel = 0; + + try { + info = audio->getDeviceInfo(aParams.deviceId); + } + catch (RtAudioError& e) { + qInfo(logAudio()) << "Device error:" << aParams.deviceId << ":" << QString::fromStdString(e.getMessage()); + return isInitialized; + } + + if (info.probed) + { + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << QString::fromStdString(info.name) << "(" << aParams.deviceId << ") successfully probed"; + + RtAudioFormat sampleFormat; + outFormat.setByteOrder(QAudioFormat::LittleEndian); + outFormat.setCodec("audio/pcm"); + + if (info.nativeFormats == 0) + { + qCritical(logAudio()) << " No natively supported data formats!"; + return false; + } + else { + qDebug(logAudio()) << " Supported formats:" << + (info.nativeFormats & RTAUDIO_SINT8 ? "8-bit int," : "") << + (info.nativeFormats & RTAUDIO_SINT16 ? "16-bit int," : "") << + (info.nativeFormats & RTAUDIO_SINT24 ? "24-bit int," : "") << + (info.nativeFormats & RTAUDIO_SINT32 ? "32-bit int," : "") << + (info.nativeFormats & RTAUDIO_FLOAT32 ? "32-bit float," : "") << + (info.nativeFormats & RTAUDIO_FLOAT64 ? "64-bit float," : ""); + + qInfo(logAudio()) << " Preferred sample rate:" << info.preferredSampleRate; + if (setup.isinput) { + outFormat.setChannelCount(info.inputChannels); + } + else { + outFormat.setChannelCount(info.outputChannels); + } + + qInfo(logAudio()) << " Channels:" << outFormat.channelCount(); + + if (outFormat.channelCount() > 2) { + outFormat.setChannelCount(2); + } + else if (outFormat.channelCount() < 1) + { + qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "No channels found, aborting setup."; + return false; + } + + aParams.nChannels = outFormat.channelCount(); + + + outFormat.setSampleRate(info.preferredSampleRate); + + if (outFormat.sampleRate() < 44100) { + outFormat.setSampleRate(48000); + } + + if (info.nativeFormats & RTAUDIO_FLOAT32) { + outFormat.setSampleType(QAudioFormat::Float); + outFormat.setSampleSize(32); + sampleFormat = RTAUDIO_FLOAT32; + } + else if (info.nativeFormats & RTAUDIO_SINT32) { + outFormat.setSampleType(QAudioFormat::SignedInt); + outFormat.setSampleSize(32); + sampleFormat = RTAUDIO_SINT32; + } + else if (info.nativeFormats & RTAUDIO_SINT16) { + outFormat.setSampleType(QAudioFormat::SignedInt); + outFormat.setSampleSize(16); + sampleFormat = RTAUDIO_SINT16; + } + else { + qCritical(logAudio()) << "Cannot find supported sample format!"; + return false; + } + } + + + qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Selected format: SampleSize" << outFormat.sampleSize() << "Channel Count" << outFormat.channelCount() << + "Sample Rate" << outFormat.sampleRate() << "Codec" << outFormat.codec() << "Sample Type" << outFormat.sampleType(); + + // We "hopefully" now have a valid format that is supported so try connecting + converter = new audioConverter(); + converterThread = new QThread(this); + if (setup.isinput) { + converterThread->setObjectName("audioConvIn()"); + } + else { + converterThread->setObjectName("audioConvOut()"); + } + converter->moveToThread(converterThread); + + connect(this, SIGNAL(setupConverter(QAudioFormat, QAudioFormat, quint8, quint8)), converter, SLOT(init(QAudioFormat, QAudioFormat, quint8, quint8))); + connect(converterThread, SIGNAL(finished()), converter, SLOT(deleteLater())); + connect(this, SIGNAL(sendToConverter(audioPacket)), converter, SLOT(convert(audioPacket))); + converterThread->start(QThread::TimeCriticalPriority); + + + // Per channel chunk size. + this->chunkSize = (outFormat.bytesForDuration(setup.blockSize * 1000) / (outFormat.sampleSize()/8) / outFormat.channelCount()); + + try { + if (setup.isinput) { + audio->openStream(NULL, &aParams, sampleFormat, outFormat.sampleRate(), &this->chunkSize, &staticWrite, this, &options); + emit setupConverter(outFormat, inFormat, 7, setup.resampleQuality); + connect(converter, SIGNAL(converted(audioPacket)), this, SLOT(convertedInput(audioPacket))); + } + else { + audio->openStream(&aParams, NULL, sampleFormat, outFormat.sampleRate(), &this->chunkSize, &staticRead, this , &options); + emit setupConverter(inFormat, outFormat, 7, setup.resampleQuality); + connect(converter, SIGNAL(converted(audioPacket)), this, SLOT(convertedOutput(audioPacket))); + } + audio->startStream(); + isInitialized = true; + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "device successfully opened"; + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "detected latency:" << audio->getStreamLatency(); + } + catch (RtAudioError& e) { + qInfo(logAudio()) << "Error opening:" << QString::fromStdString(e.getMessage()); + } + } + else + { + qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << QString::fromStdString(info.name) << "(" << aParams.deviceId << ") could not be probed, check audio configuration!"; + } + + if (!setup.isinput) + { + this->setVolume(setup.localAFgain); + } + + + return isInitialized; +} + + +void rtHandler::setVolume(unsigned char volume) +{ + + this->volume = audiopot[volume]; + + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "setVolume: " << volume << "(" << this->volume << ")"; +} + +void rtHandler::incomingAudio(audioPacket packet) +{ + packet.volume = volume; + emit sendToConverter(packet); + return; +} + +int rtHandler::readData(void* outputBuffer, void* inputBuffer, + unsigned int nFrames, double streamTime, RtAudioStreamStatus status) +{ + Q_UNUSED(inputBuffer); + Q_UNUSED(streamTime); + int nBytes = nFrames * outFormat.channelCount() * (outFormat.sampleSize()/8); + + //lastSentSeq = packet.seq; + if (arrayBuffer.length() >= nBytes) { + std::memcpy(outputBuffer, arrayBuffer.constData(), nBytes); + arrayBuffer.remove(0, nBytes); + } + + if (status == RTAUDIO_INPUT_OVERFLOW) { + isUnderrun = true; + } + else if (status == RTAUDIO_OUTPUT_UNDERFLOW) { + isOverrun = true; + } + else + { + isUnderrun = false; + isOverrun = false; + } + return 0; +} + + +int rtHandler::writeData(void* outputBuffer, void* inputBuffer, + unsigned int nFrames, double streamTime, RtAudioStreamStatus status) +{ + Q_UNUSED(outputBuffer); + Q_UNUSED(streamTime); + Q_UNUSED(status); + audioPacket packet; + packet.time = QTime::currentTime(); + packet.sent = 0; + packet.volume = volume; + memcpy(&packet.guid, setup.guid, GUIDLEN); + packet.data.append((char*)inputBuffer, nFrames *outFormat.channelCount() * (outFormat.sampleSize()/8)); + emit sendToConverter(packet); + if (status == RTAUDIO_INPUT_OVERFLOW) { + isUnderrun = true; + } + else if (status == RTAUDIO_OUTPUT_UNDERFLOW) { + isOverrun = true; + } + else + { + isUnderrun = false; + isOverrun = false; + } + + return 0; +} + + +void rtHandler::convertedOutput(audioPacket packet) +{ + arrayBuffer.append(packet.data); + currentLatency = packet.time.msecsTo(QTime::currentTime()) + (outFormat.durationForBytes(audio->getStreamLatency() * (outFormat.sampleSize() / 8) * outFormat.channelCount()) * 1000); + emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun, isOverrun); +} + + + +void rtHandler::convertedInput(audioPacket audio) +{ + if (audio.data.size() > 0) { + emit haveAudioData(audio); + amplitude = audio.amplitude; + emit haveLevels(getAmplitude(), setup.latency, currentLatency, isUnderrun, isOverrun); + } +} + + + +void rtHandler::changeLatency(const quint16 newSize) +{ + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Changing latency to: " << newSize << " from " << setup.latency; +} + +int rtHandler::getLatency() +{ + return currentLatency; +} + + +quint16 rtHandler::getAmplitude() +{ + return amplitude; +} diff --git a/rthandler.h b/rthandler.h new file mode 100644 index 0000000..cb8ec58 --- /dev/null +++ b/rthandler.h @@ -0,0 +1,111 @@ +#ifndef rtHandler_H +#define rtHandler_H + +#include +#include +#include + +#ifdef Q_OS_WIN +#include "RtAudio.h" +#else +#include "rtaudio/RtAudio.h" +#endif + + +#include +#include +#include + + +/* wfview Packet types */ +#include "packettypes.h" + +/* Logarithmic taper for volume control */ +#include "audiotaper.h" + + +/* Audio converter class*/ +#include "audioconverter.h" + +#include + + +class rtHandler : public QObject +{ + Q_OBJECT + +public: + rtHandler(QObject* parent = 0); + ~rtHandler(); + + int getLatency(); + + + void getNextAudioChunk(QByteArray& data); + quint16 getAmplitude(); + +public slots: + bool init(audioSetup setup); + void changeLatency(const quint16 newSize); + void setVolume(unsigned char volume); + void convertedInput(audioPacket audio); + void convertedOutput(audioPacket audio); + void incomingAudio(const audioPacket data); + + +private slots: + +signals: + void audioMessage(QString message); + void sendLatency(quint16 newSize); + void haveAudioData(const audioPacket& data); + void haveLevels(quint16 amplitude, quint16 latency, quint16 current, bool under, bool over); + void setupConverter(QAudioFormat in, QAudioFormat out, quint8 opus, quint8 resamp); + void sendToConverter(audioPacket audio); + +private: + + + int readData(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status); + + static int staticRead(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) { + return static_cast(userData)->readData(outputBuffer, inputBuffer, nFrames, streamTime, status); + } + + + int writeData(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status); + + static int staticWrite(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) { + return static_cast(userData)->writeData(outputBuffer, inputBuffer, nFrames, streamTime, status); + } + + + bool isInitialized = false; + + RtAudio* audio = Q_NULLPTR; + int audioDevice = 0; + RtAudio::StreamParameters aParams; + RtAudio::StreamOptions options; + RtAudio::DeviceInfo info; + + quint16 audioLatency; + unsigned int chunkSize; + + quint32 lastSeq; + quint32 lastSentSeq = 0; + + quint16 currentLatency; + quint16 amplitude = 0; + qreal volume = 1.0; + + audioSetup setup; + QAudioFormat inFormat; + QAudioFormat outFormat; + audioConverter* converter = Q_NULLPTR; + QThread* converterThread = Q_NULLPTR; + QByteArray arrayBuffer; + bool isUnderrun = false; + bool isOverrun = true; +}; + +#endif // rtHandler_H diff --git a/udpaudio.cpp b/udpaudio.cpp index 39ef3bf..b49359d 100644 --- a/udpaudio.cpp +++ b/udpaudio.cpp @@ -16,8 +16,20 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint init(lport); // Perform connection QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpAudio::dataReceived); + if (rxSetup.type == qtAudio) { + rxaudio = new audioHandler(); + } + else if (rxSetup.type == portAudio) { + rxaudio = new paHandler(); + } + else if (rxSetup.type == rtAudio) { + rxaudio = new rtHandler(); + } + else + { + qCritical(logAudio()) << "Unsupported Receive Audio Handler selected!"; + } - rxaudio = new audioHandler(); rxAudioThread = new QThread(this); rxAudioThread->setObjectName("rxAudio()"); @@ -42,7 +54,20 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint pingTimer->start(PING_PERIOD); // send ping packets every 100ms if (enableTx) { - txaudio = new audioHandler(); + if (txSetup.type == qtAudio) { + txaudio = new audioHandler(); + } + else if (txSetup.type == portAudio) { + txaudio = new paHandler(); + } + else if (txSetup.type == rtAudio) { + txaudio = new rtHandler(); + } + else + { + qCritical(logAudio()) << "Unsupported Transmit Audio Handler selected!"; + } + txAudioThread = new QThread(this); rxAudioThread->setObjectName("txAudio()"); @@ -52,7 +77,7 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint 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(txaudio, SIGNAL(haveLevels(quint16, quint16, quint16, bool, bool)), this, SLOT(getTxLevels(quint16, quint16, quint16, bool, bool))); connect(txAudioThread, SIGNAL(finished()), txaudio, SLOT(deleteLater())); emit setupTxAudio(txSetup); diff --git a/udpaudio.h b/udpaudio.h index dc726ea..478c342 100644 --- a/udpaudio.h +++ b/udpaudio.h @@ -28,6 +28,8 @@ #include "udpbase.h" #include "audiohandler.h" +#include "pahandler.h" +#include "rthandler.h" // Class for all audio communications. @@ -67,10 +69,10 @@ private: uint16_t sendAudioSeq = 0; - audioHandler* rxaudio = Q_NULLPTR; + QObject* rxaudio = Q_NULLPTR; QThread* rxAudioThread = Q_NULLPTR; - audioHandler* txaudio = Q_NULLPTR; + QObject* txaudio = Q_NULLPTR; QThread* txAudioThread = Q_NULLPTR; QTimer* txAudioTimer = Q_NULLPTR; diff --git a/wfmain.cpp b/wfmain.cpp index 26b86a0..3789bf1 100644 --- a/wfmain.cpp +++ b/wfmain.cpp @@ -56,8 +56,6 @@ wfmain::wfmain(const QString serialPortCL, const QString hostCL, const QString s setSerialDevicesUI(); - setAudioDevicesUI(); - setDefaultColors(); setDefPrefs(); @@ -65,6 +63,9 @@ wfmain::wfmain(const QString serialPortCL, const QString hostCL, const QString s setupPlots(); loadSettings(); // Look for saved preferences + + setAudioDevicesUI(); + setTuningSteps(); // TODO: Combine into preferences qDebug(logSystem()) << "Running setUIToPrefs()"; @@ -102,6 +103,10 @@ wfmain::~wfmain() if (rigCtl != Q_NULLPTR) { delete rigCtl; } + + if (prefs.audioSystem == portAudio) { + Pa_Terminate(); + } delete rpt; delete ui; delete settings; @@ -1061,43 +1066,6 @@ void wfmain::setUIToPrefs() ui->useCIVasRigIDChk->blockSignals(false); } -void wfmain::setAudioDevicesUI() -{ - - // Enumerate audio devices, need to do before settings are loaded. - qDebug(logSystem()) << "Finding audio output devices"; - const auto audioOutputs = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); - for (const QAudioDeviceInfo& deviceInfo : audioOutputs) { -#ifdef Q_OS_WIN - if (deviceInfo.realm() == "wasapi") { -#endif - ui->audioOutputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); - ui->serverTXAudioOutputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); -#ifdef Q_OS_WIN - } - -#endif - } - - qDebug(logSystem()) << "Finding audio input devices"; - const auto audioInputs = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); - for (const QAudioDeviceInfo& deviceInfo : audioInputs) { -#ifdef Q_OS_WIN - if (deviceInfo.realm() == "wasapi") { -#endif - ui->audioInputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); - ui->serverRXAudioInputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); -#ifdef Q_OS_WIN - } -#endif - } - // Set these to default audio devices initially. - qDebug(logSystem()) << "Audio devices done."; - rxSetup.port = QAudioDeviceInfo::defaultOutputDevice(); - txSetup.port = QAudioDeviceInfo::defaultInputDevice(); - qDebug(logSystem()) << "Audio set to default device initially"; -} - void wfmain::setSerialDevicesUI() { ui->serialDeviceListCombo->blockSignals(true); @@ -1302,6 +1270,7 @@ void wfmain::setDefPrefs() defPrefs.meter2Type = meterNone; defPrefs.tcpPort = 0; defPrefs.waterfallFormat = 0; + defPrefs.audioSystem = qtAudio; udpDefPrefs.ipAddress = QString(""); udpDefPrefs.controlLANPort = 50001; @@ -1422,6 +1391,12 @@ void wfmain::loadSettings() rxSetup.localAFgain = prefs.localAFgain; txSetup.localAFgain = 255; + prefs.audioSystem = static_cast(settings->value("AudioSystem", defPrefs.audioSystem).toInt()); + ui->audioSystemCombo->blockSignals(true); + ui->audioSystemCombo->setCurrentIndex(prefs.audioSystem); + ui->audioSystemCombo->blockSignals(false); + + settings->endGroup(); // Misc. user settings (enable PTT, draw peaks, etc) @@ -1429,6 +1404,7 @@ void wfmain::loadSettings() prefs.enablePTT = settings->value("EnablePTT", defPrefs.enablePTT).toBool(); ui->pttEnableChk->setChecked(prefs.enablePTT); prefs.niceTS = settings->value("NiceTS", defPrefs.niceTS).toBool(); + settings->endGroup(); settings->beginGroup("LAN"); @@ -1531,31 +1507,12 @@ void wfmain::loadSettings() ui->audioTXCodecCombo->setCurrentIndex(f); ui->audioRXCodecCombo->blockSignals(false); - ui->audioOutputCombo->blockSignals(true); rxSetup.name = settings->value("AudioOutput", "").toString(); - qInfo(logGui()) << "Got Audio Output: " << rxSetup.name; - int audioOutputIndex = ui->audioOutputCombo->findText(rxSetup.name); - if (audioOutputIndex != -1) { - ui->audioOutputCombo->setCurrentIndex(audioOutputIndex); + qInfo(logGui()) << "Got Audio Output from Settings: " << rxSetup.name; - QVariant v = ui->audioOutputCombo->currentData(); - rxSetup.port = v.value(); - - } - ui->audioOutputCombo->blockSignals(false); - - ui->audioInputCombo->blockSignals(true); txSetup.name = settings->value("AudioInput", "").toString(); - qInfo(logGui()) << "Got Audio Input: " << txSetup.name; - int audioInputIndex = ui->audioInputCombo->findText(txSetup.name); - if (audioInputIndex != -1) { - ui->audioInputCombo->setCurrentIndex(audioInputIndex); + qInfo(logGui()) << "Got Audio Input from Settings: " << txSetup.name; - QVariant v = ui->audioInputCombo->currentData(); - txSetup.port = v.value(); - - } - ui->audioInputCombo->blockSignals(false); rxSetup.resampleQuality = settings->value("ResampleQuality", "4").toInt(); txSetup.resampleQuality = rxSetup.resampleQuality; @@ -1614,6 +1571,8 @@ void wfmain::loadSettings() rigTemp->txAudioSetup.localAFgain = 255; rigTemp->rxAudioSetup.resampleQuality = 4; rigTemp->txAudioSetup.resampleQuality = 4; + rigTemp->rxAudioSetup.type = prefs.audioSystem; + rigTemp->txAudioSetup.type = prefs.audioSystem; rigTemp->baudRate = prefs.serialPortBaud; rigTemp->civAddr = prefs.radioCIVAddr; @@ -1628,31 +1587,8 @@ void wfmain::loadSettings() memcpy(rigTemp->guid, QUuid::fromString(guid).toRfc4122().constData(), GUIDLEN); #endif - ui->serverRXAudioInputCombo->blockSignals(true); rigTemp->rxAudioSetup.name = settings->value("ServerAudioInput", "").toString(); - qInfo(logGui()) << "Got Server Audio Input: " << rigTemp->rxAudioSetup.name; - int serverAudioInputIndex = ui->serverRXAudioInputCombo->findText(rigTemp->rxAudioSetup.name); - if (serverAudioInputIndex != -1) { - ui->serverRXAudioInputCombo->setCurrentIndex(serverAudioInputIndex); - - QVariant v = ui->serverRXAudioInputCombo->currentData(); - rigTemp->rxAudioSetup.port = v.value(); - - } - ui->serverRXAudioInputCombo->blockSignals(false); - - ui->serverTXAudioOutputCombo->blockSignals(true); rigTemp->txAudioSetup.name = settings->value("ServerAudioOutput", "").toString(); - qInfo(logGui()) << "Got Server Audio Output: " << rigTemp->txAudioSetup.name; - int serverAudioOutputIndex = ui->serverTXAudioOutputCombo->findText(rigTemp->txAudioSetup.name); - if (serverAudioOutputIndex != -1) { - ui->serverTXAudioOutputCombo->setCurrentIndex(serverAudioOutputIndex); - - QVariant v = ui->serverTXAudioOutputCombo->currentData(); - rigTemp->txAudioSetup.port = v.value(); - - } - ui->serverTXAudioOutputCombo->blockSignals(false); serverConfig.rigs.append(rigTemp); int row = 0; @@ -1793,25 +1729,40 @@ void wfmain::on_serverAudioPortText_textChanged(QString text) void wfmain::on_serverRXAudioInputCombo_currentIndexChanged(int value) { - if (serverConfig.rigs.size() > 0) - { - QVariant v = ui->serverRXAudioInputCombo->itemData(value); - serverConfig.rigs.first()->rxAudioSetup.port = v.value(); - serverConfig.rigs.first()->rxAudioSetup.name = ui->serverRXAudioInputCombo->itemText(value); - qDebug(logGui()) << "Changed default server audio input to:" << serverConfig.rigs.first()->rxAudioSetup.name; + if (!serverConfig.rigs.isEmpty()) + { + if (prefs.audioSystem == qtAudio) { + QVariant v = ui->serverRXAudioInputCombo->itemData(value); + serverConfig.rigs.first()->rxAudioSetup.port = v.value(); + } + else { + serverConfig.rigs.first()->rxAudioSetup.portInt = ui->serverRXAudioInputCombo->itemData(value).toInt(); + } + } + + serverConfig.rigs.first()->rxAudioSetup.name = ui->audioInputCombo->itemText(value); + } void wfmain::on_serverTXAudioOutputCombo_currentIndexChanged(int value) { - if (serverConfig.rigs.size() > 0) { - QVariant v = ui->serverTXAudioOutputCombo->itemData(value); - serverConfig.rigs.first()->txAudioSetup.port = v.value(); - serverConfig.rigs.first()->txAudioSetup.name = ui->serverTXAudioOutputCombo->itemText(value); - qDebug(logGui()) << "Changed default server audio output to:" << serverConfig.rigs.first()->txAudioSetup.name; + if (!serverConfig.rigs.isEmpty()) + { + if (prefs.audioSystem == qtAudio) { + QVariant v = ui->serverTXAudioOutputCombo->itemData(value); + serverConfig.rigs.first()->txAudioSetup.port = v.value(); + } + else { + serverConfig.rigs.first()->txAudioSetup.portInt = ui->serverTXAudioOutputCombo->itemData(value).toInt(); + } + } + + serverConfig.rigs.first()->txAudioSetup.name = ui->audioInputCombo->itemText(value); + } void wfmain::on_serverUsersTable_cellChanged(int row, int column) @@ -1871,6 +1822,8 @@ void wfmain::saveSettings() settings->setValue("SerialPortBaud", prefs.serialPortBaud); settings->setValue("VirtualSerialPort", prefs.virtualSerialPort); settings->setValue("localAFgain", prefs.localAFgain); + settings->setValue("AudioSystem", prefs.audioSystem); + settings->endGroup(); // Misc. user settings (enable PTT, draw peaks, etc) @@ -3364,6 +3317,8 @@ void wfmain::receiveRigID(rigCapabilities rigCaps) ui->useRTSforPTTchk->blockSignals(false); ui->connectBtn->setText("Disconnect"); // We must be connected now. + ui->audioSystemCombo->setEnabled(false); + prepareWf(ui->wfLengthSlider->value()); if(usingLAN) { @@ -4622,20 +4577,33 @@ void wfmain::on_passwordTxt_textChanged(QString text) void wfmain::on_audioOutputCombo_currentIndexChanged(int value) { - QVariant v = ui->audioOutputCombo->itemData(value); - rxSetup.port = v.value(); + + if (prefs.audioSystem == qtAudio) { + QVariant v = ui->audioOutputCombo->itemData(value); + rxSetup.port = v.value(); + } + else { + rxSetup.portInt = ui->audioOutputCombo->itemData(value).toInt(); + } rxSetup.name = ui->audioOutputCombo->itemText(value); - qDebug(logGui()) << "Changed default audio output to:" << rxSetup.name; + qDebug(logGui()) << "Changed audio output to:" << rxSetup.name; } void wfmain::on_audioInputCombo_currentIndexChanged(int value) { - QVariant v = ui->audioInputCombo->itemData(value); - txSetup.port = v.value(); + + if (prefs.audioSystem == qtAudio) { + QVariant v = ui->audioInputCombo->itemData(value); + txSetup.port = v.value(); + } + else { + txSetup.portInt = ui->audioInputCombo->itemData(value).toInt(); + } txSetup.name = ui->audioInputCombo->itemText(value); - qDebug(logGui()) << "Changed default audio input to:" << txSetup.name; + + qDebug(logGui()) << "Changed audio input to:" << txSetup.name; } void wfmain::on_audioSampleRateCombo_currentIndexChanged(QString text) @@ -4690,6 +4658,7 @@ void wfmain::on_connectBtn_clicked() if (haveRigCaps) { emit sendCloseComm(); ui->connectBtn->setText("Connect"); + ui->audioSystemCombo->setEnabled(true); haveRigCaps = false; rigName->setText("NONE"); } @@ -5799,3 +5768,204 @@ void wfmain::on_debugBtn_clicked() adjustSize(); } + + +void wfmain::setAudioDevicesUI() +{ + + // Enumerate audio devices, need to do before settings are loaded, + // First clear all existing entries + ui->audioInputCombo->blockSignals(true); + ui->audioOutputCombo->blockSignals(true); + ui->serverTXAudioOutputCombo->blockSignals(true); + ui->serverRXAudioInputCombo->blockSignals(true); + + ui->audioInputCombo->clear(); + ui->audioOutputCombo->clear(); + ui->serverTXAudioOutputCombo->clear(); + ui->serverRXAudioInputCombo->clear(); + + qDebug(logSystem()) << "Finding audio devices, output=" << rxSetup.name << "input="<audioOutputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); + ui->serverTXAudioOutputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); +#ifdef Q_OS_WIN + } + #endif + } + + const auto audioInputs = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); + for (const QAudioDeviceInfo& deviceInfo : audioInputs) { +#ifdef Q_OS_WIN + if (deviceInfo.realm() == "wasapi") { +#endif + ui->audioInputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); + ui->serverRXAudioInputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); +#ifdef Q_OS_WIN + } +#endif + } + + } + break; + case portAudio: + { + PaError err; + + err = Pa_Initialize(); + + if (err != paNoError) + { + qInfo(logAudio()) << "ERROR: Cannot initialize Portaudio"; + return; + } + + qInfo(logAudio()) << "PortAudio version: " << Pa_GetVersionInfo()->versionText; + + int numDevices; + numDevices = Pa_GetDeviceCount(); + qInfo(logAudio()) << "Pa_CountDevices returned" << numDevices; + + const PaDeviceInfo* info; + for (int i = 0; i < numDevices; i++) + { + info = Pa_GetDeviceInfo(i); + if (info->maxInputChannels > 0) { + qInfo(logAudio()) << (i == Pa_GetDefaultInputDevice() ? "*" : " ") << "(" << i << ") Output Device : " << info->name; + + ui->audioInputCombo->addItem(info->name, i); + ui->serverRXAudioInputCombo->addItem(info->name, i); + } + if (info->maxOutputChannels > 0) { + qInfo(logAudio()) << (i == Pa_GetDefaultOutputDevice() ? "*" : " ") << "(" << i << ") Input Device : " << info->name; + ui->audioOutputCombo->addItem(info->name, i); + ui->serverTXAudioOutputCombo->addItem(info->name, i); + } + } + } + break; + case rtAudio: + { + Pa_Terminate(); + +#if defined(Q_OS_LINUX) + RtAudio* audio = new RtAudio(RtAudio::Api::LINUX_ALSA); +#elif defined(Q_OS_WIN) + RtAudio* audio = new RtAudio(RtAudio::Api::WINDOWS_WASAPI); +#elif defined(Q_OS_MACX) + RtAudio* audio = new RtAudio(RtAudio::Api::MACOSX_CORE); +#endif + + + // Enumerate audio devices, need to do before settings are loaded. + std::map apiMap; + apiMap[RtAudio::MACOSX_CORE] = "OS-X Core Audio"; + apiMap[RtAudio::WINDOWS_ASIO] = "Windows ASIO"; + apiMap[RtAudio::WINDOWS_DS] = "Windows DirectSound"; + apiMap[RtAudio::WINDOWS_WASAPI] = "Windows WASAPI"; + apiMap[RtAudio::UNIX_JACK] = "Jack Client"; + apiMap[RtAudio::LINUX_ALSA] = "Linux ALSA"; + apiMap[RtAudio::LINUX_PULSE] = "Linux PulseAudio"; + apiMap[RtAudio::LINUX_OSS] = "Linux OSS"; + apiMap[RtAudio::RTAUDIO_DUMMY] = "RtAudio Dummy"; + + std::vector< RtAudio::Api > apis; + RtAudio::getCompiledApi(apis); + + qInfo(logAudio()) << "RtAudio Version " << QString::fromStdString(RtAudio::getVersion()); + + qInfo(logAudio()) << "Compiled APIs:"; + for (unsigned int i = 0; i < apis.size(); i++) { + qInfo(logAudio()) << " " << QString::fromStdString(apiMap[apis[i]]); + } + + RtAudio::DeviceInfo info; + + qInfo(logAudio()) << "Current API: " << QString::fromStdString(apiMap[audio->getCurrentApi()]); + + unsigned int devices = audio->getDeviceCount(); + qInfo(logAudio()) << "Found " << devices << " audio device(s) *=default"; + + for (unsigned int i = 1; i < devices; i++) { + info = audio->getDeviceInfo(i); + if (info.outputChannels > 0) { + qInfo(logAudio()) << (info.isDefaultOutput ? "*" : " ") << "(" << i << ") Output Device : " << QString::fromStdString(info.name); + ui->audioOutputCombo->addItem(QString::fromStdString(info.name), i); + ui->serverTXAudioOutputCombo->addItem(QString::fromStdString(info.name), i); + } + if (info.inputChannels > 0) { + qInfo(logAudio()) << (info.isDefaultInput ? "*" : " ") << "(" << i << ") Input Device : " << QString::fromStdString(info.name); + ui->audioInputCombo->addItem(QString::fromStdString(info.name), i); + ui->serverRXAudioInputCombo->addItem(QString::fromStdString(info.name), i); + } + } + + delete audio; + + } + break; + + } + + + // Stop blocking signals so we can set the current values + ui->audioInputCombo->blockSignals(false); + ui->audioOutputCombo->blockSignals(false); + ui->serverTXAudioOutputCombo->blockSignals(false); + ui->serverRXAudioInputCombo->blockSignals(false); + + + rxSetup.type = prefs.audioSystem; + txSetup.type = prefs.audioSystem; + + int audioInputIndex = ui->audioInputCombo->findText(txSetup.name); + if (audioInputIndex != -1) { + ui->audioInputCombo->setCurrentIndex(audioInputIndex); + } + else { + qDebug(logSystem()) << "Audio input not found"; + } + + int audioOutputIndex = ui->audioOutputCombo->findText(rxSetup.name); + if (audioOutputIndex != -1) { + ui->audioOutputCombo->setCurrentIndex(audioOutputIndex); + } + else { + qDebug(logSystem()) << "Audio output not found"; + } + + if (!serverConfig.rigs.isEmpty()) + + { + qInfo(logGui()) << "Got Server Audio Input: " << serverConfig.rigs.first()->rxAudioSetup.name; + int serverAudioInputIndex = ui->serverRXAudioInputCombo->findText(serverConfig.rigs.first()->rxAudioSetup.name); + if (serverAudioInputIndex != -1) { + ui->serverRXAudioInputCombo->setCurrentIndex(serverAudioInputIndex); + } + + qInfo(logGui()) << "Got Server Audio Output: " << serverConfig.rigs.first()->txAudioSetup.name; + int serverAudioOutputIndex = ui->serverTXAudioOutputCombo->findText(serverConfig.rigs.first()->txAudioSetup.name); + if (serverAudioOutputIndex != -1) { + ui->serverTXAudioOutputCombo->setCurrentIndex(serverAudioOutputIndex); + } + } + // Set these to default audio devices initially. + qDebug(logSystem()) << "Audio devices done."; +} + +void wfmain::on_audioSystemCombo_currentIndexChanged(int value) +{ + prefs.audioSystem = static_cast(value); + setAudioDevicesUI(); // Force all audio devices to update +} \ No newline at end of file diff --git a/wfmain.h b/wfmain.h index 474c165..29bded4 100644 --- a/wfmain.h +++ b/wfmain.h @@ -40,6 +40,14 @@ #include #include +#include +#ifdef Q_OS_WIN +#include "RtAudio.h" +#else +#include "rtaudio/RtAudio.h" +#endif + + namespace Ui { class wfmain; } @@ -529,6 +537,8 @@ private slots: void on_radioStatusBtn_clicked(); + void on_audioSystemCombo_currentIndexChanged(int value); + private: Ui::wfmain *ui; void closeEvent(QCloseEvent *event); @@ -769,6 +779,7 @@ private: meterKind meter2Type; quint16 tcpPort; quint8 waterfallFormat; + audioType audioSystem; // plot scheme } prefs; diff --git a/wfmain.ui b/wfmain.ui index e1b24a7..124a54b 100644 --- a/wfmain.ui +++ b/wfmain.ui @@ -2066,7 +2066,7 @@ - 4 + 0 @@ -2633,6 +2633,32 @@ + + + + Audio System + + + + + + + + QT Audio + + + + + PortAudio + + + + + RT Audio + + + + diff --git a/wfserver.pro b/wfserver.pro index 37dad25..d17f9eb 100644 --- a/wfserver.pro +++ b/wfserver.pro @@ -17,21 +17,38 @@ DEFINES += WFVIEW_VERSION=\\\"1.2e\\\" DEFINES += BUILD_WFSERVER -CONFIG(debug, release|debug) { -# For Debug builds only: -QMAKE_CXXFLAGS += -faligned-new -WIN32:DESTDIR = wfview-debug +CONFIG(debug, release|debug) { + # For Debug builds only: + QMAKE_CXXFLAGS += -faligned-new + win32:DESTDIR = wfview-release + win32:LIBS += -L../portaudio/msvc/Win32/Debug/ -lportaudio_x86 } else { -# For Release builds only: -linux:QMAKE_CXXFLAGS += -s -QMAKE_CXXFLAGS += -fvisibility=hidden -QMAKE_CXXFLAGS += -fvisibility-inlines-hidden -QMAKE_CXXFLAGS += -faligned-new -linux:QMAKE_LFLAGS += -O2 -s -WIN32:DESTDIR = wfview-release + # For Release builds only: + linux:QMAKE_CXXFLAGS += -s + QMAKE_CXXFLAGS += -fvisibility=hidden + QMAKE_CXXFLAGS += -fvisibility-inlines-hidden + QMAKE_CXXFLAGS += -faligned-new + linux:QMAKE_LFLAGS += -O2 -s + win32:DESTDIR = wfview-debug + win32:LIBS += -L../portaudio/msvc/Win32/Release/ -lportaudio_x86 } +# RTAudio defines +win32:DEFINES += __WINDOWS_WASAPI__ +#win32:DEFINES += __WINDOWS_DS__ # Requires DirectSound libraries +#linux:DEFINES += __LINUX_ALSA__ +#linux:DEFINES += __LINUX_OSS__ +linux:DEFINES += __LINUX_PULSE__ +macx:DEFINES += __MACOSX_CORE__ +win32:SOURCES += ../rtaudio/RTAudio.cpp +win32:HEADERS += ../rtaudio/RTAUdio.h +!linux:INCLUDEPATH += ../rtaudio +linux:LIBS += -lpulse -lpulse-simple -lrtaudio -lpthread + +win32:INCLUDEPATH += ../portaudio/include +!win32:LIBS += -lportaudio + # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the @@ -144,6 +161,8 @@ SOURCES += main.cpp\ udpcivdata.cpp \ udpaudio.cpp \ logcategories.cpp \ + pahandler.cpp \ + rthandler.cpp \ audiohandler.cpp \ audioconverter.cpp \ udpserver.cpp \ @@ -163,6 +182,8 @@ HEADERS += servermain.h \ udpcivdata.h \ udpaudio.h \ logcategories.h \ + pahandler.h \ + rthandler.h \ audiohandler.h \ audioconverter.h \ udpserver.h \ diff --git a/wfview.pro b/wfview.pro index 667ffff..ef0e2be 100644 --- a/wfview.pro +++ b/wfview.pro @@ -16,19 +16,36 @@ DEFINES += WFVIEW_VERSION=\\\"1.2e\\\" DEFINES += BUILD_WFVIEW CONFIG(debug, release|debug) { -# For Debug builds only: -QMAKE_CXXFLAGS += -faligned-new -WIN32:DESTDIR = wfview-release + # For Debug builds only: + QMAKE_CXXFLAGS += -faligned-new + win32:DESTDIR = wfview-release + win32:LIBS += -L../portaudio/msvc/Win32/Debug/ -lportaudio_x86 } else { -# For Release builds only: -linux:QMAKE_CXXFLAGS += -s -QMAKE_CXXFLAGS += -fvisibility=hidden -QMAKE_CXXFLAGS += -fvisibility-inlines-hidden -QMAKE_CXXFLAGS += -faligned-new -linux:QMAKE_LFLAGS += -O2 -s -WIN32:DESTDIR = wfview-debug + # For Release builds only: + linux:QMAKE_CXXFLAGS += -s + QMAKE_CXXFLAGS += -fvisibility=hidden + QMAKE_CXXFLAGS += -fvisibility-inlines-hidden + QMAKE_CXXFLAGS += -faligned-new + linux:QMAKE_LFLAGS += -O2 -s + win32:DESTDIR = wfview-debug + win32:LIBS += -L../portaudio/msvc/Win32/Release/ -lportaudio_x86 } +# RTAudio defines +win32:DEFINES += __WINDOWS_WASAPI__ +#win32:DEFINES += __WINDOWS_DS__ # Requires DirectSound libraries +#linux:DEFINES += __LINUX_ALSA__ +#linux:DEFINES += __LINUX_OSS__ +linux:DEFINES += __LINUX_PULSE__ +macx:DEFINES += __MACOSX_CORE__ +win32:SOURCES += ../rtaudio/RTAudio.cpp +win32:HEADERS += ../rtaudio/RTAUdio.h +!linux:INCLUDEPATH += ../rtaudio +linux:LIBS += -lpulse -lpulse-simple -lrtaudio -lpthread + +win32:INCLUDEPATH += ../portaudio/include +!win32:LIBS += -lportaudio + # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the @@ -59,10 +76,6 @@ isEmpty(PREFIX) { DEFINES += PREFIX=\\\"$$PREFIX\\\" -# Choose audio system, uses QTMultimedia if both are commented out. -# DEFINES += RTAUDIO -# DEFINES += PORTAUDIO - contains(DEFINES, RTAUDIO) { # RTAudio defines win32:DEFINES += __WINDOWS_WASAPI__ @@ -167,6 +180,8 @@ SOURCES += main.cpp\ udpcivdata.cpp \ udpaudio.cpp \ logcategories.cpp \ + pahandler.cpp \ + rthandler.cpp \ audiohandler.cpp \ audioconverter.cpp \ calibrationwindow.cpp \ @@ -193,6 +208,8 @@ HEADERS += wfmain.h \ udpcivdata.h \ udpaudio.h \ logcategories.h \ + pahandler.h \ + rthandler.h \ audiohandler.h \ audioconverter.h \ calibrationwindow.h \ diff --git a/wfview.vcxproj b/wfview.vcxproj index 5c147a6..355ab27 100644 --- a/wfview.vcxproj +++ b/wfview.vcxproj @@ -16,12 +16,11 @@ QtVS_v304 10.0.19041.0 10.0.19041.0 - $(MSBuildProjectDirectory)\QtMsBuild - + $(MSBuildProjectDirectory)\QtMsBuild v142 - release\ + wfview-release\ false NotSet Application @@ -30,17 +29,14 @@ v142 - debug\ + wfview-debug\ false NotSet Application debug\ wfview - - - - + @@ -48,37 +44,11 @@ - - - - - - debug\ - debug\ - wfview - true - - - release\ - release\ - wfview - true - false - - - msvc2019 - core;network;gui;multimedia;widgets;serialport;printsupport - - - msvc2019 - core;network;gui;multimedia;widgets;serialport;printsupport - - - - + wfview-debug\debug\wfviewtruewfview-release\release\wfviewtruefalsemsvc2019core;network;gui;multimedia;widgets;serialport;printsupportmsvc2019core;network;gui;multimedia;widgets;serialport;printsupport + - .;..\qcustomplot;..\opus\include;..\eigen;..\r8brain-free-src;resampler;release;/include;%(AdditionalIncludeDirectories) + .;..\rtaudio;..\portaudio\include;..\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 @@ -87,19 +57,17 @@ Sync release\ MaxSpeed - _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION="1.2e";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="b510b70";HOST="wfview.org";UNAME="build";NDEBUG;QT_NO_DEBUG;%(PreprocessorDefinitions) + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION="1.2e";BUILD_WFVIEW;__WINDOWS_WASAPI__;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="63c5e02";HOST="wfview.org";UNAME="build";NDEBUG;QT_NO_DEBUG;%(PreprocessorDefinitions) false - - + MultiThreadedDLL true true Level3 - true - + true - ..\opus\win32\VS2015\Win32\Release\opus.lib;shell32.lib;%(AdditionalDependencies) - ..\opus\win32\VS2015\Win32\Release;C:\opensslx86\lib;C:\Utils\my_sql\mysql-5.7.25-win32\lib;C:\Utils\postgresqlx86\pgsql\lib;%(AdditionalLibraryDirectories) + ..\portaudio\msvc\Win32\Release\portaudio_x86.lib;..\opus\win32\VS2015\Win32\Release\opus.lib;shell32.lib;%(AdditionalDependencies) + ..\portaudio\msvc\Win32\Release;..\opus\win32\VS2015\Win32\Release;C:\opensslx86\lib;C:\Utils\my_sql\mysql-5.7.25-win32\lib;C:\Utils\postgresqlx86\pgsql\lib;%(AdditionalLibraryDirectories) "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions) true false @@ -117,31 +85,12 @@ 0 - _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION=\"1.2e\";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=\"b510b70\";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.2e\";BUILD_WFVIEW;__WINDOWS_WASAPI__;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=\"63c5e02\";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.h - Moc'ing %(Identity)... - output - $(Configuration) - moc_%(Filename).cpp - - - default - Rcc'ing %(Identity)... - $(Configuration) - qrc_%(Filename).cpp - - - Uic'ing %(Identity)... - $(ProjectDir) - ui_%(Filename).h - - + 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;..\r8brain-free-src;resampler;debug;/include;%(AdditionalIncludeDirectories) + .;..\rtaudio;..\portaudio\include;..\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 @@ -150,17 +99,16 @@ Sync debug\ Disabled - _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION="1.2e";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="b510b70";HOST="wfview.org";UNAME="build";%(PreprocessorDefinitions) + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION="1.2e";BUILD_WFVIEW;__WINDOWS_WASAPI__;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="63c5e02";HOST="wfview.org";UNAME="build";%(PreprocessorDefinitions) false MultiThreadedDebugDLL true true Level3 - true - + true - ..\opus\win32\VS2015\Win32\Debug\opus.lib;shell32.lib;%(AdditionalDependencies) - ..\opus\win32\VS2015\Win32\Debug;C:\opensslx86\lib;C:\Utils\my_sql\mysql-5.7.25-win32\lib;C:\Utils\postgresqlx86\pgsql\lib;%(AdditionalLibraryDirectories) + ..\portaudio\msvc\Win32\Debug\portaudio_x86.lib;..\opus\win32\VS2015\Win32\Debug\opus.lib;shell32.lib;%(AdditionalDependencies) + ..\portaudio\msvc\Win32\Debug;..\opus\win32\VS2015\Win32\Debug;C:\opensslx86\lib;C:\Utils\my_sql\mysql-5.7.25-win32\lib;C:\Utils\postgresqlx86\pgsql\lib;%(AdditionalLibraryDirectories) "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions) true true @@ -176,29 +124,11 @@ 0 - _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WFVIEW_VERSION=\"1.2e\";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=\"b510b70\";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.2e\";BUILD_WFVIEW;__WINDOWS_WASAPI__;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=\"63c5e02\";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.h - Moc'ing %(Identity)... - output - $(Configuration) - moc_%(Filename).cpp - - - default - Rcc'ing %(Identity)... - $(Configuration) - qrc_%(Filename).cpp - - - Uic'ing %(Identity)... - $(ProjectDir) - ui_%(Filename).h - - + msvc./$(Configuration)/moc_predefs.hMoc'ing %(Identity)...output$(Configuration)moc_%(Filename).cppdefaultRcc'ing %(Identity)...$(Configuration)qrc_%(Filename).cppUic'ing %(Identity)...$(ProjectDir)ui_%(Filename).h + @@ -208,6 +138,7 @@ + @@ -216,6 +147,7 @@ + @@ -228,58 +160,264 @@ + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + Document true @@ -296,21 +434,127 @@ release\moc_predefs.h;%(Outputs) true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -344,16 +588,30 @@ - resources - resources - + + + + + + + + + + resourcesresources - style - style - + + + + + + + + + + stylestyle @@ -367,9 +625,6 @@ - - - - + \ No newline at end of file diff --git a/wfview.vcxproj.filters b/wfview.vcxproj.filters index 40ce604..a8fca3d 100644 --- a/wfview.vcxproj.filters +++ b/wfview.vcxproj.filters @@ -47,9 +47,15 @@ + + Source Files + Source Files + + Source Files + Source Files @@ -71,6 +77,9 @@ Source Files + + Source Files + Source Files @@ -95,6 +104,9 @@ Source Files + + Source Files + Source Files @@ -107,6 +119,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + Source Files @@ -116,26 +137,20 @@ Source Files - - Source Files - - - Source Files - - - Source Files - - - Source Files - + + Header Files + Header Files Header Files + + Header Files + Header Files @@ -160,6 +175,9 @@ Header Files + + Header Files + Header Files @@ -187,6 +205,9 @@ Header Files + + Header Files + Header Files @@ -202,6 +223,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + Header Files @@ -214,23 +244,71 @@ Header Files - - Header Files - - - Header Files - - - Header Files - + + + + + + + + + + + + + + Generated Files Generated Files + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -387,11 +465,6 @@ - - - - - Header Files - + \ No newline at end of file