From 304bd5d951870e1761052efbb28f76b9e4294455 Mon Sep 17 00:00:00 2001 From: Mooneer Salem Date: Sun, 11 Feb 2024 01:54:45 -0500 Subject: [PATCH 1/4] Cache PortAudio sound info to improve startup performance. --- src/audio/PortAudioEngine.cpp | 60 ++++++++++++++++++++++++++-- src/audio/PortAudioEngine.h | 12 ++++++ src/gui/dialogs/dlg_audiooptions.cpp | 23 +---------- src/gui/dialogs/dlg_easy_setup.cpp | 12 +----- src/main.cpp | 54 +++++++------------------ 5 files changed, 87 insertions(+), 74 deletions(-) diff --git a/src/audio/PortAudioEngine.cpp b/src/audio/PortAudioEngine.cpp index 58778561f..918126717 100644 --- a/src/audio/PortAudioEngine.cpp +++ b/src/audio/PortAudioEngine.cpp @@ -20,6 +20,9 @@ // //========================================================================= +#include +#include + #include #include "portaudio.h" #include "PortAudioDevice.h" @@ -51,17 +54,66 @@ void PortAudioEngine::start() } else { + if (cachePopulationThread_.joinable()) + { + cachePopulationThread_.join(); + } + + std::mutex theadStartMtx; + std::condition_variable threadStartCV; + std::unique_lock threadStartLock(theadStartMtx); + + cachePopulationThread_ = std::thread([&]() { + std::unique_lock lock(deviceCacheMutex_); + + { + std::unique_lock innerThreadStartLock(theadStartMtx); + threadStartCV.notify_one(); + } + + deviceListCache_[AUDIO_ENGINE_IN] = getAudioDeviceList_(AUDIO_ENGINE_IN); + deviceListCache_[AUDIO_ENGINE_OUT] = getAudioDeviceList_(AUDIO_ENGINE_OUT); + }); + + threadStartCV.wait(threadStartLock); + initialized_ = true; } } void PortAudioEngine::stop() { + if (cachePopulationThread_.joinable()) + { + cachePopulationThread_.join(); + } + Pa_Terminate(); + initialized_ = false; + deviceListCache_.clear(); + sampleRateList_.clear(); } std::vector PortAudioEngine::getAudioDeviceList(AudioDirection direction) +{ + std::unique_lock lock(deviceCacheMutex_); + return deviceListCache_[direction]; +} + +std::vector PortAudioEngine::getSupportedSampleRates(wxString deviceName, AudioDirection direction) +{ + std::unique_lock lock(deviceCacheMutex_); + + auto key = std::pair(deviceName, direction); + if (sampleRateList_.count(key) == 0) + { + sampleRateList_[key] = getSupportedSampleRates_(deviceName, direction); + } + return sampleRateList_[key]; +} + +std::vector PortAudioEngine::getAudioDeviceList_(AudioDirection direction) { int numDevices = Pa_GetDeviceCount(); std::vector result; @@ -130,14 +182,14 @@ std::vector PortAudioEngine::getAudioDeviceList(AudioD return result; } -std::vector PortAudioEngine::getSupportedSampleRates(wxString deviceName, AudioDirection direction) +std::vector PortAudioEngine::getSupportedSampleRates_(wxString deviceName, AudioDirection direction) { std::vector result; auto devInfo = getAudioDeviceList(direction); for (auto& device : devInfo) { - if (device.name.Find(deviceName) == 0) + if (device.name.IsSameAs(deviceName)) { PaStreamParameters streamParameters; @@ -161,7 +213,9 @@ std::vector PortAudioEngine::getSupportedSampleRates(wxString deviceName, A } rateIndex++; - } + } + + break; } } diff --git a/src/audio/PortAudioEngine.h b/src/audio/PortAudioEngine.h index fde82319e..a1ad9b2a9 100644 --- a/src/audio/PortAudioEngine.h +++ b/src/audio/PortAudioEngine.h @@ -23,6 +23,9 @@ #ifndef PORT_AUDIO_ENGINE_H #define PORT_AUDIO_ENGINE_H +#include +#include +#include #include "IAudioEngine.h" class PortAudioEngine : public IAudioEngine @@ -40,6 +43,15 @@ class PortAudioEngine : public IAudioEngine private: bool initialized_; + + // Device cache and associated management. + std::map > deviceListCache_; + std::map, std::vector > sampleRateList_; + std::recursive_mutex deviceCacheMutex_; + std::thread cachePopulationThread_; + + std::vector getAudioDeviceList_(AudioDirection direction); + std::vector getSupportedSampleRates_(wxString deviceName, AudioDirection direction); }; #endif // PORT_AUDIO_ENGINE_H \ No newline at end of file diff --git a/src/gui/dialogs/dlg_audiooptions.cpp b/src/gui/dialogs/dlg_audiooptions.cpp index 785ad0605..207ed67ff 100644 --- a/src/gui/dialogs/dlg_audiooptions.cpp +++ b/src/gui/dialogs/dlg_audiooptions.cpp @@ -50,18 +50,6 @@ extern wxConfigBase *pConfig; void AudioOptsDialog::audioEngineInit(void) { m_isPaInitialized = true; - - auto engine = AudioEngineFactory::GetAudioEngine(); - engine->setOnEngineError([this](IAudioEngine&, std::string error, void*) - { - CallAfter([&]() { - wxMessageBox(wxT("Sound engine failed to initialize"), wxT("Error"), wxOK); - }); - - m_isPaInitialized = false; - }, nullptr); - - engine->start(); } @@ -356,8 +344,6 @@ AudioOptsDialog::~AudioOptsDialog() wxGetApp().appConfiguration.audioConfigWindowWidth = sz.GetWidth(); wxGetApp().appConfiguration.audioConfigWindowHeight = sz.GetHeight(); - AudioEngineFactory::GetAudioEngine()->stop(); - // Disconnect Events this->Disconnect(wxEVT_HIBERNATE, wxActivateEventHandler(AudioOptsDialog::OnHibernate)); this->Disconnect(wxEVT_ICONIZE, wxIconizeEventHandler(AudioOptsDialog::OnIconize)); @@ -638,7 +624,7 @@ int AudioOptsDialog::buildListOfSupportedSampleRates(wxComboBox *cbSampleRate, w cbSampleRate->Clear(); for (auto& dev : deviceList) { - if (dev.name.Find(devName) == 0) + if (dev.name.IsSameAs(devName)) { auto supportedSampleRates = engine->getSupportedSampleRates( @@ -1057,7 +1043,6 @@ void AudioOptsDialog::plotDeviceOutputForAFewSecs(wxString devName, PlotScalar * } device->stop(); - codec2_fifo_destroy(callbackFifo); } break; @@ -1149,9 +1134,6 @@ void AudioOptsDialog::OnCancelAudioParameters(wxCommandEvent& event) { if(m_isPaInitialized) { - auto engine = AudioEngineFactory::GetAudioEngine(); - engine->stop(); - engine->setOnEngineError(nullptr, nullptr); m_isPaInitialized = false; } EndModal(wxCANCEL); @@ -1170,9 +1152,6 @@ void AudioOptsDialog::OnOkAudioParameters(wxCommandEvent& event) if (status == 0) { if(m_isPaInitialized) { - auto engine = AudioEngineFactory::GetAudioEngine(); - engine->stop(); - engine->setOnEngineError(nullptr, nullptr); m_isPaInitialized = false; } EndModal(wxOK); diff --git a/src/gui/dialogs/dlg_easy_setup.cpp b/src/gui/dialogs/dlg_easy_setup.cpp index 8a76a7d81..9f5b83226 100644 --- a/src/gui/dialogs/dlg_easy_setup.cpp +++ b/src/gui/dialogs/dlg_easy_setup.cpp @@ -736,9 +736,6 @@ void EasySetupDialog::OnTest(wxCommandEvent& event) { txTestAudioDevice_->stop(); txTestAudioDevice_ = nullptr; - - auto audioEngine = AudioEngineFactory::GetAudioEngine(); - audioEngine->stop(); } if (hamlibTestObject_ != nullptr && hamlibTestObject_->isConnected()) @@ -876,8 +873,6 @@ void EasySetupDialog::OnTest(wxCommandEvent& event) if (radioOutDeviceName != "none") { auto audioEngine = AudioEngineFactory::GetAudioEngine(); - audioEngine->start(); - txTestAudioDevice_ = audioEngine->getAudioDevice(radioOutDeviceName, IAudioEngine::AUDIO_ENGINE_OUT, radioOutSampleRate, 1); if (txTestAudioDevice_ == nullptr) @@ -898,8 +893,6 @@ void EasySetupDialog::OnTest(wxCommandEvent& event) serialPortTestObject_->disconnect(); serialPortTestObject_ = nullptr; } - - audioEngine->stop(); return; } @@ -1136,8 +1129,9 @@ void EasySetupDialog::updateAudioDevices_() std::map finalAnalogTxDeviceList; auto audioEngine = AudioEngineFactory::GetAudioEngine(); + audioEngine->stop(); audioEngine->start(); - + auto inputDevices = audioEngine->getAudioDeviceList(IAudioEngine::AUDIO_ENGINE_IN); auto outputDevices = audioEngine->getAudioDeviceList(IAudioEngine::AUDIO_ENGINE_OUT); @@ -1385,8 +1379,6 @@ void EasySetupDialog::updateAudioDevices_() m_analogDevicePlayback->SetSelection(index); } } - - audioEngine->stop(); } bool EasySetupDialog::canTestRadioSettings_() diff --git a/src/main.cpp b/src/main.cpp index d57c3e8ee..45f4e9e6a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -630,6 +630,17 @@ MainFrame::MainFrame(wxWindow *parent) : TopFrame(parent, wxID_ANY, _("FreeDV ") pthread_setname_np(pthread_self(), "FreeDV GUI"); #endif // defined(__linux__) + // Begin loading of audio device cache. + auto engine = AudioEngineFactory::GetAudioEngine(); + engine->setOnEngineError([&](IAudioEngine&, std::string error, void* state) { + executeOnUiThreadAndWait_([&, error]() { + wxMessageBox(wxString::Format( + "Error encountered while initializing the audio engine: %s.", + error), wxT("Error"), wxOK, this); + }); + }, nullptr); + engine->start(); + m_reporterDialog = nullptr; m_filterDialog = nullptr; @@ -2391,10 +2402,6 @@ void MainFrame::stopRxStream() m_newSpkOutFilter = true; deleteEQFilters(g_rxUserdata); delete g_rxUserdata; - - auto engine = AudioEngineFactory::GetAudioEngine(); - engine->stop(); - engine->setOnEngineError(nullptr, nullptr); } } @@ -2416,28 +2423,16 @@ void MainFrame::startRxStream() if (g_verbose) fprintf(stderr, "startRxStream .....\n"); if(!m_RxRunning) { m_RxRunning = true; - - auto engine = AudioEngineFactory::GetAudioEngine(); - engine->setOnEngineError([&](IAudioEngine&, std::string error, void* state) { - executeOnUiThreadAndWait_([&, error]() { - wxMessageBox(wxString::Format( - "Error encountered while initializing the audio engine: %s.", - error), wxT("Error"), wxOK, this); - }); - }, nullptr); - engine->start(); + auto engine = AudioEngineFactory::GetAudioEngine(); + if (g_nSoundCards == 0) { executeOnUiThreadAndWait_([&]() { wxMessageBox(wxT("No Sound Cards configured, use Tools - Audio Config to configure"), wxT("Error"), wxOK); }); - m_RxRunning = false; - - engine->stop(); - engine->setOnEngineError(nullptr, nullptr); - + m_RxRunning = false; return; } else if (g_nSoundCards == 1) @@ -2491,11 +2486,7 @@ void MainFrame::startRxStream() rxOutSoundDevice.reset(); } - m_RxRunning = false; - - engine->stop(); - engine->setOnEngineError(nullptr, nullptr); - + m_RxRunning = false; return; } else @@ -2602,10 +2593,6 @@ void MainFrame::startRxStream() } m_RxRunning = false; - - engine->stop(); - engine->setOnEngineError(nullptr, nullptr); - return; } else @@ -3023,14 +3010,6 @@ bool MainFrame::validateSoundCardSetup() // Translate device names to IDs auto engine = AudioEngineFactory::GetAudioEngine(); - engine->setOnEngineError([&](IAudioEngine&, std::string error, void* state) { - CallAfter([&]() { - wxMessageBox(wxString::Format( - "Error encountered while initializing the audio engine: %s.", - error), wxT("Error"), wxOK, this); - }); - }, nullptr); - engine->start(); auto defaultInputDevice = engine->getDefaultAudioDevice(IAudioEngine::AUDIO_ENGINE_IN); auto defaultOutputDevice = engine->getDefaultAudioDevice(IAudioEngine::AUDIO_ENGINE_OUT); @@ -3175,9 +3154,6 @@ bool MainFrame::validateSoundCardSetup() canRun = false; } - engine->stop(); - engine->setOnEngineError(nullptr, nullptr); - return canRun; } From 418d61aba8351fa283e0a2d2f201a0d0b2240f15 Mon Sep 17 00:00:00 2001 From: Mooneer Salem Date: Sun, 11 Feb 2024 01:56:43 -0500 Subject: [PATCH 2/4] Add PR #689 to changelog. --- CMakeLists.txt | 4 ++-- USER_MANUAL.md | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b511e51d..a3381e247 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ endif() set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11" CACHE STRING "Minimum OS X deployment version") set(PROJECT_NAME FreeDV) -set(PROJECT_VERSION 1.9.8) +set(PROJECT_VERSION 1.9.9) set(PROJECT_DESCRIPTION "HF Digital Voice for Radio Amateurs") set(PROJECT_HOMEPAGE_URL "https://freedv.org") @@ -48,7 +48,7 @@ endif(APPLE) # Adds a tag to the end of the version string. Leave empty # for official release builds. -set(FREEDV_VERSION_TAG "") +set(FREEDV_VERSION_TAG "devel") # Prevent in-source builds to protect automake/autoconf config. # If an in-source build is attempted, you will still need to clean up a few diff --git a/USER_MANUAL.md b/USER_MANUAL.md index fa79364ba..5740bcfa3 100644 --- a/USER_MANUAL.md +++ b/USER_MANUAL.md @@ -889,6 +889,11 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes # Release Notes +## V1.9.9 TBD 2024 + +1. Bugfixes: + * Cache PortAudio sound info to improve startup performance. (PR #689) + ## V1.9.8 February 2024 1. Bugfixes: From 18efb30f800ea364c3e0fe90ef4965a5decf3ba4 Mon Sep 17 00:00:00 2001 From: Mooneer Salem Date: Fri, 16 Feb 2024 21:13:00 -0800 Subject: [PATCH 3/4] Assume minChannels = 1 for ALSA special devices. --- src/audio/PortAudioEngine.cpp | 46 ++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/audio/PortAudioEngine.cpp b/src/audio/PortAudioEngine.cpp index 918126717..c601d4bd0 100644 --- a/src/audio/PortAudioEngine.cpp +++ b/src/audio/PortAudioEngine.cpp @@ -144,25 +144,44 @@ std::vector PortAudioEngine::getAudioDeviceList_(Audio // on Windows. PaStreamParameters streamParameters; streamParameters.device = index; - streamParameters.channelCount = 1; streamParameters.sampleFormat = paInt16; streamParameters.suggestedLatency = Pa_GetDeviceInfo(index)->defaultHighInputLatency; streamParameters.hostApiSpecificStreamInfo = NULL; - + streamParameters.channelCount = 1; + + // On Linux, the below logic causes the device lookup process to take MUCH + // longer than it does on other platforms, mainly because of the special devices + // it provides to PortAudio. For these, we're just going to assume the minimum + // valid channels is 1. int maxChannels = direction == AUDIO_ENGINE_IN ? deviceInfo->maxInputChannels : deviceInfo->maxOutputChannels; - while (streamParameters.channelCount < maxChannels) + bool isDeviceWithKnownMinimum = + !strcmp(deviceInfo->name, "sysdefault") || + !strcmp(deviceInfo->name, "front") || + !strcmp(deviceInfo->name, "surround") || + !strcmp(deviceInfo->name, "samplerate") || + !strcmp(deviceInfo->name, "speexrate") || + !strcmp(deviceInfo->name, "pulse") || + !strcmp(deviceInfo->name, "upmix") || + !strcmp(deviceInfo->name, "vdownmix") || + !strcmp(deviceInfo->name, "dmix") || + !strcmp(deviceInfo->name, "default"); + if (!isDeviceWithKnownMinimum) { - PaError err = Pa_IsFormatSupported( - direction == AUDIO_ENGINE_IN ? &streamParameters : NULL, - direction == AUDIO_ENGINE_OUT ? &streamParameters : NULL, - deviceInfo->defaultSampleRate); - - if (err == paFormatIsSupported) + while (streamParameters.channelCount < maxChannels) { - break; - } + fprintf(stderr, "checking channel# %d for device %s\n", streamParameters.channelCount, deviceInfo->name); + PaError err = Pa_IsFormatSupported( + direction == AUDIO_ENGINE_IN ? &streamParameters : NULL, + direction == AUDIO_ENGINE_OUT ? &streamParameters : NULL, + deviceInfo->defaultSampleRate); + + if (err != paFormatIsSupported) + { + break; + } - streamParameters.channelCount++; + streamParameters.channelCount++; + } } // Add information about this device to the result array. @@ -171,8 +190,7 @@ std::vector PortAudioEngine::getAudioDeviceList_(Audio device.name = wxString::FromUTF8(deviceInfo->name); device.apiName = hostApiName; device.minChannels = streamParameters.channelCount; - device.maxChannels = - direction == AUDIO_ENGINE_IN ? deviceInfo->maxInputChannels : deviceInfo->maxOutputChannels; + device.maxChannels = maxChannels; device.defaultSampleRate = deviceInfo->defaultSampleRate; result.push_back(device); From edc84e4dd5bb8b5228cec0fcd551274872a0db7c Mon Sep 17 00:00:00 2001 From: Mooneer Salem Date: Mon, 19 Feb 2024 13:12:19 -0800 Subject: [PATCH 4/4] Remove debugging output. --- src/audio/PortAudioEngine.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/audio/PortAudioEngine.cpp b/src/audio/PortAudioEngine.cpp index c601d4bd0..e515d0d81 100644 --- a/src/audio/PortAudioEngine.cpp +++ b/src/audio/PortAudioEngine.cpp @@ -169,7 +169,6 @@ std::vector PortAudioEngine::getAudioDeviceList_(Audio { while (streamParameters.channelCount < maxChannels) { - fprintf(stderr, "checking channel# %d for device %s\n", streamParameters.channelCount, deviceInfo->name); PaError err = Pa_IsFormatSupported( direction == AUDIO_ENGINE_IN ? &streamParameters : NULL, direction == AUDIO_ENGINE_OUT ? &streamParameters : NULL,