From 0868d898f6331d14ab8baa53a99f3c7816bbcfc9 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 19 May 2021 18:11:06 +0200 Subject: [PATCH] Create virtual audio sinks on Linux --- assets/sunshine.conf | 4 +- sunshine/audio.cpp | 204 ++++++++++-------- sunshine/audio.h | 21 ++ sunshine/nvhttp.cpp | 4 +- sunshine/platform/common.h | 61 ++++-- sunshine/platform/linux/audio.cpp | 332 ++++++++++++++++++------------ sunshine/rtsp.cpp | 22 +- 7 files changed, 403 insertions(+), 245 deletions(-) diff --git a/assets/sunshine.conf b/assets/sunshine.conf index c4ea57a4..6dd5cecf 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -79,8 +79,8 @@ # # You can find the name of the audio sink using the following command: # !! Linux only !! -# pacmd list-sources | grep "name:" -# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo.monitor +# pacmd list-sinks | grep "name:" +# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo # # !! Windows only !! # tools\audio-info.exe diff --git a/sunshine/audio.cpp b/sunshine/audio.cpp index 5960562d..39158f7b 100644 --- a/sunshine/audio.cpp +++ b/sunshine/audio.cpp @@ -5,6 +5,7 @@ #include "platform/common.h" #include "audio.h" +#include "config.h" #include "main.h" #include "thread_safe.h" #include "utility.h" @@ -14,47 +15,65 @@ using namespace std::literals; using opus_t = util::safe_ptr; using sample_queue_t = std::shared_ptr>>; -struct opus_stream_config_t { - std::int32_t sampleRate; - int channelCount; - int streams; - int coupledStreams; - const std::uint8_t *mapping; +struct audio_ctx_t { + // We want to change the sink for the first stream only + std::unique_ptr sink_flag; + std::unique_ptr control; + + platf::sink_t sink; }; -constexpr std::uint8_t map_stereo[] { 0, 1 }; -constexpr std::uint8_t map_surround51[] { 0, 4, 1, 5, 2, 3 }; -constexpr std::uint8_t map_high_surround51[] { 0, 1, 2, 3, 4, 5 }; +static int start_audio_control(audio_ctx_t &ctx); +static void stop_audio_control(audio_ctx_t &); + +int map_stream(int channels, bool quality); constexpr auto SAMPLE_RATE = 48000; -static opus_stream_config_t stereo = { - SAMPLE_RATE, - 2, - 1, - 1, - map_stereo +opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] { + { + SAMPLE_RATE, + 2, + 1, + 1, + platf::speaker::map_stereo, + }, + { + SAMPLE_RATE, + 6, + 4, + 2, + platf::speaker::map_surround51, + }, + { + SAMPLE_RATE, + 6, + 6, + 0, + platf::speaker::map_surround51, + }, + { + SAMPLE_RATE, + 8, + 5, + 3, + platf::speaker::map_surround71, + }, + { + SAMPLE_RATE, + 8, + 8, + 0, + platf::speaker::map_surround71, + }, }; -static opus_stream_config_t Surround51 = { - SAMPLE_RATE, - 6, - 4, - 2, - map_surround51 -}; - -static opus_stream_config_t HighSurround51 = { - SAMPLE_RATE, - 6, - 6, - 0, - map_high_surround51 -}; +auto control_shared = safe::make_shared(start_audio_control, stop_audio_control); void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t config, void *channel_data) { //FIXME: Pick correct opus_stream_config_t based on config.channels - auto stream = &stereo; + auto stream = &stream_configs[map_stream(config.channels, config.high_quality)]; + opus_t opus { opus_multistream_encoder_create( stream->sampleRate, stream->channelCount, @@ -81,75 +100,43 @@ void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t confi } } -const platf::card_t *active_card(const std::vector &cards) { - for(auto &card : cards) { - if(card.active_profile) { - return &card; - } - } - - return nullptr; -} - void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data) { //FIXME: Pick correct opus_stream_config_t based on config.channels - auto stream = &stereo; + auto stream = &stream_configs[map_stream(config.channels, config.high_quality)]; - auto control = platf::audio_control(); + auto ref = control_shared.ref(); + if(!ref) { + return; + } + + auto &control = ref->control; if(!control) { BOOST_LOG(error) << "Couldn't create audio control"sv; return; } - auto cards = control->card_info(); - if(cards.empty()) { - return; + std::string *sink = &ref->sink.host; + if(ref->sink.null) { + auto &null = *ref->sink.null; + switch(stream->channelCount) { + case 2: + sink = &null.stereo; + break; + case 6: + sink = &null.surround51; + break; + case 8: + sink = &null.surround71; + break; + } } - auto card = active_card(cards); - if(!card || (card->stereo.empty() && card->surround51.empty() && card->surround71.empty())) { - return; - } + // Only the first may change the default sink + if( + !ref->sink_flag->exchange(true, std::memory_order_acquire) && + control->set_sink(*sink)) { - const platf::profile_t *profile; - switch(config.channels) { - case 2: - if(!card->stereo.empty()) { - profile = &card->stereo[0]; - } - else if(!card->surround51.empty()) { - profile = &card->surround51[0]; - } - else if(!card->surround71.empty()) { - profile = &card->surround71[0]; - } - break; - case 6: - if(!card->surround51.empty()) { - profile = &card->surround51[0]; - } - else if(!card->surround71.empty()) { - profile = &card->surround71[0]; - } - else { - profile = &card->stereo[0]; - } - break; - case 8: - if(!card->surround71.empty()) { - profile = &card->surround71[0]; - } - else if(!card->surround51.empty()) { - profile = &card->surround51[0]; - } - else { - profile = &card->stereo[0]; - } - break; - } - - if(control->set_output(*card, *profile)) { return; } @@ -166,7 +153,7 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co auto frame_size = config.packetDuration * stream->sampleRate / 1000; int samples_per_frame = frame_size * stream->channelCount; - auto mic = control->create_mic(stream->sampleRate, frame_size); + auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size); if(!mic) { BOOST_LOG(error) << "Couldn't create audio input"sv; @@ -185,7 +172,7 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co continue; case platf::capture_e::reinit: mic.reset(); - mic = control->create_mic(stream->sampleRate, frame_size); + mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size); if(!mic) { BOOST_LOG(error) << "Couldn't re-initialize audio input"sv; @@ -199,4 +186,41 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co samples->raise(std::move(sample_buffer)); } } + +int map_stream(int channels, bool quality) { + int shift = quality ? 1 : 0; + switch(channels) { + case 2: + return STEREO; + case 6: + return SURROUND51 + shift; + case 8: + return SURROUND71 + shift; + } + return STEREO; +} + +int start_audio_control(audio_ctx_t &ctx) { + ctx.sink_flag = std::make_unique(false); + + if(!(ctx.control = platf::audio_control())) { + return -1; + } + + auto sink = ctx.control->sink_info(); + if(!sink) { + return -1; + } + + ctx.sink = std::move(*sink); + return 0; +} +void stop_audio_control(audio_ctx_t &ctx) { + // restore audio-sink if applicable + const std::string &sink = config::audio.sink.empty() ? ctx.sink.host : config::audio.sink; + if(!sink.empty()) { + // Best effort, it's allowed to fail + ctx.control->set_sink(sink); + } +} } // namespace audio diff --git a/sunshine/audio.h b/sunshine/audio.h index f170fc36..01f145af 100644 --- a/sunshine/audio.h +++ b/sunshine/audio.h @@ -4,10 +4,31 @@ #include "thread_safe.h" #include "utility.h" namespace audio { +enum stream_config_e : int { + STEREO, + SURROUND51, + HIGH_SURROUND51, + SURROUND71, + HIGH_SURROUND71, + MAX_STREAM_CONFIG +}; + +struct opus_stream_config_t { + std::int32_t sampleRate; + int channelCount; + int streams; + int coupledStreams; + const std::uint8_t *mapping; +}; + +extern opus_stream_config_t stream_configs[MAX_STREAM_CONFIG]; + struct config_t { int packetDuration; int channels; int mask; + + bool high_quality; }; using packet_t = util::buffer_t; diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index ae413c39..6ac68d6e 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -117,9 +117,7 @@ void save_state() { } void load_state() { - auto file_state = fs::current_path() / config::nvhttp.file_state; - - if(!fs::exists(file_state)) { + if(!fs::exists(config::nvhttp.file_state)) { unique_id = util::uuid_t::generate().string(); return; } diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index c044aa53..978182f9 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -30,6 +30,36 @@ constexpr std::uint16_t B = 0x2000; constexpr std::uint16_t X = 0x4000; constexpr std::uint16_t Y = 0x8000; +namespace speaker { +enum speaker_e { + FRONT_LEFT, + FRONT_RIGHT, + FRONT_CENTER, + LOW_FREQUENCY, + BACK_LEFT, + BACK_RIGHT, + SIDE_LEFT, + SIDE_RIGHT, +}; + +constexpr std::uint8_t map_stereo[] { + FRONT_LEFT, FRONT_RIGHT +}; +constexpr std::uint8_t map_surround51[] { + FRONT_LEFT, BACK_LEFT, FRONT_RIGHT, BACK_RIGHT, FRONT_CENTER, LOW_FREQUENCY +}; +constexpr std::uint8_t map_surround71[] { + FRONT_LEFT, + BACK_LEFT, + FRONT_RIGHT, + BACK_RIGHT, + SIDE_LEFT, + SIDE_RIGHT, + FRONT_CENTER, + LOW_FREQUENCY, +}; +} // namespace speaker + enum class dev_type_e { none, dxgi, @@ -102,21 +132,18 @@ public: virtual ~img_t() = default; }; -struct profile_t { - std::string name; - std::string description; +struct sink_t { + // Play on host PC + std::string host; - bool available; -}; - -struct card_t { - std::string name; - std::string description; - - std::optional active_profile; - std::vector stereo; - std::vector surround51; - std::vector surround71; + // On Windows, it is not possible to create a virtual sink + // Therefore, it is optional + struct null_t { + std::string stereo; + std::string surround51; + std::string surround71; + }; + std::optional null; }; struct hwdevice_t { @@ -168,11 +195,11 @@ public: class audio_control_t { public: - virtual int set_output(const card_t &card, const profile_t &) = 0; + virtual int set_sink(const std::string &sink) = 0; - virtual std::unique_ptr create_mic(std::uint32_t sample_rate, std::uint32_t frame_size) = 0; + virtual std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0; - virtual std::vector card_info() = 0; + virtual std::optional sink_info() = 0; virtual ~audio_control_t() = default; }; diff --git a/sunshine/platform/linux/audio.cpp b/sunshine/platform/linux/audio.cpp index 86cac399..f0044ee8 100644 --- a/sunshine/platform/linux/audio.cpp +++ b/sunshine/platform/linux/audio.cpp @@ -17,12 +17,36 @@ namespace platf { using namespace std::literals; -struct mic_attr_t : public mic_t { - pa_sample_spec ss; - util::safe_ptr mic; +constexpr pa_channel_position_t position_mapping[] { + PA_CHANNEL_POSITION_FRONT_LEFT, + PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, + PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, + PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_SIDE_LEFT, + PA_CHANNEL_POSITION_SIDE_RIGHT, +}; - explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate, - std::uint8_t channels) : ss { format, sample_rate, channels }, mic {} {} +std::string to_string(const char *name, const std::uint8_t *mapping, int channels) { + std::stringstream ss; + + ss << "rate=48000 sink_name="sv << name << " format=s16le channels="sv << channels << " channel_map="sv; + std::for_each_n(mapping, channels - 1, [&ss](std::uint8_t pos) { + ss << pa_channel_position_to_string(position_mapping[pos]) << ','; + }); + + ss << pa_channel_position_to_string(position_mapping[mapping[channels - 1]]); + + ss << " sink_properties=device.description="sv << name; + auto result = ss.str(); + + BOOST_LOG(debug) << "null-sink args: "sv << result; + return result; +} + +struct mic_attr_t : public mic_t { + util::safe_ptr mic; capture_e sample(std::vector &sample_buf) override { auto sample_size = sample_buf.size(); @@ -39,8 +63,16 @@ struct mic_attr_t : public mic_t { } }; -std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t) { - auto mic = std::make_unique(PA_SAMPLE_S16LE, sample_rate, 2); +std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate) { + auto mic = std::make_unique(); + + pa_sample_spec ss { PA_SAMPLE_S16LE, sample_rate, (std::uint8_t)channels }; + pa_channel_map pa_map; + + pa_map.channels = channels; + std::for_each_n(pa_map.map, pa_map.channels, [mapping](auto &channel) mutable { + channel = position_mapping[*mapping++]; + }); int status; @@ -52,7 +84,7 @@ std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t) { mic->mic.reset( pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, audio_sink, - "sunshine-record", &mic->ss, nullptr, nullptr, &status)); + "sunshine-record", &ss, &pa_map, nullptr, &status)); if(!mic->mic) { auto err_str = pa_strerror(status); @@ -66,6 +98,22 @@ std::unique_ptr microphone(std::uint32_t sample_rate, std::uint32_t) { } namespace pa { +template +struct add_const_helper; + +template +struct add_const_helper { + using type = const std::remove_pointer_t *; +}; + +template +struct add_const_helper { + using type = const T *; +}; + +template +using add_const_t = typename add_const_helper, T>::type; + template void pa_free(T *p) { pa_xfree(p); @@ -76,10 +124,10 @@ using op_t = util::safe_ptr; using string_t = util::safe_ptr>; template -using cb_t = std::function; +using cb_t = std::function i, int eol)>; template -void cb(ctx_t::pointer ctx, const T *i, int eol, void *userdata) { +void cb(ctx_t::pointer ctx, add_const_t i, int eol, void *userdata) { auto &f = *(cb_t *)userdata; // For some reason, pulseaudio calls this callback after disconnecting @@ -90,6 +138,12 @@ void cb(ctx_t::pointer ctx, const T *i, int eol, void *userdata) { f(ctx, i, eol); } +void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) { + auto alarm = (safe::alarm_raw_t *)userdata; + + alarm->ring(i); +} + void ctx_state_cb(ctx_t::pointer ctx, void *userdata) { auto &f = *(std::function *)userdata; @@ -103,67 +157,6 @@ void success_cb(ctx_t::pointer ctx, int status, void *userdata) { alarm->ring(status ? 0 : 1); } -profile_t make(pa_card_profile_info2 *profile) { - return profile_t { - profile->name, - profile->description, - profile->available == 1 - }; -} - -card_t make(const pa_card_info *card) { - boost::regex stereo_expr { - ".*output(?!.*surround).*stereo.*" - }; - - boost::regex surround51_expr { - ".*output.*surround(.*(^\\d|51).*|$)" - }; - - boost::regex surround71_expr { - ".*output.*surround.*7.?1.*" - }; - - std::vector stereo; - std::vector surround51; - std::vector surround71; - - std::for_each_n(card->profiles2, card->n_profiles, [&](pa_card_profile_info2 *profile) { - if(boost::regex_match(profile->name, stereo_expr)) { - stereo.emplace_back(make(profile)); - } - if(boost::regex_match(profile->name, surround51_expr)) { - surround51.emplace_back(make(profile)); - } - if(boost::regex_match(profile->name, surround71_expr)) { - surround71.emplace_back(make(profile)); - } - }); - - std::optional active_profile; - if(card->active_profile2->name != "off"sv) { - active_profile = make(card->active_profile2); - } - - return card_t { - card->name, - card->driver, - std::move(active_profile), - std::move(stereo), - std::move(surround51), - std::move(surround71), - }; -} - -const card_t *active_card(const std::vector &cards) { - for(auto &card : cards) { - if(card.active_profile) { - return &card; - } - } - - return nullptr; -} class server_t : public audio_control_t { enum ctx_event_e : int { ready, @@ -175,6 +168,12 @@ public: loop_t loop; ctx_t ctx; + struct { + std::uint32_t stereo = PA_INVALID_INDEX; + std::uint32_t surround51 = PA_INVALID_INDEX; + std::uint32_t surround71 = PA_INVALID_INDEX; + } index; + std::unique_ptr> events; std::unique_ptr> events_cb; @@ -237,55 +236,169 @@ public: return 0; } - std::vector card_info() override { - auto alarm = safe::make_alarm(); + int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) { + auto alarm = safe::make_alarm(); - std::vector cards; - cb_t f = [&cards, alarm](ctx_t::pointer ctx, const pa_card_info *card_info, int eol) { - if(!card_info) { + op_t op { + pa_context_load_module( + ctx.get(), + "module-null-sink", + to_string(name, channel_mapping, channels).c_str(), + cb_i, + alarm.get()), + }; + + alarm->wait(); + return *alarm->status(); + } + + int unload_null(std::uint32_t i) { + if(i == PA_INVALID_INDEX) { + return 0; + } + + auto alarm = safe::make_alarm(); + + op_t op { + pa_context_unload_module(ctx.get(), i, success_cb, alarm.get()) + }; + + alarm->wait(); + + if(*alarm->status()) { + BOOST_LOG(error) << "Couldn't unload null-sink with index ["sv << i << "]: "sv << pa_strerror(pa_context_errno(ctx.get())); + return -1; + } + + return 0; + } + + std::optional sink_info() override { + constexpr auto stereo = "sink-sunshine-stereo"; + constexpr auto surround51 = "sink-sunshine-surround51"; + constexpr auto surround71 = "sink-sunshine-surround71"; + + auto alarm = safe::make_alarm(); + + 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; + + cb_t 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 card info: "sv << pa_strerror(pa_context_errno(ctx)); + BOOST_LOG(error) << "Couldn't get pulseaudio sink info: "sv << pa_strerror(pa_context_errno(ctx)); + + alarm->ring(-1); } - alarm->ring(true); + alarm->ring(0); return; } - cards.emplace_back(make(card_info)); + if(sink_info->flags & PA_SINK_HARDWARE && + sink_info->channel_map.channels > channels) { + + 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; + + ++nullcount; + } + else if(!std::strcmp(sink_info->name, surround51)) { + index.surround51 = sink_info->owner_module; + + ++nullcount; + } + else if(!std::strcmp(sink_info->name, surround71)) { + index.surround71 = sink_info->owner_module; + + ++nullcount; + } }; - op_t op { pa_context_get_card_info_list(ctx.get(), cb, &f) }; + op_t op { pa_context_get_sink_info_list(ctx.get(), cb, &f) }; if(!op) { BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get())); - return {}; + return std::nullopt; } alarm->wait(); - return cards; + if(*alarm->status()) { + return std::nullopt; + } + + if(!channels) { + BOOST_LOG(warning) << "Couldn't find hardware sink"sv; + } + + if(index.stereo == PA_INVALID_INDEX) { + index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo)); + if(index.stereo == PA_INVALID_INDEX) { + BOOST_LOG(warning) << "Couldn't create virtual sink for stereo: "sv << pa_strerror(pa_context_errno(ctx.get())); + } + else { + ++nullcount; + } + } + + if(index.surround51 == PA_INVALID_INDEX) { + index.surround51 = load_null(surround51, speaker::map_surround51, sizeof(speaker::map_surround51)); + if(index.surround51 == PA_INVALID_INDEX) { + BOOST_LOG(warning) << "Couldn't create virtual sink for surround-51: "sv << pa_strerror(pa_context_errno(ctx.get())); + } + else { + ++nullcount; + } + } + + if(index.surround71 == PA_INVALID_INDEX) { + index.surround71 = load_null(surround71, speaker::map_surround71, sizeof(speaker::map_surround71)); + if(index.surround71 == PA_INVALID_INDEX) { + BOOST_LOG(warning) << "Couldn't create virtual sink for surround-71: "sv << pa_strerror(pa_context_errno(ctx.get())); + } + else { + ++nullcount; + } + } + + if(nullcount == 3) { + sink.null = std::make_optional(sink_t::null_t { stereo, surround51, surround71 }); + } + + return std::make_optional(std::move(sink)); } - std::unique_ptr create_mic(std::uint32_t sample_rate, std::uint32_t frame_size) override { - return microphone(sample_rate, frame_size); + 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); } - int set_output(const card_t &card, const profile_t &profile) override { + int set_sink(const std::string &sink) override { auto alarm = safe::make_alarm(); - op_t op { pa_context_set_card_profile_by_name( - ctx.get(), card.name.c_str(), profile.name.c_str(), success_cb, alarm.get()) + op_t op { + pa_context_set_default_sink( + ctx.get(), sink.c_str(), success_cb, alarm.get()), }; if(!op) { - BOOST_LOG(error) << "Couldn't create set profile operation: "sv << pa_strerror(pa_context_errno(ctx.get())); + BOOST_LOG(error) << "Couldn't create set default-sink operation: "sv << pa_strerror(pa_context_errno(ctx.get())); return -1; } alarm->wait(); if(*alarm->status()) { - BOOST_LOG(error) << "Couldn't set profile ["sv << profile.name << "]: "sv << pa_strerror(pa_context_errno(ctx.get())); + BOOST_LOG(error) << "Couldn't set default-sink ["sv << sink << "]: "sv << pa_strerror(pa_context_errno(ctx.get())); return -1; } @@ -294,6 +407,10 @@ public: } ~server_t() override { + unload_null(index.stereo); + unload_null(index.surround51); + unload_null(index.surround71); + if(worker.joinable()) { pa_context_disconnect(ctx.get()); @@ -319,43 +436,6 @@ std::unique_ptr audio_control() { } std::unique_ptr init() { - pa::server_t server; - if(server.init()) { - return std::make_unique(); - } - - auto cards = server.card_info(); - - for(auto &card : cards) { - BOOST_LOG(info) << "---- CARD ----"sv; - BOOST_LOG(info) << "Name: ["sv << card.name << ']'; - BOOST_LOG(info) << "Description: ["sv << card.description << ']'; - - if(card.active_profile) { - BOOST_LOG(info) << "Active profile:"sv; - BOOST_LOG(info) << " Name: [" << card.active_profile->name << ']'; - BOOST_LOG(info) << " Description: ["sv << card.active_profile->description << ']'; - BOOST_LOG(info) << " Available: ["sv << card.active_profile->available << ']'; - BOOST_LOG(info); - } - - - BOOST_LOG(info) << " -- stereo --"sv; - for(auto &profile : card.stereo) { - BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')'; - } - - BOOST_LOG(info) << " -- surround 5.1 --"sv; - for(auto &profile : card.surround51) { - BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')'; - } - - BOOST_LOG(info) << " -- surround 7.1 --"sv; - for(auto &profile : card.surround71) { - BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')'; - } - } - return std::make_unique(); } } // namespace platf \ No newline at end of file diff --git a/sunshine/rtsp.cpp b/sunshine/rtsp.cpp index fc6b77ef..1dae7c94 100644 --- a/sunshine/rtsp.cpp +++ b/sunshine/rtsp.cpp @@ -293,15 +293,22 @@ void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { auto seqn_str = to_string(req->sequenceNumber); option.content = const_cast(seqn_str.c_str()); - std::string_view payload; - if(config::video.hevc_mode == 1) { - payload = "surround-params=NONE"sv; - } - else { - payload = "sprop-parameter-sets=AAAAAU;surround-params=NONE"sv; + std::stringstream ss; + if(config::video.hevc_mode != 1) { + ss << "sprop-parameter-sets=AAAAAU"sv << std::endl; } - respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, payload); + for(auto &stream_config : audio::stream_configs) { + ss << "a=fmtp:97 surround-params="sv << stream_config.channelCount << stream_config.streams << stream_config.coupledStreams; + + std::for_each_n(stream_config.mapping, stream_config.channelCount, [&ss](std::uint8_t digit) { + ss << (char)(digit + '0'); + }); + + ss << std::endl; + } + + respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, ss.str()); } void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { @@ -404,6 +411,7 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { try { config.audio.channels = util::from_view(args.at("x-nv-audio.surround.numChannels"sv)); config.audio.mask = util::from_view(args.at("x-nv-audio.surround.channelMask"sv)); + config.audio.high_quality = util::from_view(args.at("x-nv-audio.surround.AudioQuality"sv)); config.audio.packetDuration = util::from_view(args.at("x-nv-aqos.packetDuration"sv)); config.packetsize = util::from_view(args.at("x-nv-video[0].packetSize"sv));