Allow audio sinks to match on device names

Names are more stable than IDs on Windows
This commit is contained in:
Cameron Gutman 2023-05-02 22:16:16 -05:00
parent 3fa5f74635
commit 1d6ea8c759
3 changed files with 109 additions and 14 deletions

View File

@ -447,6 +447,8 @@ audio_sink
tools\audio-info.exe
.. Tip:: If you have multiple audio devices with identical names, use the Device ID instead.
.. Tip:: If you want to mute the host speakers, use `virtual_sink`_ instead.
**Default**
@ -466,7 +468,7 @@ audio_sink
**Windows**
.. code-block:: text
audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}
audio_sink = Speakers (High Definition Audio Device)
virtual_sink
^^^^^^^^^^^^
@ -488,7 +490,7 @@ virtual_sink
**Example**
.. code-block:: text
virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}
virtual_sink = Steam Streaming Speakers
Network
-------

View File

@ -515,7 +515,10 @@ namespace platf::audio {
UINT count;
collection->GetCount(&count);
std::string virtual_device_id = config::audio.virtual_sink;
// If the sink isn't a device name, we'll assume it's a device ID
auto virtual_device_id = find_device_id_by_name(config::audio.virtual_sink).value_or(converter.from_bytes(config::audio.virtual_sink));
auto virtual_device_found = false;
for (auto x = 0; x < count; ++x) {
audio::device_t device;
collection->Item(x, &device);
@ -526,6 +529,7 @@ namespace platf::audio {
audio::wstring_t wstring;
device->GetId(&wstring);
std::wstring device_id { wstring.get() };
audio::prop_t prop;
device->OpenPropertyStore(STGM_READ, &prop);
@ -548,17 +552,27 @@ namespace platf::audio {
<< std::endl;
if (virtual_device_id.empty() && adapter_name == virtual_adapter_name) {
virtual_device_id = converter.to_bytes(wstring.get());
virtual_device_id = std::move(device_id);
virtual_device_found = true;
break;
}
else if (virtual_device_id == device_id) {
virtual_device_found = true;
break;
}
}
if (!virtual_device_id.empty()) {
if (virtual_device_found) {
auto name_suffix = converter.to_bytes(virtual_device_id);
sink.null = std::make_optional(sink_t::null_t {
"virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id,
"virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id,
"virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id,
"virtual-"s.append(formats[format_t::stereo - 1].name) + name_suffix,
"virtual-"s.append(formats[format_t::surr51 - 1].name) + name_suffix,
"virtual-"s.append(formats[format_t::surr71 - 1].name) + name_suffix,
});
}
else if (!virtual_device_id.empty()) {
BOOST_LOG(warning) << "Unable to find the specified virtual sink: "sv << virtual_device_id;
}
return sink;
}
@ -604,7 +618,8 @@ namespace platf::audio {
}
}
auto wstring_device_id = converter.from_bytes(sv.data());
// If the sink isn't a device name, we'll assume it's a device ID
auto wstring_device_id = find_device_id_by_name(sink).value_or(converter.from_bytes(sv.data()));
if (type == format_t::none) {
// wstring_device_id does not contain virtual-(format name)
@ -660,6 +675,83 @@ namespace platf::audio {
return failure;
}
/**
* @brief Find the audio device ID given a user-specified name
*
* @param name The name provided by the user
*
* @return The matching device ID, or nothing if not found
*/
std::optional<std::wstring>
find_device_id_by_name(const std::string &name) {
if (name.empty()) {
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);
if (FAILED(status)) {
BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']';
return std::nullopt;
}
UINT count;
collection->GetCount(&count);
auto wstring_name = converter.from_bytes(name.data());
for (auto x = 0; x < count; ++x) {
audio::device_t device;
collection->Item(x, &device);
if (!validate_device(device)) {
continue;
}
audio::wstring_t wstring_id;
device->GetId(&wstring_id);
audio::prop_t prop;
device->OpenPropertyStore(STGM_READ, &prop);
prop_var_t adapter_friendly_name;
prop_var_t device_friendly_name;
prop_var_t device_desc;
prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop);
prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop);
prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop);
auto adapter_name = no_null((LPWSTR) adapter_friendly_name.prop.pszVal);
auto device_name = no_null((LPWSTR) device_friendly_name.prop.pszVal);
auto device_description = no_null((LPWSTR) device_desc.prop.pszVal);
// Match the user-specified name against any of the user-visible strings
if (std::wcscmp(wstring_name.c_str(), adapter_name) == 0 ||
std::wcscmp(wstring_name.c_str(), device_name) == 0 ||
std::wcscmp(wstring_name.c_str(), device_description) == 0) {
return std::make_optional(std::wstring { wstring_id.get() });
}
}
return std::nullopt;
}
int
init() {
auto status = CoCreateInstance(

View File

@ -448,13 +448,14 @@
type="text"
class="form-control"
id="audio_sink"
placeholder="{0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}"
placeholder="Speakers (High Definition Audio Device)"
v-model="config.audio_sink"
/>
<div class="form-text">
The name of the audio sink used for Audio Loopback<br />
The name of the audio sink used for audio capture. If not set, the default audio device will be used.<br />
You can find the name of the audio sink using the following command:<br />
<pre>tools\audio-info.exe</pre>
If you have multiple audio devices with identical names, use the Device ID instead.
</div>
</div>
<div class="mb-3" v-if="platform === 'linux'">
@ -506,12 +507,12 @@
type="text"
class="form-control"
id="virtual_sink"
placeholder="{0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}"
placeholder="Steam Streaming Speakers"
v-model="config.virtual_sink"
/>
<div class="form-text">
The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows Sunshine to
stream audio, while muting the speakers.
The virtual sink is an audio device that's virtual (like Steam Streaming Speakers). It allows Sunshine to
stream audio, while muting the host PC speakers.
</div>
</div>
<!--Adapter Name -->