Allow audio sinks to match on device names
Names are more stable than IDs on Windows
This commit is contained in:
@@ -447,6 +447,8 @@ audio_sink
|
|||||||
|
|
||||||
tools\audio-info.exe
|
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.
|
.. Tip:: If you want to mute the host speakers, use `virtual_sink`_ instead.
|
||||||
|
|
||||||
**Default**
|
**Default**
|
||||||
@@ -466,7 +468,7 @@ audio_sink
|
|||||||
**Windows**
|
**Windows**
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}
|
audio_sink = Speakers (High Definition Audio Device)
|
||||||
|
|
||||||
virtual_sink
|
virtual_sink
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
@@ -488,7 +490,7 @@ virtual_sink
|
|||||||
**Example**
|
**Example**
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}
|
virtual_sink = Steam Streaming Speakers
|
||||||
|
|
||||||
Network
|
Network
|
||||||
-------
|
-------
|
||||||
|
|||||||
@@ -515,7 +515,10 @@ namespace platf::audio {
|
|||||||
UINT count;
|
UINT count;
|
||||||
collection->GetCount(&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) {
|
for (auto x = 0; x < count; ++x) {
|
||||||
audio::device_t device;
|
audio::device_t device;
|
||||||
collection->Item(x, &device);
|
collection->Item(x, &device);
|
||||||
@@ -526,6 +529,7 @@ namespace platf::audio {
|
|||||||
|
|
||||||
audio::wstring_t wstring;
|
audio::wstring_t wstring;
|
||||||
device->GetId(&wstring);
|
device->GetId(&wstring);
|
||||||
|
std::wstring device_id { wstring.get() };
|
||||||
|
|
||||||
audio::prop_t prop;
|
audio::prop_t prop;
|
||||||
device->OpenPropertyStore(STGM_READ, &prop);
|
device->OpenPropertyStore(STGM_READ, &prop);
|
||||||
@@ -548,17 +552,27 @@ namespace platf::audio {
|
|||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
|
||||||
if (virtual_device_id.empty() && adapter_name == virtual_adapter_name) {
|
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 {
|
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::stereo - 1].name) + name_suffix,
|
||||||
"virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id,
|
"virtual-"s.append(formats[format_t::surr51 - 1].name) + name_suffix,
|
||||||
"virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id,
|
"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;
|
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) {
|
if (type == format_t::none) {
|
||||||
// wstring_device_id does not contain virtual-(format name)
|
// wstring_device_id does not contain virtual-(format name)
|
||||||
@@ -660,6 +675,83 @@ namespace platf::audio {
|
|||||||
return failure;
|
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
|
int
|
||||||
init() {
|
init() {
|
||||||
auto status = CoCreateInstance(
|
auto status = CoCreateInstance(
|
||||||
|
|||||||
@@ -448,13 +448,14 @@
|
|||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
id="audio_sink"
|
id="audio_sink"
|
||||||
placeholder="{0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}"
|
placeholder="Speakers (High Definition Audio Device)"
|
||||||
v-model="config.audio_sink"
|
v-model="config.audio_sink"
|
||||||
/>
|
/>
|
||||||
<div class="form-text">
|
<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 />
|
You can find the name of the audio sink using the following command:<br />
|
||||||
<pre>tools\audio-info.exe</pre>
|
<pre>tools\audio-info.exe</pre>
|
||||||
|
If you have multiple audio devices with identical names, use the Device ID instead.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3" v-if="platform === 'linux'">
|
<div class="mb-3" v-if="platform === 'linux'">
|
||||||
@@ -506,12 +507,12 @@
|
|||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
id="virtual_sink"
|
id="virtual_sink"
|
||||||
placeholder="{0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}"
|
placeholder="Steam Streaming Speakers"
|
||||||
v-model="config.virtual_sink"
|
v-model="config.virtual_sink"
|
||||||
/>
|
/>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows Sunshine to
|
The virtual sink is an audio device that's virtual (like Steam Streaming Speakers). It allows Sunshine to
|
||||||
stream audio, while muting the speakers.
|
stream audio, while muting the host PC speakers.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--Adapter Name -->
|
<!--Adapter Name -->
|
||||||
|
|||||||
Reference in New Issue
Block a user