From 53125ffeca3414af23cf2a71e7dfbae070ad6010 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 9 May 2023 20:02:28 -0500 Subject: [PATCH] Add support for installing the Steam Streaming Speakers driver (#1262) Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- docs/source/about/advanced_usage.rst | 19 +++- src/config.cpp | 7 +- src/config.h | 1 + src/platform/windows/audio.cpp | 139 ++++++++++++++++++----- src_assets/common/assets/web/config.html | 13 +++ 5 files changed, 146 insertions(+), 33 deletions(-) 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. +
+
@@ -992,6 +1004,7 @@ "amd_vbaq": "enabled", "capture": "", "controller": "enabled", + "install_steam_audio_drivers": "enabled", "dwmflush": "enabled", "encoder": "", "fps": "[10,30,60,90,120]",