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..18b2eff09cf60 100644 --- a/xbmc/cores/AudioEngine/AESinkFactory.h +++ b/xbmc/cores/AudioEngine/AESinkFactory.h @@ -42,6 +42,18 @@ struct AESinkRegEntry Cleanup cleanupFunc; }; +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 { public: @@ -49,8 +61,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..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) @@ -1191,17 +1195,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 +1786,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() @@ -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 01a204c852039..54d46e3fd86c3 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; } @@ -733,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, ""); @@ -755,7 +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 device = devInfo.ToDeviceString(sinkInfo.m_sinkName); std::stringstream ss; @@ -793,15 +898,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 +927,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 +940,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); 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;