diff --git a/audiohandler.cpp b/audiohandler.cpp index d8227e4..c17a6f4 100644 --- a/audiohandler.cpp +++ b/audiohandler.cpp @@ -8,6 +8,10 @@ #include "logcategories.h" #include "ulaw.h" +#if defined(Q_OS_WIN) && defined(PORTAUDIO) +#include +#endif + audioHandler::audioHandler(QObject* parent) { @@ -29,6 +33,7 @@ audioHandler::~audioHandler() } delete audio; #elif defined(PORTAUDIO) + Pa_StopStream(audio); #else stop(); #endif @@ -81,7 +86,7 @@ bool audioHandler::init(audioSetup setupIn) setup.bits = 16; } - ringBuf = new wilt::Ring(100); // Should be customizable. + ringBuf = new wilt::Ring(setupIn.latency / 8 + 1); // Should be customizable. tempBuf.sent = 0; @@ -124,14 +129,9 @@ bool audioHandler::init(audioSetup setupIn) if (info.probed) { - // if "preferred" sample rate is 44100, try 48K instead - if (info.preferredSampleRate == (unsigned int)44100) { - qDebug(logAudio()) << "Preferred sample rate 44100, trying 48000"; - this->nativeSampleRate = 48000; - } - else { - this->nativeSampleRate = info.preferredSampleRate; - } + // Always use the "preferred" sample rate + // We can always resample if needed + this->nativeSampleRate = info.preferredSampleRate; // Per channel chunk size. this->chunkSize = (this->nativeSampleRate / 50); @@ -189,6 +189,79 @@ bool audioHandler::init(audioSetup setupIn) #elif defined(PORTAUDIO) + + PaError err; + +#ifdef Q_OS_WIN + CoInitialize(0); +#endif + + memset(&aParams, 0,sizeof(PaStreamParameters)); + + if (setup.port > 0) { + aParams.device = setup.port; + } + else if (setup.isinput) { + aParams.device = Pa_GetDefaultInputDevice(); + } + else { + aParams.device = Pa_GetDefaultOutputDevice(); + } + + info = Pa_GetDeviceInfo(aParams.device); + + aParams.channelCount = 2; + aParams.hostApiSpecificStreamInfo = NULL; + aParams.sampleFormat = paInt16; + aParams.suggestedLatency = info->defaultLowInputLatency; + aParams.hostApiSpecificStreamInfo = NULL; + + // Always use the "preferred" sample rate + // We can always resample if needed + this->nativeSampleRate = info->defaultSampleRate; + + // Per channel chunk size. + this->chunkSize = (this->nativeSampleRate / 50); + + qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << info->name << "(" << aParams.device << ") successfully probed"; + if (setup.isinput) { + devChannels = info->maxInputChannels; + } + else { + devChannels = info->maxOutputChannels; + } + qInfo(logAudio()) << " Channels:" << devChannels; + if (devChannels > 2) { + devChannels = 2; + } + aParams.channelCount = devChannels; + + qInfo(logAudio()) << " chunkSize: " << chunkSize; + + if (setup.isinput) { + err=Pa_OpenStream(&audio, &aParams, 0, this->nativeSampleRate, this->chunkSize, paNoFlag, &audioHandler::staticWrite, (void*)this); + //err = Pa_OpenDefaultStream(&audio, 2, 0, paFloat32, this->nativeSampleRate, this->chunkSize, &audioHandler::staticWrite, (void*)this); + } + else { + err=Pa_OpenStream(&audio, 0, &aParams, this->nativeSampleRate, this->chunkSize, paNoFlag, &audioHandler::staticRead, (void*)this); + //err = Pa_OpenDefaultStream(&audio, 0, 2, paFloat32, this->nativeSampleRate, this->chunkSize, &audioHandler::staticRead, (void*)this); + } + + 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); + } + //qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "detected latency:" << audio->getStreamLatency(); + + + + #else format.setSampleSize(16); @@ -293,7 +366,7 @@ void audioHandler::start() } if (setup.isinput) { -#ifdef Q_OS_MACX +#ifndef Q_OS_WIN this->open(QIODevice::WriteOnly); #else this->open(QIODevice::WriteOnly | QIODevice::Unbuffered); @@ -301,7 +374,7 @@ void audioHandler::start() audioInput->start(this); } else { -#ifdef Q_OS_MACX +#ifndef Q_OS_WIN this->open(QIODevice::ReadOnly); #else this->open(QIODevice::ReadOnly | QIODevice::Unbuffered); @@ -328,7 +401,8 @@ void audioHandler::setVolume(unsigned char volume) /// /// #if defined(RTAUDIO) -int audioHandler::readData(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status) +int audioHandler::readData(void* outputBuffer, void* inputBuffer, + unsigned int nFrames, double streamTime, RtAudioStreamStatus status) { Q_UNUSED(inputBuffer); Q_UNUSED(streamTime); @@ -337,6 +411,15 @@ int audioHandler::readData(void* outputBuffer, void* inputBuffer, unsigned int n int nBytes = nFrames * devChannels * 2; // This is ALWAYS 2 bytes per sample and 2 channels quint8* buffer = (quint8*)outputBuffer; #elif defined(PORTAUDIO) + +int audioHandler::readData(const void* inputBuffer, void* outputBuffer, + unsigned long nFrames, const PaStreamCallbackTimeInfo * streamTime, PaStreamCallbackFlags status) +{ + Q_UNUSED(inputBuffer); + Q_UNUSED(streamTime); + Q_UNUSED(status); + int nBytes = nFrames * devChannels * 2; // This is ALWAYS 2 bytes per sample and 2 channels + quint8* buffer = (quint8*)outputBuffer; #else qint64 audioHandler::readData(char* buffer, qint64 nBytes) { @@ -416,13 +499,15 @@ qint64 audioHandler::readData(char* buffer, qint64 nBytes) #if defined(RTAUDIO) return 0; #elif defined(PORTAUDIO) + return 0; #else return nBytes; #endif } #if defined(RTAUDIO) -int audioHandler::writeData(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status) +int audioHandler::writeData(void* outputBuffer, void* inputBuffer, + unsigned int nFrames, double streamTime, RtAudioStreamStatus status) { Q_UNUSED(outputBuffer); Q_UNUSED(streamTime); @@ -430,6 +515,15 @@ int audioHandler::writeData(void* outputBuffer, void* inputBuffer, unsigned int int nBytes = nFrames * devChannels * 2; // This is ALWAYS 2 bytes per sample and 2 channels const char* data = (const char*)inputBuffer; #elif defined(PORTAUDIO) +int audioHandler::writeData(const void* inputBuffer, void* outputBuffer, + unsigned long nFrames, const PaStreamCallbackTimeInfo * streamTime, + PaStreamCallbackFlags status) +{ + Q_UNUSED(outputBuffer); + Q_UNUSED(streamTime); + Q_UNUSED(status); + int nBytes = nFrames * devChannels * 2; // This is ALWAYS 2 bytes per sample and 2 channels + const char* data = (const char*)inputBuffer; #else qint64 audioHandler::writeData(const char* data, qint64 nBytes) { @@ -467,6 +561,7 @@ qint64 audioHandler::writeData(const char* data, qint64 nBytes) #if defined(RTAUDIO) return 0; #elif defined(PORTAUDIO) + return 0; #else return nBytes; #endif @@ -591,7 +686,7 @@ void audioHandler::incomingAudio(audioPacket inPacket) if (!ringBuf->try_write(inPacket)) { - qDebug(logAudio()) << "Buffer full! capacity:" << ringBuf->capacity() << "length" << ringBuf->size(); + qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Buffer full! capacity:" << ringBuf->capacity() << "length" << ringBuf->size(); } return; } @@ -600,6 +695,8 @@ void audioHandler::changeLatency(const quint16 newSize) { qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Changing latency to: " << newSize << " from " << setup.latency; setup.latency = newSize; + delete ringBuf; + ringBuf = new wilt::Ring(setup.latency / 8 + 1); // Should be customizable. } int audioHandler::getLatency() @@ -618,13 +715,13 @@ void audioHandler::getNextAudioChunk(QByteArray& ret) { currentLatency = packet.time.msecsTo(QTime::currentTime()); - while (currentLatency > setup.latency) { + if (currentLatency > setup.latency) { qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Packet " << hex << packet.seq << " arrived too late (increase output latency!) " << dec << packet.time.msecsTo(QTime::currentTime()) << "ms"; - if (!ringBuf->try_read(packet)) - break; - currentLatency = packet.time.msecsTo(QTime::currentTime()); + // if (!ringBuf->try_read(packet)) + // break; + // currentLatency = packet.time.msecsTo(QTime::currentTime()); } //qDebug(logAudio) << "Chunksize" << this->chunkSize << "Packet size" << packet.data.length(); diff --git a/audiohandler.h b/audiohandler.h index d2902d2..875d261 100644 --- a/audiohandler.h +++ b/audiohandler.h @@ -9,10 +9,14 @@ #include #if defined(RTAUDIO) +#ifdef Q_OS_WIN +#include "RtAudio.h" +#else #include "rtaudio/RtAudio.h" +#endif #elif defined (PORTAUDIO) #include "portaudio.h" -#error "PORTAUDIO is not currently supported" +//#error "PORTAUDIO is not currently supported" #else #include #include @@ -67,9 +71,8 @@ struct audioSetup { quint8 codec; bool ulaw; bool isinput; -#if defined(RTAUDIO) +#if defined(RTAUDIO) || defined(PORTAUDIO) int port; -#elif defined(PORTAUDIO) #else QAudioDeviceInfo port; #endif @@ -137,6 +140,21 @@ private: return static_cast(userData)->writeData(outputBuffer, inputBuffer, nFrames, streamTime, status); } #elif defined(PORTAUDIO) + int readData(const void* inputBuffer, void* outputBuffer, + unsigned long nFrames, + const PaStreamCallbackTimeInfo* streamTime, + PaStreamCallbackFlags status); + static int staticRead(const void* inputBuffer, void* outputBuffer, unsigned long nFrames, const PaStreamCallbackTimeInfo* streamTime, PaStreamCallbackFlags status, void* userData) { + return ((audioHandler*)userData)->readData(inputBuffer, outputBuffer, nFrames, streamTime, status); + } + + 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 ((audioHandler*)userData)->writeData(inputBuffer, outputBuffer, nFrames, streamTime, status); + } #else qint64 readData(char* data, qint64 nBytes); @@ -154,6 +172,9 @@ private: RtAudio::StreamOptions options; RtAudio::DeviceInfo info; #elif defined(PORTAUDIO) + PaStream* audio = Q_NULLPTR; + PaStreamParameters aParams; + const PaDeviceInfo *info; #else QAudioOutput* audioOutput=Q_NULLPTR; QAudioInput* audioInput=Q_NULLPTR; diff --git a/udphandler.cpp b/udphandler.cpp index d5368a4..161ce92 100644 --- a/udphandler.cpp +++ b/udphandler.cpp @@ -346,7 +346,7 @@ void udpHandler::dataReceived() audio = new udpAudio(localIP, radioIP, audioPort, rxSetup, txSetup); QObject::connect(civ, SIGNAL(receive(QByteArray)), this, SLOT(receiveFromCivStream(QByteArray))); - QObject::connect(audio, SIGNAL(haveAudioData(audioPacket)), this, SLOT(receiveAudioData(audioPacket))); + //QObject::connect(audio, SIGNAL(haveAudioData(audioPacket)), this, SLOT(receiveAudioData(audioPacket))); QObject::connect(this, SIGNAL(haveChangeLatency(quint16)), audio, SLOT(changeLatency(quint16))); QObject::connect(this, SIGNAL(haveSetVolume(unsigned char)), audio, SLOT(setVolume(unsigned char))); @@ -740,7 +740,7 @@ udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, audio connect(this, SIGNAL(setupRxAudio(audioSetup)), rxaudio, SLOT(init(audioSetup))); // signal/slot not currently used. - connect(this, SIGNAL(haveAudioData(audioPacket)), rxaudio, SLOT(incomingAudio(audioPacket))); + //connect(this, SIGNAL(haveAudioData(audioPacket)), rxaudio, SLOT(incomingAudio(audioPacket))); connect(this, SIGNAL(haveChangeLatency(quint16)), rxaudio, SLOT(changeLatency(quint16))); connect(this, SIGNAL(haveSetVolume(unsigned char)), rxaudio, SLOT(setVolume(unsigned char))); connect(rxAudioThread, SIGNAL(finished()), rxaudio, SLOT(deleteLater())); @@ -945,8 +945,8 @@ void udpAudio::dataReceived() tempAudio.data = r.mid(0x18); // Prefer signal/slot to forward audio as it is thread/safe // Need to do more testing but latency appears fine. - //audioLatency = rxaudio->incomingAudio(tempAudio); - emit haveAudioData(tempAudio); + rxaudio->incomingAudio(tempAudio); + //emit haveAudioData(tempAudio); audioLatency = rxaudio->getLatency(); } break; diff --git a/udpserver.cpp b/udpserver.cpp index c16ebbc..3219815 100644 --- a/udpserver.cpp +++ b/udpserver.cpp @@ -653,7 +653,9 @@ void udpServer::audioReceived() tempAudio.sent = 0; tempAudio.data = r.mid(0x18); //qInfo(logUdpServer()) << "sending tx audio " << in->seq; - emit haveAudioData(tempAudio); + //emit haveAudioData(tempAudio); + txaudio->incomingAudio(tempAudio); + } } break; diff --git a/wfmain.cpp b/wfmain.cpp index 95d8353..e1d5ebb 100644 --- a/wfmain.cpp +++ b/wfmain.cpp @@ -106,6 +106,11 @@ wfmain::~wfmain() delete rpt; delete ui; delete settings; + +#if defined(PORTAUDIO) + Pa_Terminate(); +#endif + } void wfmain::closeEvent(QCloseEvent *event) @@ -1023,6 +1028,35 @@ void wfmain::setAudioDevicesUI() #elif defined(PORTAUDIO) // Use PortAudio device enumeration + + PaError err; + + err = Pa_Initialize(); + + if (err != paNoError) + { + qInfo(logAudio()) << "ERROR: Cannot initialize Portaudio"; + } + + 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); + } + if (info->maxOutputChannels > 0) { + qInfo(logAudio()) << (i == Pa_GetDefaultOutputDevice() ? "*" : " ") << "(" << i << ") Input Device : " << info->name; + ui->audioOutputCombo->addItem(info->name, i); + } + } #else // If no external library is configured, use QTMultimedia @@ -1466,6 +1500,7 @@ void wfmain::loadSettings() #if defined(RTAUDIO) rxSetup.port = ui->audioOutputCombo->itemData(audioOutputIndex).toInt(); #elif defined(PORTAUDIO) + rxSetup.port = ui->audioOutputCombo->itemData(audioOutputIndex).toInt(); #else QVariant v = ui->audioOutputCombo->currentData(); rxSetup.port = v.value(); @@ -1482,6 +1517,7 @@ void wfmain::loadSettings() #if defined(RTAUDIO) txSetup.port = ui->audioInputCombo->itemData(audioInputIndex).toInt(); #elif defined(PORTAUDIO) + txSetup.port = ui->audioInputCombo->itemData(audioInputIndex).toInt(); #else QVariant v = ui->audioInputCombo->currentData(); txSetup.port = v.value(); @@ -4223,6 +4259,7 @@ void wfmain::on_audioOutputCombo_currentIndexChanged(int value) #if defined(RTAUDIO) rxSetup.port = ui->audioOutputCombo->itemData(value).toInt(); #elif defined(PORTAUDIO) + rxSetup.port = ui->audioOutputCombo->itemData(value).toInt(); #else QVariant v = ui->audioOutputCombo->itemData(value); rxSetup.port = v.value(); @@ -4236,6 +4273,7 @@ void wfmain::on_audioInputCombo_currentIndexChanged(int value) #if defined(RTAUDIO) txSetup.port = ui->audioInputCombo->itemData(value).toInt(); #elif defined(PORTAUDIO) + txSetup.port = ui->audioInputCombo->itemData(value).toInt(); #else QVariant v = ui->audioInputCombo->itemData(value); txSetup.port = v.value(); diff --git a/wfview.pro b/wfview.pro index 3a197c5..c05ba29 100644 --- a/wfview.pro +++ b/wfview.pro @@ -48,13 +48,29 @@ DEFINES += PREFIX=\\\"$$PREFIX\\\" # DEFINES += RTAUDIO # DEFINES += PORTAUDIO -# 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__ +contains(DEFINES, RTAUDIO) { + # 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 -lpthread +} + +contains(DEFINES, PORTAUDIO) { + CONFIG(debug, release|debug) { + win32:LIBS += -L../portaudio/msvc/Win32/Debug/ -lportaudio_x86 + } else { + win32:LIBS += -L../portaudio/msvc/Win32/Release/ -lportaudio_x86 + } + win32:INCLUDEPATH += ../portaudio/include + !win32:LIBS += -lportaudio +} macx:INCLUDEPATH += /usr/local/include /opt/local/include macx:LIBS += -L/usr/local/lib -L/opt/local/lib @@ -111,12 +127,9 @@ CONFIG(debug, release|debug) { win32:LIBS += -L../opus/win32/VS2015/Win32/Release/ -lopus } -#linux:LIBS += -L./ -l$$QCPLIB -lpulse -lpulse-simple -lpthread linux:LIBS += -L./ -l$$QCPLIB -lopus -macx:LIBS += -framework CoreAudio -framework CoreFoundation -lpthread -lopus +macx:LIBS += -framework CoreAudio -framework CoreFoundation -lpthread -lopus -#win32:SOURCES += rtaudio/RTAudio.cpp -#win32:HEADERS += rtaudio/RTAUdio.h !linux:SOURCES += ../qcustomplot/qcustomplot.cpp !linux:HEADERS += ../qcustomplot/qcustomplot.h !linux:INCLUDEPATH += ../qcustomplot @@ -124,7 +137,6 @@ macx:LIBS += -framework CoreAudio -framework CoreFoundation -lpthread -lopus !linux:INCLUDEPATH += ../opus/include INCLUDEPATH += resampler -!linux:INCLUDEPATH += rtaudio SOURCES += main.cpp\ wfmain.cpp \