Easy Setup: Use card names instead of device names for generating device list (#932)

* WIP code to get card/port names.

* Fix Linux compiler errors.

* Ensure that PulseAudio is adding card names when being interrogated.

* Initial logic to use card names instead of device names in Easy Setup dialog.

* WASAPI: Make sure Easy Setup can still work with FlexRadio virtual devices.

* Remove Windows-specific logic from Easy Setup dialog code.

* Add PR #932 to changelog.
pull/935/head
Mooneer Salem 2025-06-14 01:53:49 -07:00 committed by GitHub
parent 9824bdd3f9
commit c5a041c989
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 123 additions and 71 deletions

View File

@ -804,6 +804,7 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes
* FreeDV Reporter: Fix issue with first column not being aligned properly with other columns. (PR #922)
* FreeDV Reporter: Work around Linux bug preventing some flag emojis from being fully deleted on backspace. (PR #931)
* Fix GTK+ assertion after FreeDV Reporter has been open for a long time. (PR #929)
* Easy Setup: Use card names instead of device names for generating device list. (PR #932)
2. Documentation:
* Add missing dependency for macOS builds to README. (PR #925; thanks @relistan!)
* Add note about using XWayland on Linux. (PR #926)

View File

@ -29,8 +29,11 @@
struct AudioDeviceSpecification
{
int deviceId;
wxString name;
wxString apiName;
wxString name; // Display/config name of device
wxString cardName; // Name of the audio device
int cardIndex; // TBD - internal data for PulseAudio to look up cardName
wxString portName; // Name of the port from the above audio device (e.g. "Speakers" on Windows). Optional.
wxString apiName; // Name of the active audio API
int defaultSampleRate;
int maxChannels;
int minChannels;

View File

@ -363,6 +363,7 @@ AudioDeviceSpecification MacAudioEngine::getAudioSpecification_(int coreAudioId,
AudioDeviceSpecification device;
device.deviceId = coreAudioId;
device.name = wxString::FromUTF8(deviceName);
device.cardName = device.name;
device.apiName = "Core Audio";
device.maxChannels = numChannels;
device.minChannels = 1;

View File

@ -135,6 +135,7 @@ std::vector<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList(AudioD
AudioDeviceSpecification device;
device.deviceId = index;
device.name = wxString::FromUTF8(deviceInfo->name);
device.cardName = device.name;
device.apiName = hostApiName;
// For "whitelisted" devices, also assume channel counts

View File

@ -23,6 +23,9 @@
#include "PulseAudioDevice.h"
#include "PulseAudioEngine.h"
#include <map>
#include "../util/logging/ulog.h"
PulseAudioEngine::PulseAudioEngine()
: initialized_(false)
{
@ -140,6 +143,7 @@ void PulseAudioEngine::stop()
struct PulseAudioDeviceListTemp
{
std::vector<AudioDeviceSpecification> result;
std::map<int, std::string> cardResult;
PulseAudioEngine* thisPtr;
};
@ -164,7 +168,8 @@ std::vector<AudioDeviceSpecification> PulseAudioEngine::getAudioDeviceList(Audio
AudioDeviceSpecification device;
device.deviceId = i->index;
device.name = i->name;
device.name = wxString::FromUTF8(i->name);
device.cardIndex = i->card;
device.apiName = "PulseAudio";
device.maxChannels = i->sample_spec.channels;
device.minChannels = 1; // TBD: can minimum be >1 on PulseAudio or pipewire?
@ -187,7 +192,8 @@ std::vector<AudioDeviceSpecification> PulseAudioEngine::getAudioDeviceList(Audio
AudioDeviceSpecification device;
device.deviceId = i->index;
device.name = i->name;
device.name = wxString::FromUTF8(i->name);
device.cardIndex = i->card;
device.apiName = "PulseAudio";
device.maxChannels = i->sample_spec.channels;
device.minChannels = 1; // TBD: can minimum be >1 on PulseAudio or pipewire?
@ -204,8 +210,40 @@ std::vector<AudioDeviceSpecification> PulseAudioEngine::getAudioDeviceList(Audio
pa_threaded_mainloop_wait(mainloop_);
}
pa_operation_unref(op);
pa_operation_unref(op);
// Get list of cards
op = pa_context_get_card_info_list(context_, [](pa_context *c, const pa_card_info *i, int eol, void *userdata)
{
PulseAudioDeviceListTemp* tempObj = static_cast<PulseAudioDeviceListTemp*>(userdata);
if (eol)
{
pa_threaded_mainloop_signal(tempObj->thisPtr->mainloop_, 0);
return;
}
tempObj->cardResult[i->index] = i->name;
}, &tempObj);
// Wait for the operation to complete
for(;;)
{
if (pa_operation_get_state(op) != PA_OPERATION_RUNNING) break;
pa_threaded_mainloop_wait(mainloop_);
}
pa_operation_unref(op);
pa_threaded_mainloop_unlock(mainloop_);
// Iterate over result and populate cardName
for (auto& obj : tempObj.result)
{
if (tempObj.cardResult.find(obj.cardIndex) != tempObj.cardResult.end())
{
obj.cardName = wxString::FromUTF8(tempObj.cardResult[obj.cardIndex].c_str());
}
}
return tempObj.result;
}

View File

@ -179,6 +179,7 @@ std::vector<AudioDeviceSpecification> WASAPIAudioEngine::getAudioDeviceList(Audi
{
devSpec.deviceId = index;
result.push_back(devSpec);
log_debug("Found device %s (card = %s, port = %s)", (const char*)devSpec.name.ToUTF8(), (const char*)devSpec.cardName.ToUTF8(), (const char*)devSpec.portName.ToUTF8());
}
device->Release();
}
@ -381,6 +382,73 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d
AudioDeviceSpecification spec;
spec.name = wxString::FromUTF8(getUTF8String_(friendlyName.pwszVal));
spec.apiName = "Windows WASAPI";
// Get card and port info
hr = propStore->GetValue(PKEY_DeviceInterface_FriendlyName, &friendlyName);
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not get card name (hr = " << hr << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
PropVariantClear(&friendlyName);
propStore->Release();
return AudioDeviceSpecification::GetInvalidDevice();
}
if (friendlyName.vt == VT_EMPTY)
{
log_warn("Device does not have a card name!");
PropVariantClear(&friendlyName);
propStore->Release();
return AudioDeviceSpecification::GetInvalidDevice();
}
spec.cardName = wxString::FromUTF8(getUTF8String_(friendlyName.pwszVal));
hr = propStore->GetValue(PKEY_Device_DeviceDesc, &friendlyName);
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not get port name (hr = " << hr << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
PropVariantClear(&friendlyName);
propStore->Release();
return AudioDeviceSpecification::GetInvalidDevice();
}
if (friendlyName.vt == VT_EMPTY)
{
log_warn("Device does not have a port name!");
PropVariantClear(&friendlyName);
propStore->Release();
return AudioDeviceSpecification::GetInvalidDevice();
}
spec.portName = wxString::FromUTF8(getUTF8String_(friendlyName.pwszVal));
bool noPortName = spec.portName.Length() == 0 && spec.cardName != spec.name;
bool isFlexRadio = spec.cardName.StartsWith("FlexRadio");
if (noPortName)
{
// If there's no port name, just use the same name for cardName
// as the card's full name.
spec.cardName = spec.name;
}
else if (isFlexRadio)
{
// We also have a carveout for FlexRadio virtual audio devices
// so that Easy Setup can automatically group the RX and TX
// devices together.
spec.cardName = spec.portName;
}
// Activate IAudioClient so we can obtain format info
IAudioClient* audioClient = nullptr;

View File

@ -1151,14 +1151,14 @@ void EasySetupDialog::updateAudioDevices_()
for (auto& dev : inputDevices)
{
if (dev.name.Find(_("Microsoft Sound Mapper")) != -1 ||
dev.name.Find(_(" [Loopback]")) != -1)
if (dev.cardName.Find(_("Microsoft Sound Mapper")) != -1 ||
dev.cardName.Find(_(" [Loopback]")) != -1)
{
// Sound mapper and loopback devices should be skipped.
continue;
}
wxString cleanedDeviceName = dev.name;
wxString cleanedDeviceName = dev.cardName;
SoundDeviceData* soundData = new SoundDeviceData();
assert(soundData != nullptr);
@ -1187,74 +1187,14 @@ void EasySetupDialog::updateAudioDevices_()
for (auto& dev : outputDevices)
{
if (dev.name.Find(_("Microsoft Sound Mapper")) != -1 ||
dev.name.Find(_(" [Loopback]")) != -1)
if (dev.cardName.Find(_("Microsoft Sound Mapper")) != -1 ||
dev.cardName.Find(_(" [Loopback]")) != -1)
{
// Sound mapper and loopback devices should be skipped.
continue;
}
// For Windows, some devices have a designator at the beginning
// (e.g. "Microphone (USB Audio CODEC)" and "Speakers (USB Audio CODEC)".
// We need to be able to strip the designator without affecting
// the actual name of the device. To do this, we remove a character
// at a time from the beginning of the device name until we find a
// device in the current list with the same suffix. If we do find
// such a device, then we use this suffix as the device name to show
// in the list instead.
wxString cleanedDeviceName = dev.name;
if (finalRadioDeviceList.find(dev.name) == finalRadioDeviceList.end())
{
SoundDeviceData* foundItem = nullptr;
wxString oldDeviceName;
do
{
for (auto& kvp : finalRadioDeviceList)
{
if (kvp.first.Find("DAX") == 0)
{
// FlexRadio devices are treated differently
// so we shouldn't consider them here.
continue;
}
auto suffix = kvp.first.Mid(kvp.first.size() - cleanedDeviceName.size());
if (suffix == cleanedDeviceName)
{
foundItem = kvp.second;
oldDeviceName = kvp.first;
break;
}
}
if (foundItem == nullptr)
{
cleanedDeviceName = cleanedDeviceName.Mid(1);
}
} while (cleanedDeviceName.Length() > 5 && foundItem == nullptr);
if (foundItem == nullptr)
{
cleanedDeviceName = dev.name;
}
else
{
// Rename device in device list to "cleaned up" name.
cleanedDeviceName.Trim(false);
cleanedDeviceName.Trim(true);
auto parenthesisLoc = cleanedDeviceName.Find(_("("));
if (parenthesisLoc >= 0)
{
cleanedDeviceName = cleanedDeviceName.Mid(parenthesisLoc + 1);
if (cleanedDeviceName.Right(1) == _(")"))
{
cleanedDeviceName.RemoveLast(1);
}
}
finalRadioDeviceList.erase(oldDeviceName);
finalRadioDeviceList[cleanedDeviceName] = foundItem;
}
}
wxString cleanedDeviceName = dev.cardName;
SoundDeviceData* soundData = finalRadioDeviceList[cleanedDeviceName];
if (soundData == nullptr)