From e8ef7080347d452019e3b37d998e6cd3d0680771 Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Mon, 11 Apr 2022 18:09:51 -0500 Subject: [PATCH 1/4] Fix virtual sink overriding config sink. --- sunshine/audio.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sunshine/audio.cpp b/sunshine/audio.cpp index 4279eb4c..3cc0e5e0 100644 --- a/sunshine/audio.cpp +++ b/sunshine/audio.cpp @@ -135,9 +135,14 @@ void capture(safe::mail_t mail, config_t config, void *channel_data) { return; } - std::string *sink = - config::audio.sink.empty() ? &ref->sink.host : &config::audio.sink; - if(ref->sink.null) { + // Order of priorty: + // 1. Config + // 2. Virtual if available + // 3. Host + std::string *sink = &ref->sink.host; + if(!config::audio.sink.empty()) { + sink = &config::audio.sink; + } else if(ref->sink.null) { auto &null = *ref->sink.null; switch(stream->channelCount) { case 2: From 62ca9c31a07a7205c8b7d4af367a18cb2a80aff7 Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Mon, 11 Apr 2022 18:11:06 -0500 Subject: [PATCH 2/4] Updated the linux for better pulse behavior. --- sunshine/platform/linux/audio.cpp | 97 ++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/sunshine/platform/linux/audio.cpp b/sunshine/platform/linux/audio.cpp index c210da24..7b6f9c6c 100644 --- a/sunshine/platform/linux/audio.cpp +++ b/sunshine/platform/linux/audio.cpp @@ -65,7 +65,7 @@ struct mic_attr_t : public mic_t { } }; -std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) { +std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) { auto mic = std::make_unique(); pa_sample_spec ss { PA_SAMPLE_S16LE, sample_rate, (std::uint8_t)channels }; @@ -81,14 +81,9 @@ std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std int status; - const char *audio_sink = "@DEFAULT_MONITOR@"; - if(!config::audio.sink.empty()) { - audio_sink = config::audio.sink.c_str(); - } - mic->mic.reset( pa_simple_new(nullptr, "sunshine", - pa_stream_direction_t::PA_STREAM_RECORD, audio_sink, + pa_stream_direction_t::PA_STREAM_RECORD, source_name.c_str(), "sunshine-record", &ss, &pa_map, &pa_attr, &status)); if(!mic->mic) { @@ -128,6 +123,18 @@ using loop_t = util::safe_ptr; using op_t = util::safe_ptr; using string_t = util::safe_ptr>; +template +using cb_simple_t = std::function i)>; + +template +void cb(ctx_t::pointer ctx, add_const_t i, void *userdata) { + auto &f = *(cb_simple_t *)userdata; + + // Cannot similarly filter on eol here. Unless reported otherwise assume + // we have no need for special filtering like cb? + f(ctx, i); +} + template using cb_t = std::function i, int eol)>; @@ -172,6 +179,7 @@ class server_t : public audio_control_t { public: loop_t loop; ctx_t ctx; + std::string requested_sink; struct { std::uint32_t stereo = PA_INVALID_INDEX; @@ -287,8 +295,6 @@ public: sink_t sink; - // If hardware sink with more channels found, set that as host - int channels = 0; // Count of all virtual sinks that are created by us int nullcount = 0; @@ -304,11 +310,6 @@ public: return; } - if(sink_info->active_port != nullptr) { - sink.host = sink_info->name; - channels = sink_info->channel_map.channels; - } - // Ensure Sunshine won't create a sink that already exists. if(!std::strcmp(sink_info->name, stereo)) { index.stereo = sink_info->owner_module; @@ -341,8 +342,11 @@ public: return std::nullopt; } - if(!channels) { + auto sink_name = get_default_sink_name(); + if(sink_name.empty()) { BOOST_LOG(warning) << "Couldn't find an active sink"sv; + } else { + sink.host = sink_name; } if(index.stereo == PA_INVALID_INDEX) { @@ -382,13 +386,72 @@ public: return std::make_optional(std::move(sink)); } + std::string get_default_sink_name() { + std::string sink_name = "@DEFAULT_SINK@"s; + auto alarm = safe::make_alarm(); + + cb_simple_t server_f = [&](ctx_t::pointer ctx, const pa_server_info *server_info) { + if(!server_info) { + BOOST_LOG(error) << "Couldn't get pulseaudio server info: "sv << pa_strerror(pa_context_errno(ctx)); + alarm->ring(-1); + } + + sink_name = server_info->default_sink_name; + alarm->ring(0); + }; + + op_t server_op { pa_context_get_server_info(ctx.get(), cb, &server_f) }; + alarm->wait(); + // No need to check status. If it failed just return default name. + return sink_name; + } + + std::string get_monitor_name(const std::string &sink_name) { + std::string monitor_name = "@DEFAULT_MONITOR@"s; + auto alarm = safe::make_alarm(); + + cb_t sink_f = [&](ctx_t::pointer ctx, const pa_sink_info *sink_info, int eol) { + if(!sink_info) { + if(!eol) { + BOOST_LOG(error) << "Couldn't get pulseaudio sink info for ["sv << sink_name + << "]: "sv << pa_strerror(pa_context_errno(ctx)); + alarm->ring(-1); + } + + alarm->ring(0); + return; + } + + monitor_name = sink_info->monitor_source_name; + }; + + op_t sink_op { pa_context_get_sink_info_by_name(ctx.get(), sink_name.c_str(), cb, &sink_f) }; + + alarm->wait(); + // No need to check status. If it failed just return default name. + BOOST_LOG(info) << "Found default monitor by name: "sv << monitor_name; + return monitor_name; + } + std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override { - return ::platf::microphone(mapping, channels, sample_rate, frame_size); + // Sink choice priority: + // 1. Config sink + // 2. Last sink swapped to (Usually virtual in this case) + // 3. Default Sink + // An attempt was made to always use default to match the switching mechanic, + // but this happens right after the swap so the default returned by PA was not + // the new one just set! + auto sink_name = config::audio.sink; + if(sink_name.empty()) sink_name = requested_sink; + if(sink_name.empty()) sink_name = get_default_sink_name(); + + return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name)); } int set_sink(const std::string &sink) override { auto alarm = safe::make_alarm(); + BOOST_LOG(info) << "Setting default sink to: ["sv << sink << "]"sv; op_t op { pa_context_set_default_sink( ctx.get(), sink.c_str(), success_cb, alarm.get()), @@ -406,6 +469,8 @@ public: return -1; } + requested_sink = sink; + return 0; } From bd033f9e1515bda072d48c0006598f2b21521ab7 Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Mon, 11 Apr 2022 18:12:11 -0500 Subject: [PATCH 3/4] Fixed formatting. --- sunshine/platform/linux/audio.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sunshine/platform/linux/audio.cpp b/sunshine/platform/linux/audio.cpp index 7b6f9c6c..2770e515 100644 --- a/sunshine/platform/linux/audio.cpp +++ b/sunshine/platform/linux/audio.cpp @@ -345,7 +345,8 @@ public: auto sink_name = get_default_sink_name(); if(sink_name.empty()) { BOOST_LOG(warning) << "Couldn't find an active sink"sv; - } else { + } + else { sink.host = sink_name; } @@ -438,13 +439,13 @@ public: // 1. Config sink // 2. Last sink swapped to (Usually virtual in this case) // 3. Default Sink - // An attempt was made to always use default to match the switching mechanic, - // but this happens right after the swap so the default returned by PA was not + // An attempt was made to always use default to match the switching mechanic, + // but this happens right after the swap so the default returned by PA was not // the new one just set! auto sink_name = config::audio.sink; if(sink_name.empty()) sink_name = requested_sink; if(sink_name.empty()) sink_name = get_default_sink_name(); - + return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name)); } From 63a83cdf7a43a35c15c0556733ab43e4372e3a9e Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Mon, 11 Apr 2022 19:02:47 -0500 Subject: [PATCH 4/4] Fix formatting --- sunshine/audio.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sunshine/audio.cpp b/sunshine/audio.cpp index 3cc0e5e0..2894d128 100644 --- a/sunshine/audio.cpp +++ b/sunshine/audio.cpp @@ -135,14 +135,15 @@ void capture(safe::mail_t mail, config_t config, void *channel_data) { return; } - // Order of priorty: + // Order of priorty: // 1. Config // 2. Virtual if available // 3. Host std::string *sink = &ref->sink.host; if(!config::audio.sink.empty()) { sink = &config::audio.sink; - } else if(ref->sink.null) { + } + else if(ref->sink.null) { auto &null = *ref->sink.null; switch(stream->channelCount) { case 2: