Skip to content

Commit

Permalink
[ActiveAE] Validates audio device names in settings (existence in sys…
Browse files Browse the repository at this point in the history
…tem)

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.
  • Loading branch information
thexai committed Sep 1, 2023
1 parent 82465be commit ba77cb9
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 11 deletions.
5 changes: 5 additions & 0 deletions xbmc/cores/AudioEngine/AESinkFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 56 additions & 0 deletions xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand All @@ -669,6 +671,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg)
{
UnconfigureSink();
LoadSettings();
ValidateOutputDevices(false);
m_extError = false;
Configure();
if (!m_extError)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
122 changes: 111 additions & 11 deletions xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, "");
Expand All @@ -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;

Expand Down
1 change: 1 addition & 0 deletions xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
16 changes: 16 additions & 0 deletions xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<CAEDeviceInfo> AEDeviceInfoList;

0 comments on commit ba77cb9

Please sign in to comment.