Skip to content

Commit

Permalink
Merge pull request #689 from drowe67/ms-portaudio-cache
Browse files Browse the repository at this point in the history
Cache PortAudio sound info to improve startup performance.
  • Loading branch information
tmiw authored Feb 19, 2024
2 parents 7678245 + edc84e4 commit 605e20b
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 88 deletions.
1 change: 1 addition & 0 deletions USER_MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,7 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes
## V1.9.9 TBD 2024

1. Bugfixes:
* Cache PortAudio sound info to improve startup performance. (PR #689)
* Fix typo in cardinal directions list. (PR #688)
* Shrink size of callsign list to prevent it from disappearing off the screen. (PR #692)

Expand Down
105 changes: 88 additions & 17 deletions src/audio/PortAudioEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
//
//=========================================================================

#include <mutex>
#include <condition_variable>

#include <wx/string.h>
#include "portaudio.h"
#include "PortAudioDevice.h"
Expand Down Expand Up @@ -51,17 +54,66 @@ void PortAudioEngine::start()
}
else
{
if (cachePopulationThread_.joinable())
{
cachePopulationThread_.join();
}

std::mutex theadStartMtx;
std::condition_variable threadStartCV;
std::unique_lock<std::mutex> threadStartLock(theadStartMtx);

cachePopulationThread_ = std::thread([&]() {
std::unique_lock<std::recursive_mutex> lock(deviceCacheMutex_);

{
std::unique_lock<std::mutex> 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<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList(AudioDirection direction)
{
std::unique_lock<std::recursive_mutex> lock(deviceCacheMutex_);
return deviceListCache_[direction];
}

std::vector<int> PortAudioEngine::getSupportedSampleRates(wxString deviceName, AudioDirection direction)
{
std::unique_lock<std::recursive_mutex> lock(deviceCacheMutex_);

auto key = std::pair<wxString, AudioDirection>(deviceName, direction);
if (sampleRateList_.count(key) == 0)
{
sampleRateList_[key] = getSupportedSampleRates_(deviceName, direction);
}
return sampleRateList_[key];
}

std::vector<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList_(AudioDirection direction)
{
int numDevices = Pa_GetDeviceCount();
std::vector<AudioDeviceSpecification> result;
Expand Down Expand Up @@ -92,25 +144,43 @@ std::vector<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList(AudioD
// 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;
}
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.
Expand All @@ -119,8 +189,7 @@ std::vector<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList(AudioD
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);
Expand All @@ -130,14 +199,14 @@ std::vector<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList(AudioD
return result;
}

std::vector<int> PortAudioEngine::getSupportedSampleRates(wxString deviceName, AudioDirection direction)
std::vector<int> PortAudioEngine::getSupportedSampleRates_(wxString deviceName, AudioDirection direction)
{
std::vector<int> result;
auto devInfo = getAudioDeviceList(direction);

for (auto& device : devInfo)
{
if (device.name.Find(deviceName) == 0)
if (device.name.IsSameAs(deviceName))
{
PaStreamParameters streamParameters;

Expand All @@ -161,7 +230,9 @@ std::vector<int> PortAudioEngine::getSupportedSampleRates(wxString deviceName, A
}

rateIndex++;
}
}

break;
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/audio/PortAudioEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
#ifndef PORT_AUDIO_ENGINE_H
#define PORT_AUDIO_ENGINE_H

#include <map>
#include <thread>
#include <mutex>
#include "IAudioEngine.h"

class PortAudioEngine : public IAudioEngine
Expand All @@ -40,6 +43,15 @@ class PortAudioEngine : public IAudioEngine

private:
bool initialized_;

// Device cache and associated management.
std::map<AudioDirection, std::vector<AudioDeviceSpecification> > deviceListCache_;
std::map<std::pair<wxString, AudioDirection>, std::vector<int> > sampleRateList_;
std::recursive_mutex deviceCacheMutex_;
std::thread cachePopulationThread_;

std::vector<AudioDeviceSpecification> getAudioDeviceList_(AudioDirection direction);
std::vector<int> getSupportedSampleRates_(wxString deviceName, AudioDirection direction);
};

#endif // PORT_AUDIO_ENGINE_H
23 changes: 1 addition & 22 deletions src/gui/dialogs/dlg_audiooptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}


Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -1057,7 +1043,6 @@ void AudioOptsDialog::plotDeviceOutputForAFewSecs(wxString devName, PlotScalar *
}

device->stop();

codec2_fifo_destroy(callbackFifo);
}
break;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
12 changes: 2 additions & 10 deletions src/gui/dialogs/dlg_easy_setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -736,9 +736,6 @@ void EasySetupDialog::OnTest(wxCommandEvent& event)
{
txTestAudioDevice_->stop();
txTestAudioDevice_ = nullptr;

auto audioEngine = AudioEngineFactory::GetAudioEngine();
audioEngine->stop();
}

if (hamlibTestObject_ != nullptr && hamlibTestObject_->isConnected())
Expand Down Expand Up @@ -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)
Expand All @@ -898,8 +893,6 @@ void EasySetupDialog::OnTest(wxCommandEvent& event)
serialPortTestObject_->disconnect();
serialPortTestObject_ = nullptr;
}

audioEngine->stop();
return;
}

Expand Down Expand Up @@ -1136,8 +1129,9 @@ void EasySetupDialog::updateAudioDevices_()
std::map<wxString, SoundDeviceData*> 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);

Expand Down Expand Up @@ -1385,8 +1379,6 @@ void EasySetupDialog::updateAudioDevices_()
m_analogDevicePlayback->SetSelection(index);
}
}

audioEngine->stop();
}

bool EasySetupDialog::canTestRadioSettings_()
Expand Down
Loading

0 comments on commit 605e20b

Please sign in to comment.