Create virtual audio sinks on Linux

This commit is contained in:
loki 2021-05-19 18:11:06 +02:00
parent 2b04e1428c
commit 0868d898f6
7 changed files with 403 additions and 245 deletions

View File

@ -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

View File

@ -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<OpusMSEncoder, opus_multistream_encoder_destroy>;
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<std::int16_t>>>;
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<std::atomic_bool> sink_flag;
std::unique_ptr<platf::audio_control_t> 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<audio_ctx_t>(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<platf::card_t> &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<std::atomic_bool>(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

View File

@ -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<std::uint8_t>;

View File

@ -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;
}

View File

@ -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<profile_t> active_profile;
std::vector<profile_t> stereo;
std::vector<profile_t> surround51;
std::vector<profile_t> 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_t> 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<mic_t> create_mic(std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
virtual std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
virtual std::vector<card_t> card_info() = 0;
virtual std::optional<sink_t> sink_info() = 0;
virtual ~audio_control_t() = default;
};

View File

@ -17,12 +17,36 @@
namespace platf {
using namespace std::literals;
struct mic_attr_t : public mic_t {
pa_sample_spec ss;
util::safe_ptr<pa_simple, pa_simple_free> 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<pa_simple, pa_simple_free> mic;
capture_e sample(std::vector<std::int16_t> &sample_buf) override {
auto sample_size = sample_buf.size();
@ -39,8 +63,16 @@ struct mic_attr_t : public mic_t {
}
};
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate, std::uint32_t) {
auto mic = std::make_unique<mic_attr_t>(PA_SAMPLE_S16LE, sample_rate, 2);
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate) {
auto mic = std::make_unique<mic_attr_t>();
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<mic_t> 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<mic_t> microphone(std::uint32_t sample_rate, std::uint32_t) {
}
namespace pa {
template<bool B, class T>
struct add_const_helper;
template<class T>
struct add_const_helper<true, T> {
using type = const std::remove_pointer_t<T> *;
};
template<class T>
struct add_const_helper<false, T> {
using type = const T *;
};
template<class T>
using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type;
template<class T>
void pa_free(T *p) {
pa_xfree(p);
@ -76,10 +124,10 @@ using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
using string_t = util::safe_ptr<char, pa_free<char>>;
template<class T>
using cb_t = std::function<void(ctx_t::pointer, const T *, int eol)>;
using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>;
template<class T>
void cb(ctx_t::pointer ctx, const T *i, int eol, void *userdata) {
void cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
auto &f = *(cb_t<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<int> *)userdata;
alarm->ring(i);
}
void ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
auto &f = *(std::function<void(ctx_t::pointer)> *)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<profile_t> stereo;
std::vector<profile_t> surround51;
std::vector<profile_t> 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<profile_t> 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<card_t> &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<safe::event_t<ctx_event_e>> events;
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
@ -237,55 +236,169 @@ public:
return 0;
}
std::vector<card_t> card_info() override {
auto alarm = safe::make_alarm<bool>();
int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
auto alarm = safe::make_alarm<int>();
std::vector<card_t> cards;
cb_t<pa_card_info> 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<int>();
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_t> 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<int>();
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<pa_sink_info *> 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<pa_card_info>, &f) };
op_t op { pa_context_get_sink_info_list(ctx.get(), cb<pa_sink_info *>, &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<mic_t> create_mic(std::uint32_t sample_rate, std::uint32_t frame_size) override {
return microphone(sample_rate, frame_size);
std::unique_ptr<mic_t> 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<int>();
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_t> audio_control() {
}
std::unique_ptr<deinit_t> init() {
pa::server_t server;
if(server.init()) {
return std::make_unique<deinit_t>();
}
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<deinit_t>();
}
} // namespace platf

View File

@ -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<char *>(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));