From 82465be609a0893ff44a9f7f717f3e3192cba0a6 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Mon, 21 Aug 2023 11:27:04 +0200 Subject: [PATCH 1/2] [ActiveAE] Improve CAESinkFactory::ParseDevice Prepares to allow include friendly name in the device string e.g: Before WASAPI:{B12C1A75-F8A8-4071-8CFB-A285C619CCB8} After WASAPI:{B12C1A75-F8A8-4071-8CFB-A285C619CCB8}:HDMI - DENON-AVR (NVIDIA High Definition Audio) --- xbmc/cores/AudioEngine/AESinkFactory.cpp | 41 +++++++++---- xbmc/cores/AudioEngine/AESinkFactory.h | 11 +++- .../AudioEngine/Engines/ActiveAE/ActiveAE.cpp | 24 ++++---- .../Engines/ActiveAE/ActiveAESink.cpp | 58 +++++++++---------- 4 files changed, 79 insertions(+), 55 deletions(-) diff --git a/xbmc/cores/AudioEngine/AESinkFactory.cpp b/xbmc/cores/AudioEngine/AESinkFactory.cpp index d93a96351f701..6f27372de7786 100644 --- a/xbmc/cores/AudioEngine/AESinkFactory.cpp +++ b/xbmc/cores/AudioEngine/AESinkFactory.cpp @@ -38,41 +38,58 @@ bool CAESinkFactory::HasSinks() return !m_AESinkRegEntry.empty(); } -void CAESinkFactory::ParseDevice(std::string &device, std::string &driver) +AESinkDevice CAESinkFactory::ParseDevice(const std::string& device) { - int pos = device.find_first_of(':'); + AESinkDevice dev{}; + dev.name = device; + + size_t pos = dev.name.find_first_of(':'); bool found = false; - if (pos > 0) + + if (pos != std::string::npos) { - driver = device.substr(0, pos); + dev.driver = device.substr(0, pos); for (const auto& reg : m_AESinkRegEntry) { - if (!StringUtils::EqualsNoCase(driver, reg.second.sinkName)) + if (!StringUtils::EqualsNoCase(dev.driver, reg.second.sinkName)) continue; - device = device.substr(pos + 1, device.length() - pos - 1); + dev.name = dev.name.substr(pos + 1, dev.name.length() - pos - 1); found = true; } } if (!found) - driver.clear(); + dev.driver.clear(); + + pos = dev.name.find_first_of(':'); + + if (pos != std::string::npos) + { + // if no known driver found considers the string starts + // with the device name and discarts the rest + if (found) + dev.friendlyName = dev.name.substr(pos + 1); + dev.name = dev.name.substr(0, pos); + } + + return dev; } -std::unique_ptr CAESinkFactory::Create(std::string& device, AEAudioFormat& desiredFormat) +std::unique_ptr CAESinkFactory::Create(const std::string& device, + AEAudioFormat& desiredFormat) { // extract the driver from the device string if it exists - std::string driver; - ParseDevice(device, driver); + const AESinkDevice dev = ParseDevice(device); AEAudioFormat tmpFormat = desiredFormat; std::unique_ptr sink; - std::string tmpDevice = device; + std::string tmpDevice = dev.name; for (const auto& reg : m_AESinkRegEntry) { - if (driver != reg.second.sinkName) + if (dev.driver != reg.second.sinkName) continue; sink = reg.second.createFunc(tmpDevice, tmpFormat); diff --git a/xbmc/cores/AudioEngine/AESinkFactory.h b/xbmc/cores/AudioEngine/AESinkFactory.h index 92728bf2f91d4..704dc7673e7ce 100644 --- a/xbmc/cores/AudioEngine/AESinkFactory.h +++ b/xbmc/cores/AudioEngine/AESinkFactory.h @@ -42,6 +42,13 @@ struct AESinkRegEntry Cleanup cleanupFunc; }; +struct AESinkDevice +{ + std::string driver; + std::string name; + std::string friendlyName; +}; + class CAESinkFactory { public: @@ -49,8 +56,8 @@ class CAESinkFactory static void ClearSinks(); static bool HasSinks(); - static void ParseDevice(std::string &device, std::string &driver); - static std::unique_ptr Create(std::string& device, AEAudioFormat& desiredFormat); + static AESinkDevice ParseDevice(const std::string& device); + static std::unique_ptr Create(const std::string& device, AEAudioFormat& desiredFormat); static void EnumerateEx(std::vector& list, bool force, const std::string& driver); static void Cleanup(); diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp index f205a0c66bfd5..696d88c3f64e7 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp @@ -1191,17 +1191,18 @@ void CActiveAE::Configure(AEAudioFormat *desiredFmt) m_extKeepConfig = 0ms; std::string device = (m_sinkRequestFormat.m_dataFormat == AE_FMT_RAW) ? m_settings.passthroughdevice : m_settings.device; - std::string driver; - CAESinkFactory::ParseDevice(device, driver); - if ((!CompareFormat(m_sinkRequestFormat, m_sinkFormat) && !CompareFormat(m_sinkRequestFormat, oldSinkRequestFormat)) || - m_currDevice.compare(device) != 0 || - m_settings.driver.compare(driver) != 0) + + const AESinkDevice dev = CAESinkFactory::ParseDevice(device); + + if ((!CompareFormat(m_sinkRequestFormat, m_sinkFormat) && + !CompareFormat(m_sinkRequestFormat, oldSinkRequestFormat)) || + m_currDevice.compare(dev.name) != 0 || m_settings.driver.compare(dev.driver) != 0) { FlushEngine(); if (!InitSink()) return; - m_settings.driver = driver; - m_currDevice = device; + m_settings.driver = dev.driver; + m_currDevice = dev.name; initSink = true; m_stats.Reset(m_sinkFormat.m_sampleRate, m_mode == MODE_PCM); m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::VOLUME, &m_volume, sizeof(float)); @@ -1781,12 +1782,11 @@ bool CActiveAE::NeedReconfigureSink() ApplySettingsToFormat(newFormat, m_settings); std::string device = (newFormat.m_dataFormat == AE_FMT_RAW) ? m_settings.passthroughdevice : m_settings.device; - std::string driver; - CAESinkFactory::ParseDevice(device, driver); - return !CompareFormat(newFormat, m_sinkFormat) || - m_currDevice.compare(device) != 0 || - m_settings.driver.compare(driver) != 0; + const AESinkDevice dev = CAESinkFactory::ParseDevice(device); + + return !CompareFormat(newFormat, m_sinkFormat) || m_currDevice.compare(dev.name) != 0 || + m_settings.driver.compare(dev.driver) != 0; } bool CActiveAE::InitSink() diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp index 01a204c852039..0b22c41b3a7de 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp @@ -73,15 +73,14 @@ void CActiveAESink::Dispose() AEDeviceType CActiveAESink::GetDeviceType(const std::string &device) { - std::string dev = device; - std::string dri; - CAESinkFactory::ParseDevice(dev, dri); + const AESinkDevice dev = CAESinkFactory::ParseDevice(device); + for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) { for (AEDeviceInfoList::iterator itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2) { CAEDeviceInfo& info = *itt2; - if (info.m_deviceName == dev) + if (info.m_deviceName == dev.name) return info.m_deviceType; } } @@ -104,18 +103,16 @@ bool CActiveAESink::HasPassthroughDevice() bool CActiveAESink::SupportsFormat(const std::string &device, AEAudioFormat &format) { - std::string dev = device; - std::string dri; + const AESinkDevice dev = CAESinkFactory::ParseDevice(device); - CAESinkFactory::ParseDevice(dev, dri); for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) { - if (dri == itt->m_sinkName) + if (dev.driver == itt->m_sinkName) { for (auto itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2) { CAEDeviceInfo& info = *itt2; - if (info.m_deviceName == dev) + if (info.m_deviceName == dev.name) { bool isRaw = format.m_dataFormat == AE_FMT_RAW; bool formatExists = false; @@ -184,18 +181,16 @@ bool CActiveAESink::SupportsFormat(const std::string &device, AEAudioFormat &for bool CActiveAESink::NeedIECPacking() { - std::string dev = m_device; - std::string dri; + const AESinkDevice dev = CAESinkFactory::ParseDevice(m_device); - CAESinkFactory::ParseDevice(dev, dri); for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) { - if (dri == itt->m_sinkName) + if (dev.driver == itt->m_sinkName) { for (auto itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2) { CAEDeviceInfo& info = *itt2; - if (info.m_deviceName == dev) + if (info.m_deviceName == dev.name) { return info.m_wantsIECPassthrough; } @@ -757,6 +752,16 @@ void CActiveAESink::EnumerateOutputDevices(AEDeviceList &devices, bool passthrou std::string device = sinkInfo.m_sinkName + ":" + devInfo.m_deviceName; + const std::string friendlyName = (devInfo.m_deviceName != devInfo.m_displayName) + ? devInfo.m_displayName + : devInfo.m_displayNameExtra; + + if (!friendlyName.empty()) + { + device.append(":"); + device.append(friendlyName); + } + std::stringstream ss; /* add the sink name if we have more then one sink type */ @@ -793,15 +798,12 @@ void CActiveAESink::GetDeviceFriendlyName(const std::string& device) void CActiveAESink::OpenSink() { - // we need a copy of m_device here because ParseDevice and CreateDevice write back - // into this variable - std::string device = m_device; - std::string driver; bool passthrough = (m_requestedFormat.m_dataFormat == AE_FMT_RAW); - CAESinkFactory::ParseDevice(device, driver); - if (driver.empty() && m_sink) - driver = m_sink->GetName(); + AESinkDevice dev = CAESinkFactory::ParseDevice(m_device); + + if (dev.driver.empty() && m_sink) + dev.driver = m_sink->GetName(); // iec packing or raw if (passthrough) @@ -825,11 +827,10 @@ void CActiveAESink::OpenSink() } // get the display name of the device - GetDeviceFriendlyName(device); + GetDeviceFriendlyName(dev.name); // if we already have a driver, prepend it to the device string - if (!driver.empty()) - device = driver + ":" + device; + std::string device = dev.driver.empty() ? dev.name : dev.driver + ":" + dev.name; // WARNING: this changes format and does not use passthrough m_sinkFormat = m_requestedFormat; @@ -839,11 +840,10 @@ void CActiveAESink::OpenSink() // try first device in out list if (!m_sink && !m_sinkInfoList.empty()) { - driver = m_sinkInfoList.front().m_sinkName; - device = m_sinkInfoList.front().m_deviceInfoList.front().m_deviceName; - GetDeviceFriendlyName(device); - if (!driver.empty()) - device = driver + ":" + device; + dev.driver = m_sinkInfoList.front().m_sinkName; + dev.name = m_sinkInfoList.front().m_deviceInfoList.front().m_deviceName; + GetDeviceFriendlyName(dev.name); + device = dev.driver.empty() ? dev.name : dev.driver + ":" + dev.name; m_sinkFormat = m_requestedFormat; CLog::Log(LOGDEBUG, "CActiveAESink::OpenSink - trying to open device {}", device); m_sink = CAESinkFactory::Create(device, m_sinkFormat); From ba77cb94fc47faf4528892f33baee500dece1b54 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Mon, 21 Aug 2023 12:32:56 +0200 Subject: [PATCH 2/2] [ActiveAE] Validates audio device names in settings (existence in system) If device name not exist (drivers updates, small HW changes, etc.) is used this fallback logic: 1. Try other device in the same driver with same friendly name. 2. Try the default device of same driver. 3. Try the first device of same driver. 4. Try the default device of any driver. 5. Fallback to the first device of any driver. This should avoid 99% of times unexpected settings changes, i.e. from WASAPI to DIRECTSOUND. Settings are auto updated/fixed without user intervention when change is the same audio device renamed e.g: after audio driver updates. --- xbmc/cores/AudioEngine/AESinkFactory.h | 5 + .../AudioEngine/Engines/ActiveAE/ActiveAE.cpp | 56 ++++++++ .../AudioEngine/Engines/ActiveAE/ActiveAE.h | 1 + .../Engines/ActiveAE/ActiveAESink.cpp | 122 ++++++++++++++++-- .../Engines/ActiveAE/ActiveAESink.h | 1 + xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp | 16 +++ xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h | 2 + 7 files changed, 192 insertions(+), 11 deletions(-) diff --git a/xbmc/cores/AudioEngine/AESinkFactory.h b/xbmc/cores/AudioEngine/AESinkFactory.h index 704dc7673e7ce..18b2eff09cf60 100644 --- a/xbmc/cores/AudioEngine/AESinkFactory.h +++ b/xbmc/cores/AudioEngine/AESinkFactory.h @@ -47,6 +47,11 @@ struct AESinkDevice std::string driver; std::string name; std::string friendlyName; + + bool IsSameDeviceAs(const AESinkDevice& d) const + { + return driver == d.driver && (name == d.name || friendlyName == d.friendlyName); + } }; class CAESinkFactory diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp index 696d88c3f64e7..8bd9e45a7644a 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp @@ -516,6 +516,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) m_extError = false; m_sink.EnumerateSinkList(false, ""); LoadSettings(); + ValidateOutputDevices(true); Configure(); if (!m_isWinSysReg) { @@ -647,6 +648,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) m_controlPort.PurgeOut(CActiveAEControlProtocol::DEVICECHANGE); m_sink.EnumerateSinkList(true, ""); LoadSettings(); + ValidateOutputDevices(false); m_extError = false; Configure(); if (!m_extError) @@ -669,6 +671,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) { UnconfigureSink(); LoadSettings(); + ValidateOutputDevices(false); m_extError = false; Configure(); if (!m_extError) @@ -890,6 +893,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) m_controlPort.PurgeOut(CActiveAEControlProtocol::DEVICECOUNTCHANGE); m_sink.EnumerateSinkList(true, ""); LoadSettings(); + ValidateOutputDevices(false); } Configure(); if (!displayReset) @@ -2670,6 +2674,58 @@ void CActiveAE::LoadSettings() m_settings.silenceTimeoutMinutes = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_STREAMSILENCE); } +void CActiveAE::ValidateOutputDevices(bool saveChanges) +{ + std::string device = m_sink.ValidateOuputDevice(m_settings.device, false); + + if (!device.empty() && device != m_settings.device) + { + CLog::LogF(LOGWARNING, "audio output device setting has been updated from '{}' to '{}'", + m_settings.device, device); + + const AESinkDevice oldDevice = CAESinkFactory::ParseDevice(m_settings.device); + const AESinkDevice newDevice = CAESinkFactory::ParseDevice(device); + + m_settings.device = device; + + if (saveChanges && newDevice.IsSameDeviceAs(oldDevice)) + { + const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (settings) + { + settings->SetString(CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE, m_settings.device); + settings->Save(); + CLog::LogF(LOGDEBUG, "the change of the audio output device setting has been saved"); + } + } + } + + device = m_sink.ValidateOuputDevice(m_settings.passthroughdevice, true); + + if (!device.empty() && device != m_settings.passthroughdevice) + { + CLog::LogF(LOGWARNING, "passthrough output device setting has been updated from '{}' to '{}'", + m_settings.passthroughdevice, device); + + const AESinkDevice oldDevice = CAESinkFactory::ParseDevice(m_settings.passthroughdevice); + const AESinkDevice newDevice = CAESinkFactory::ParseDevice(device); + + m_settings.passthroughdevice = device; + + if (saveChanges && newDevice.IsSameDeviceAs(oldDevice)) + { + const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (settings) + { + settings->SetString(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE, + m_settings.passthroughdevice); + settings->Save(); + CLog::LogF(LOGDEBUG, "the change of the passthrough output device setting has been saved"); + } + } + } +} + void CActiveAE::Start() { Create(); diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h index 1ddce5eb18fbe..7c43d2037ce4e 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h @@ -308,6 +308,7 @@ class CActiveAE : public IAE, public IDispResource, private CThread void UnconfigureSink(); void Dispose(); void LoadSettings(); + void ValidateOutputDevices(bool saveChanges); bool NeedReconfigureBuffers(); bool NeedReconfigureSink(); void ApplySettingsToFormat(AEAudioFormat& format, diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp index 0b22c41b3a7de..54d46e3fd86c3 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp @@ -728,6 +728,116 @@ void CActiveAESink::PrintSinks(std::string& driver) } } +std::string CActiveAESink::ValidateOuputDevice(const std::string& device, bool passthrough) const +{ + if (m_sinkInfoList.empty()) + return {}; + + const AESinkDevice dev = CAESinkFactory::ParseDevice(device); + + // find exact match of deviceName in same driver + if (!dev.driver.empty() && !dev.name.empty()) + { + for (const auto& sink : m_sinkInfoList) + { + if (sink.m_sinkName != dev.driver) + continue; + + for (const auto& d : sink.m_deviceInfoList) + { + if (passthrough && (d.m_deviceType == AE_DEVTYPE_PCM || d.m_onlyPCM)) + continue; + + if (!passthrough && d.m_onlyPassthrough) + continue; + + if (d.m_deviceName == dev.name) + return d.ToDeviceString(sink.m_sinkName); + } + } + } + + // find same friendly name on other device in same driver + if (!dev.driver.empty() && !dev.friendlyName.empty()) + { + for (const auto& sink : m_sinkInfoList) + { + if (sink.m_sinkName != dev.driver) + continue; + + for (const auto& d : sink.m_deviceInfoList) + { + if (passthrough && (d.m_deviceType == AE_DEVTYPE_PCM || d.m_onlyPCM)) + continue; + + if (!passthrough && d.m_onlyPassthrough) + continue; + + if (d.GetFriendlyName() == dev.friendlyName) + return d.ToDeviceString(sink.m_sinkName); + } + } + } + + std::string firstDevice; + + // find default device of same driver or first device of same driver + if (!dev.driver.empty()) + { + for (const auto& sink : m_sinkInfoList) + { + if (sink.m_sinkName != dev.driver) + continue; + + for (const auto& d : sink.m_deviceInfoList) + { + if (passthrough && (d.m_deviceType == AE_DEVTYPE_PCM || d.m_onlyPCM)) + continue; + + if (!passthrough && d.m_onlyPassthrough) + continue; + + if (firstDevice.empty()) + firstDevice = d.ToDeviceString(sink.m_sinkName); + + if (d.m_deviceName.find("default") != std::string::npos) + return d.ToDeviceString(sink.m_sinkName); + } + + if (!firstDevice.empty()) + break; + } + } + + // return first device of same driver + if (!firstDevice.empty()) + return firstDevice; + + firstDevice.clear(); + + // find the default of any driver or first of any driver + for (const auto& sink : m_sinkInfoList) + { + for (const auto& d : sink.m_deviceInfoList) + { + if (passthrough && (d.m_deviceType == AE_DEVTYPE_PCM || d.m_onlyPCM)) + continue; + + if (!passthrough && d.m_onlyPassthrough) + continue; + + if (firstDevice.empty()) + firstDevice = d.ToDeviceString(sink.m_sinkName); + + if (d.m_deviceName.find("default") != std::string::npos) + return d.ToDeviceString(sink.m_sinkName); + } + } + + // return first device of any driver or empty + return firstDevice; +} + void CActiveAESink::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough) { EnumerateSinkList(false, ""); @@ -750,17 +860,7 @@ void CActiveAESink::EnumerateOutputDevices(AEDeviceList &devices, bool passthrou if (devInfo.m_onlyPCM && passthrough) continue; - std::string device = sinkInfo.m_sinkName + ":" + devInfo.m_deviceName; - - const std::string friendlyName = (devInfo.m_deviceName != devInfo.m_displayName) - ? devInfo.m_displayName - : devInfo.m_displayNameExtra; - - if (!friendlyName.empty()) - { - device.append(":"); - device.append(friendlyName); - } + const std::string device = devInfo.ToDeviceString(sinkInfo.m_sinkName); std::stringstream ss; diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h index aab1c315dd01e..44f118eb1472a 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h @@ -97,6 +97,7 @@ class CActiveAESink : private CThread void EnumerateSinkList(bool force, std::string driver); void EnumerateOutputDevices(AEDeviceList &devices, bool passthrough); + std::string ValidateOuputDevice(const std::string& device, bool passthrough) const; void Start(); void Dispose(); AEDeviceType GetDeviceType(const std::string &device); diff --git a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp index 473a8490464ce..8f8f21d97fa2f 100644 --- a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp +++ b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp @@ -64,3 +64,19 @@ std::string CAEDeviceInfo::DeviceTypeToString(enum AEDeviceType deviceType) } return "INVALID"; } + +std::string CAEDeviceInfo::GetFriendlyName() const +{ + return (m_deviceName != m_displayName) ? m_displayName : m_displayNameExtra; +} + +std::string CAEDeviceInfo::ToDeviceString(const std::string& driver) const +{ + std::string device = driver.empty() ? m_deviceName : driver + ":" + m_deviceName; + + const std::string fn = GetFriendlyName(); + if (!fn.empty()) + device += ":" + fn; + + return device; +} diff --git a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h index 387d61700f4b0..c28c29c9f68b4 100644 --- a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h +++ b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h @@ -47,6 +47,8 @@ class CAEDeviceInfo operator std::string(); static std::string DeviceTypeToString(enum AEDeviceType deviceType); + std::string GetFriendlyName() const; + std::string ToDeviceString(const std::string& driver) const; }; typedef std::vector AEDeviceInfoList;