diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst
index c4f606cd..32c52ff4 100644
--- a/docs/source/about/advanced_usage.rst
+++ b/docs/source/about/advanced_usage.rst
@@ -483,7 +483,8 @@ virtual_sink
- Stream Streaming Speakers (Linux, macOS, Windows)
- - To use this option, you must have Steam installed and have used Stream remote play at least once.
+ - Steam must be installed.
+ - Enable `install_steam_audio_drivers`_ or use Steam Remote Play at least once to install the drivers.
- `Virtual Audio Cable `_ (macOS, Windows)
@@ -492,6 +493,22 @@ virtual_sink
virtual_sink = Steam Streaming Speakers
+install_steam_audio_drivers
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+**Description**
+ Installs the Steam Streaming Speakers driver (if Steam is installed) to support surround sound and muting host audio.
+
+ .. Tip:: This option is only supported on Windows.
+
+**Default**
+ ``enabled``
+
+**Example**
+ .. code-block:: text
+
+ install_steam_audio_drivers = enabled
+
Network
-------
diff --git a/src/config.cpp b/src/config.cpp
index 50aee718..f2028f47 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -382,7 +382,11 @@ namespace config {
true // dwmflush
};
- audio_t audio {};
+ audio_t audio {
+ {}, // audio_sink
+ {}, // virtual_sink
+ true, // install_steam_drivers
+ };
stream_t stream {
10s, // ping_timeout
@@ -985,6 +989,7 @@ namespace config {
string_f(vars, "audio_sink", audio.sink);
string_f(vars, "virtual_sink", audio.virtual_sink);
+ bool_f(vars, "install_steam_audio_drivers", audio.install_steam_drivers);
string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { "pc"sv, "lan"sv, "wan"sv });
string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, { "pc"sv, "lan"sv, "wan"sv });
diff --git a/src/config.h b/src/config.h
index 06b5991e..39cfcfac 100644
--- a/src/config.h
+++ b/src/config.h
@@ -65,6 +65,7 @@ namespace config {
struct audio_t {
std::string sink;
std::string virtual_sink;
+ bool install_steam_drivers;
};
struct stream_t {
diff --git a/src/platform/windows/audio.cpp b/src/platform/windows/audio.cpp
index aa27c246..ecd0be19 100644
--- a/src/platform/windows/audio.cpp
+++ b/src/platform/windows/audio.cpp
@@ -10,6 +10,8 @@
#include
+#include
+
#define INITGUID
#include
#undef INITGUID
@@ -32,10 +34,20 @@ const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
+#if defined(__x86_64) || defined(_M_AMD64)
+ #define STEAM_DRIVER_SUBDIR L"x64"
+#elif defined(__i386) || defined(_M_IX86)
+ #define STEAM_DRIVER_SUBDIR L"x86"
+#else
+ #warning No known Steam audio driver for this architecture
+#endif
+
using namespace std::literals;
namespace platf::audio {
constexpr auto SAMPLE_RATE = 48000;
+ constexpr auto STEAM_AUDIO_DRIVER_PATH = L"%CommonProgramFiles(x86)%\\Steam\\drivers\\Windows10\\" STEAM_DRIVER_SUBDIR L"\\SteamStreamingSpeakers.inf";
+
template
void
Release(T *p) {
@@ -254,7 +266,7 @@ namespace platf::audio {
&device);
if (FAILED(status)) {
- BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']';
+ BOOST_LOG(error) << "Couldn't get default audio endpoint [0x"sv << util::hex(status).to_string_view() << ']';
return nullptr;
}
@@ -577,20 +589,6 @@ namespace platf::audio {
sink_t sink;
- audio::device_enum_t device_enum;
- auto status = CoCreateInstance(
- CLSID_MMDeviceEnumerator,
- nullptr,
- CLSCTX_ALL,
- IID_IMMDeviceEnumerator,
- (void **) &device_enum);
-
- if (FAILED(status)) {
- BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']';
-
- return std::nullopt;
- }
-
auto device = default_device(device_enum);
if (!device) {
return std::nullopt;
@@ -602,7 +600,7 @@ namespace platf::audio {
sink.host = converter.to_bytes(wstring.get());
collection_t collection;
- status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection);
+ auto status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection);
if (FAILED(status)) {
BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']';
@@ -814,22 +812,8 @@ namespace platf::audio {
return std::nullopt;
}
- audio::device_enum_t device_enum;
- auto status = CoCreateInstance(
- CLSID_MMDeviceEnumerator,
- nullptr,
- CLSCTX_ALL,
- IID_IMMDeviceEnumerator,
- (void **) &device_enum);
-
- if (FAILED(status)) {
- BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']';
-
- return std::nullopt;
- }
-
collection_t collection;
- status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection);
+ auto status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection);
if (FAILED(status)) {
BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']';
@@ -878,6 +862,79 @@ namespace platf::audio {
return std::nullopt;
}
+ /**
+ * @brief Installs the Steam Streaming Speakers driver, if present.
+ * @return `true` if installation was successful.
+ */
+ bool
+ install_steam_audio_drivers() {
+#ifdef STEAM_DRIVER_SUBDIR
+ // MinGW's libnewdev.a is missing DiInstallDriverW() even though the headers have it,
+ // so we have to load it at runtime. It's Vista or later, so it will always be available.
+ auto newdev = LoadLibraryExW(L"newdev.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (!newdev) {
+ BOOST_LOG(error) << "newdev.dll failed to load"sv;
+ return false;
+ }
+ auto fg = util::fail_guard([newdev]() {
+ FreeLibrary(newdev);
+ });
+
+ auto fn_DiInstallDriverW = (decltype(DiInstallDriverW) *) GetProcAddress(newdev, "DiInstallDriverW");
+ if (!fn_DiInstallDriverW) {
+ BOOST_LOG(error) << "DiInstallDriverW() is missing"sv;
+ return false;
+ }
+
+ // Get the current default audio device (if present)
+ auto old_default_dev = default_device(device_enum);
+
+ // Install the Steam Streaming Speakers driver
+ WCHAR driver_path[MAX_PATH] = {};
+ ExpandEnvironmentStringsW(STEAM_AUDIO_DRIVER_PATH, driver_path, ARRAYSIZE(driver_path));
+ if (fn_DiInstallDriverW(nullptr, driver_path, 0, nullptr)) {
+ BOOST_LOG(info) << "Successfully installed Steam Streaming Speakers"sv;
+
+ // Wait for 5 seconds to allow the audio subsystem to reconfigure things before
+ // modifying the default audio device or enumerating devices again.
+ Sleep(5000);
+
+ // If there was a previous default device, restore that original device as the
+ // default output device just in case installing the new one changed it.
+ if (old_default_dev) {
+ audio::wstring_t old_default_id;
+ old_default_dev->GetId(&old_default_id);
+
+ for (int x = 0; x < (int) ERole_enum_count; ++x) {
+ policy->SetDefaultEndpoint(old_default_id.get(), (ERole) x);
+ }
+ }
+
+ return true;
+ }
+ else {
+ auto err = GetLastError();
+ switch (err) {
+ case ERROR_ACCESS_DENIED:
+ BOOST_LOG(warning) << "Administrator privileges are required to install Steam Streaming Speakers"sv;
+ break;
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ BOOST_LOG(info) << "Steam audio drivers not found. This is expected if you don't have Steam installed."sv;
+ break;
+ default:
+ BOOST_LOG(warning) << "Failed to install Steam audio drivers: "sv << err;
+ break;
+ }
+
+ return false;
+ }
+#else
+ BOOST_LOG(warning) << "Unable to install Steam Streaming Speakers on unknown architecture"sv;
+ return false;
+#endif
+ }
+
int
init() {
auto status = CoCreateInstance(
@@ -893,12 +950,32 @@ namespace platf::audio {
return -1;
}
+ status = CoCreateInstance(
+ CLSID_MMDeviceEnumerator,
+ nullptr,
+ CLSCTX_ALL,
+ IID_IMMDeviceEnumerator,
+ (void **) &device_enum);
+
+ if (FAILED(status)) {
+ BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']';
+ return -1;
+ }
+
+ // Install Steam Streaming Speakers if needed. We do this during init() to ensure
+ // the sink information returned includes the new Steam Streaming Speakers device.
+ if (config::audio.install_steam_drivers && !find_device_id_by_name("Steam Streaming Speakers"s)) {
+ // This is best effort. Don't fail if it doesn't work.
+ install_steam_audio_drivers();
+ }
+
return 0;
}
~audio_control_t() override {}
policy_t policy;
+ audio::device_enum_t device_enum;
std::string assigned_sink;
};
} // namespace platf::audio
diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html
index 84c90a53..5b7b6a90 100644
--- a/src_assets/common/assets/web/config.html
+++ b/src_assets/common/assets/web/config.html
@@ -515,6 +515,18 @@
stream audio, while muting the host PC speakers.
+
+
+
+
+
+ If Steam is installed, this will automatically install the Steam Streaming Speakers driver to support
+ 5.1/7.1 surround sound and muting host audio.
+