66
77// standard includes
88#include < format>
9+ #include < functional>
10+ #include < type_traits>
911#include < utility>
1012
1113// platform includes
@@ -871,6 +873,15 @@ namespace platf::audio {
871873 std::optional<sink_t > sink_info () override {
872874 sink_t sink;
873875
876+ if (config::audio.external_audio ) {
877+ if (config::audio.sink .empty ()) {
878+ BOOST_LOG (error) << " external_audio requires an explicit audio_sink" ;
879+ return std::nullopt ;
880+ }
881+ sink.host = config::audio.sink ;
882+ return sink;
883+ }
884+
874885 // Fill host sink name with the device_id of the current default audio device.
875886 {
876887 auto device = default_device (device_enum);
@@ -987,9 +998,13 @@ namespace platf::audio {
987998 std::unique_ptr<mic_t > microphone (const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, [[maybe_unused]] bool host_audio_enabled) override {
988999 auto mic = std::make_unique<mic_wasapi_t >();
9891000
990- // Prefer the sink that was assigned to this capture session since it accounts
991- // for the priority between virtual and configured sinks.
992- const auto &requested_sink = assigned_sink.empty () ? config::audio.sink : assigned_sink;
1001+ // External routing pins capture to the configured sink; otherwise prefer the session's assigned sink.
1002+ const auto &requested_sink = config::audio.external_audio || assigned_sink.empty () ? config::audio.sink : assigned_sink;
1003+
1004+ if (config::audio.external_audio && requested_sink.empty ()) {
1005+ BOOST_LOG (error) << " external_audio requires an explicit audio_sink" ;
1006+ return nullptr ;
1007+ }
9931008
9941009 // Capture the requested sink directly instead of relying on it being the default
9951010 // render device, so that capture keeps working when the default device differs
@@ -1011,7 +1026,7 @@ namespace platf::audio {
10111026
10121027 // If this is a virtual sink, set a callback that will change the sink back if it's changed
10131028 auto virtual_sink_info = extract_virtual_sink_info (assigned_sink);
1014- if (virtual_sink_info) {
1029+ if (virtual_sink_info && !config::audio. external_audio ) {
10151030 mic->default_endpt_changed_cb = [this ] {
10161031 BOOST_LOG (info) << " Resetting sink to [" sv << assigned_sink << " ] after default changed" ;
10171032 set_sink (assigned_sink);
@@ -1098,6 +1113,10 @@ namespace platf::audio {
10981113 * @return Status from updating sink.
10991114 */
11001115 int set_sink (const std::string &sink) override {
1116+ if (config::audio.external_audio ) {
1117+ return 0 ;
1118+ }
1119+
11011120 auto device_id = set_format (sink);
11021121 if (!device_id) {
11031122 return -1 ;
@@ -1252,6 +1271,10 @@ namespace platf::audio {
12521271 * @brief Resets the default audio device from Steam Streaming Speakers.
12531272 */
12541273 void reset_default_device () {
1274+ if (config::audio.external_audio ) {
1275+ return ;
1276+ }
1277+
12551278 auto matched_steam = find_device_id (match_steam_speakers ());
12561279 if (!matched_steam) {
12571280 return ;
@@ -1380,26 +1403,29 @@ namespace platf::audio {
13801403 }
13811404
13821405 /* *
1383- * @brief Initialize Windows audio policy interfaces .
1406+ * @brief Initialize endpoint enumeration and policy control when routing is managed by Sunshine .
13841407 *
1408+ * @param create_instance COM factory used to initialize the required interfaces.
13851409 * @return 0 on success; nonzero or negative platform status on failure.
13861410 */
1387- int init () {
1388- auto status = CoCreateInstance (
1389- CLSID_CPolicyConfigClient,
1390- nullptr ,
1391- CLSCTX_ALL ,
1392- IID_IPolicyConfig,
1393- (void **) &policy
1394- );
1411+ int init (const std::function<std::remove_pointer_t <decltype (&CoCreateInstance)>> &create_instance = CoCreateInstance) {
1412+ if (!config::audio.external_audio ) {
1413+ auto status = create_instance (
1414+ CLSID_CPolicyConfigClient,
1415+ nullptr ,
1416+ CLSCTX_ALL ,
1417+ IID_IPolicyConfig,
1418+ (void **) &policy
1419+ );
13951420
1396- if (FAILED (status)) {
1397- BOOST_LOG (error) << " Couldn't create audio policy config: [0x" sv << util::hex (status).to_string_view () << ' ]' ;
1421+ if (FAILED (status)) {
1422+ BOOST_LOG (error) << " Couldn't create audio policy config: [0x" sv << util::hex (status).to_string_view () << ' ]' ;
13981423
1399- return -1 ;
1424+ return -1 ;
1425+ }
14001426 }
14011427
1402- status = CoCreateInstance (
1428+ auto status = create_instance (
14031429 CLSID_MMDeviceEnumerator,
14041430 nullptr ,
14051431 CLSCTX_ALL ,
@@ -1428,6 +1454,36 @@ namespace platf::audio {
14281454
14291455#ifdef SUNSHINE_TESTS
14301456 namespace tests {
1457+ /* *
1458+ * @brief Exercise controller initialization with a supplied COM factory.
1459+ * @param create_instance Factory providing or rejecting the requested interfaces.
1460+ * @return Status from controller initialization.
1461+ */
1462+ int initialize_audio_control (const std::function<std::remove_pointer_t <decltype (&CoCreateInstance)>> &create_instance) {
1463+ audio_control_t control;
1464+ return control.init (create_instance);
1465+ }
1466+
1467+ /* *
1468+ * @brief Exercise sink discovery without initializing Windows policy interfaces.
1469+ * @return Sinks reported by the production controller for the current configuration.
1470+ */
1471+ std::optional<sink_t > configured_sink_info () {
1472+ audio_control_t control;
1473+ return control.sink_info ();
1474+ }
1475+
1476+ /* *
1477+ * @brief Exercise the external-routing sink-change guard without policy interfaces.
1478+ * @param sink Sink a caller attempts to select.
1479+ * @return Status from the production sink setter.
1480+ */
1481+ int set_external_sink (const std::string &sink) {
1482+ audio_control_t control;
1483+ control.reset_default_device ();
1484+ return control.set_sink (sink);
1485+ }
1486+
14311487 /* *
14321488 * @brief Resolve a sink through the production Windows endpoint lookup.
14331489 *
@@ -1542,7 +1598,7 @@ namespace platf {
15421598
15431599 // Install Steam Streaming Speakers if needed. We do this during audio_control() to ensure
15441600 // the sink information returned includes the new Steam Streaming Speakers device.
1545- if (config::audio.install_steam_drivers && !control->find_device_id (control->match_steam_speakers ())) {
1601+ if (!config::audio. external_audio && config::audio.install_steam_drivers && !control->find_device_id (control->match_steam_speakers ())) {
15461602 // This is best effort. Don't fail if it doesn't work.
15471603 control->install_steam_audio_drivers ();
15481604 }
0 commit comments