Skip to content

Commit

Permalink
fix: surround - properly split channels, fixed opus params and Moonli…
Browse files Browse the repository at this point in the history
…ght settings
  • Loading branch information
ABeltramo committed Aug 17, 2024
1 parent 7a748a1 commit 6283677
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 61 deletions.
29 changes: 27 additions & 2 deletions src/core/src/core/audio.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,42 @@
#include <functional>
#include <memory>
#include <string>
#include <vector>

namespace wolf::core::audio {

typedef struct Server Server;

std::shared_ptr<Server> connect(std::string_view server = {});

constexpr auto SAMPLE_RATE = 48000;

struct AudioMode {

enum Speakers {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
SIDE_LEFT,
SIDE_RIGHT,
MAX_SPEAKERS,
};

int channels{};
int streams{};
int coupled_streams{};
std::vector<Speakers> speakers;
int bitrate{};
int sample_rate = SAMPLE_RATE;
};


struct AudioDevice {
std::string_view sink_name;
int n_channels;
int bitrate = 48000;
AudioMode mode;
};

struct VSink {
Expand Down
52 changes: 50 additions & 2 deletions src/core/src/platforms/linux/pulseaudio/pulse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,51 @@
#include <memory>
#include <pulse/pulseaudio.h>

namespace fmt {
template <> class formatter<wolf::core::audio::AudioMode::Speakers> {
public:
template <typename ParseContext> constexpr auto parse(ParseContext &ctx) {
return ctx.begin();
}

template <typename FormatContext>
auto format(const wolf::core::audio::AudioMode::Speakers &speaker, FormatContext &ctx) const {
std::string speaker_name;
// Mapping taken from
// https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/#module-null-sink
switch (speaker) {
case wolf::core::audio::AudioMode::FRONT_LEFT:
speaker_name = "front-left";
break;
case wolf::core::audio::AudioMode::FRONT_RIGHT:
speaker_name = "front-right";
break;
case wolf::core::audio::AudioMode::FRONT_CENTER:
speaker_name = "front-center";
break;
case wolf::core::audio::AudioMode::LOW_FREQUENCY:
speaker_name = "lfe";
break;
case wolf::core::audio::AudioMode::BACK_LEFT:
speaker_name = "rear-left";
break;
case wolf::core::audio::AudioMode::BACK_RIGHT:
speaker_name = "rear-right";
break;
case wolf::core::audio::AudioMode::SIDE_LEFT:
speaker_name = "side-left";
break;
case wolf::core::audio::AudioMode::SIDE_RIGHT:
speaker_name = "side-right";
break;
case wolf::core::audio::AudioMode::MAX_SPEAKERS:
break;
}
return fmt::format_to(ctx.out(), "{}", speaker_name);
}
};
} // namespace fmt

namespace wolf::core::audio {

struct Server {
Expand Down Expand Up @@ -88,8 +133,11 @@ std::shared_ptr<VSink> create_virtual_sink(const std::shared_ptr<Server> &server

queue_op(server, [server, vsink]() {
auto device = vsink->device;
auto channel_spec =
fmt::format("rate={} sink_name={} channels={}", device.bitrate, device.sink_name, device.n_channels);
auto channel_spec = fmt::format("rate={} sink_name={} channels={} channel_map={}",
device.mode.sample_rate,
device.sink_name,
device.mode.channels,
fmt::join(device.mode.speakers, ","));
auto operation = pa_context_load_module(
server->ctx,
"module-null-sink",
Expand Down
6 changes: 2 additions & 4 deletions src/moonlight-server/rest/endpoints.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,10 @@ create_run_session(const std::shared_ptr<typename SimpleWeb::Server<SimpleWeb::H
std::stoi(display_mode_str[2].data()),
state->config->support_hevc,
state->config->support_av1};
// forcing stereo, TODO: what should we select here?

auto surround_info = std::stoi(get_header(headers, "surroundAudioInfo").value_or("196610"));
int channelCount = surround_info & (65535);
state::AudioMode audio_mode;
audio::AudioMode audio_mode;
switch (channelCount) {
case 2: // stereo
audio_mode = state->host->audio_modes[0];
Expand All @@ -267,8 +267,6 @@ create_run_session(const std::shared_ptr<typename SimpleWeb::Server<SimpleWeb::H
break;
}

// auto joypad_map = get_header(headers, "remoteControllersBitmap").value(); // TODO: decipher this (might be empty)

std::string host_state_folder = utils::get_env("HOST_APPS_STATE_FOLDER", "/etc/wolf");
auto full_path = std::filesystem::path(host_state_folder) / current_client.app_state_folder / run_app.base.title;
logs::log(logs::debug, "Host app state folder: {}, creating paths", full_path.string());
Expand Down
31 changes: 21 additions & 10 deletions src/moonlight-server/rtsp/commands.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,26 @@ describe(const RTSP_PACKET &req, const state::StreamSession &session) {
payloads.push_back({"a", "a=rtpmap:98 AV1/90000"});
}

std::string audio_speakers = session.audio_mode.speakers //
/**
* GFE advertises incorrect mapping for normal quality configurations,
* as a result, Moonlight rotates all channels from index '3' to the right
* To work around this, rotate channels to the left from index '3'
*/
auto mapping_p = session.audio_mode.speakers;
if (session.audio_mode.channels > 2) { // 5.1 and 7.1
std::rotate(mapping_p.begin() + 3, mapping_p.begin() + 4, mapping_p.end());
}
std::string audio_speakers = mapping_p //
| views::transform([](auto speaker) { return (char)(speaker + '0'); }) //
| to<std::string>; //
| to<std::string>;
auto surround_params = fmt::format("fmtp:97 surround-params={}{}{}{}",
session.audio_mode.channels,
session.audio_mode.streams,
session.audio_mode.coupled_streams,
audio_speakers);

payloads.push_back({"a",
fmt::format("fmtp:97 surround-params={}{}{}{}",
session.audio_mode.channels,
session.audio_mode.streams,
session.audio_mode.coupled_streams,
audio_speakers)});
payloads.push_back({"a", surround_params});
logs::log(logs::debug, "[RTSP] Sending audio surround params: {}", surround_params);

payloads.push_back(
{"a", fmt::format("x-ss-general.featureFlags: {}", FS_PEN_TOUCH_EVENTS | FS_CONTROLLER_TOUCH_EVENTS)});
Expand Down Expand Up @@ -146,7 +156,7 @@ announce(const RTSP_PACKET &req,
gst_pipeline = session.app->h264_gst_pipeline;
}

auto audio_channels = args["x-nv-audio.surround.numChannels"].value_or(2);
auto audio_channels = args["x-nv-audio.surround.numChannels"].value_or(session.audio_mode.channels);
auto fec_percentage = 20; // TODO: setting?

long bitrate = args["x-nv-vqos[0].bw.maximumBitrateKbps"].value_or(15500);
Expand Down Expand Up @@ -209,7 +219,8 @@ announce(const RTSP_PACKET &req,
.client_ip = session.ip,

.packet_duration = args["x-nv-aqos.packetDuration"].value_or(5),
.channels = audio_channels};
.channels = audio_channels,
.bitrate = session.audio_mode.bitrate};
event_bus->fire_event(immer::box<state::AudioSession>(audio));

return ok_msg(req.seq_number);
Expand Down
24 changes: 2 additions & 22 deletions src/moonlight-server/state/data-structures.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,32 +135,12 @@ struct Config {
immer::vector<App> apps;
};

struct AudioMode {

enum Speakers {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
SIDE_LEFT,
SIDE_RIGHT,
MAX_SPEAKERS,
};

int channels{};
int streams{};
int coupled_streams{};
immer::array<Speakers> speakers;
};

/**
* Host information like network, certificates and displays
*/
struct Host {
immer::array<moonlight::DisplayMode> display_modes;
immer::array<AudioMode> audio_modes;
immer::array<audio::AudioMode> audio_modes;

const X509 *server_cert;
const EVP_PKEY *server_pkey;
Expand Down Expand Up @@ -196,7 +176,7 @@ using JoypadList = immer::map<int /* controller number */, std::shared_ptr<Joypa
*/
struct StreamSession {
moonlight::DisplayMode display_mode;
AudioMode audio_mode;
audio::AudioMode audio_mode;

std::shared_ptr<dp::event_bus> event_bus;
std::shared_ptr<App> app;
Expand Down
2 changes: 1 addition & 1 deletion src/moonlight-server/streaming/data-structures.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ struct AudioSession {

int packet_duration;
int channels;
int bitrate = 48000;
int bitrate;
};

/**
Expand Down
42 changes: 22 additions & 20 deletions src/moonlight-server/wolf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,34 +45,37 @@ immer::array<moonlight::DisplayMode> getDisplayModes() {
/**
* @brief Get the Audio Modes
*/
immer::array<state::AudioMode> getAudioModes() {
immer::array<audio::AudioMode> getAudioModes() {
return {// Stereo
{.channels = 2,
.streams = 1,
.coupled_streams = 1,
.speakers = {state::AudioMode::FRONT_LEFT, state::AudioMode::FRONT_RIGHT}},
.speakers = {audio::AudioMode::FRONT_LEFT, audio::AudioMode::FRONT_RIGHT},
.bitrate = 96000},
// 5.1
{.channels = 6,
.streams = 4,
.coupled_streams = 2,
.speakers = {state::AudioMode::FRONT_LEFT,
state::AudioMode::FRONT_RIGHT,
state::AudioMode::FRONT_CENTER,
state::AudioMode::LOW_FREQUENCY,
state::AudioMode::BACK_LEFT,
state::AudioMode::BACK_RIGHT}},
.speakers = {audio::AudioMode::FRONT_LEFT,
audio::AudioMode::FRONT_RIGHT,
audio::AudioMode::FRONT_CENTER,
audio::AudioMode::LOW_FREQUENCY,
audio::AudioMode::BACK_LEFT,
audio::AudioMode::BACK_RIGHT},
.bitrate = 256000},
// 7.1
{.channels = 8,
.streams = 5,
.coupled_streams = 3,
.speakers = {state::AudioMode::FRONT_LEFT,
state::AudioMode::FRONT_RIGHT,
state::AudioMode::FRONT_CENTER,
state::AudioMode::LOW_FREQUENCY,
state::AudioMode::BACK_LEFT,
state::AudioMode::BACK_RIGHT,
state::AudioMode::SIDE_LEFT,
state::AudioMode::SIDE_RIGHT}}};
.speakers = {audio::AudioMode::FRONT_LEFT,
audio::AudioMode::FRONT_RIGHT,
audio::AudioMode::FRONT_CENTER,
audio::AudioMode::LOW_FREQUENCY,
audio::AudioMode::BACK_LEFT,
audio::AudioMode::BACK_RIGHT,
audio::AudioMode::SIDE_LEFT,
audio::AudioMode::SIDE_RIGHT},
.bitrate = 450000}};
}

state::Host get_host_config(std::string_view pkey_filename, std::string_view cert_filename) {
Expand Down Expand Up @@ -228,10 +231,9 @@ auto setup_sessions_handlers(const immer::box<state::AppState> &app_state,
auto pulse_sink_name = fmt::format("virtual_sink_{}", session->session_id);
std::shared_ptr<audio::VSink> v_device;
if (audio_server && audio_server->server) {
v_device = audio::create_virtual_sink(audio_server->server,
audio::AudioDevice{.sink_name = pulse_sink_name,
.n_channels = session->audio_mode.channels,
.bitrate = 48000}); // TODO:
v_device = audio::create_virtual_sink(
audio_server->server,
audio::AudioDevice{.sink_name = pulse_sink_name, .mode = session->audio_mode});
}

/* Setup devices paths */
Expand Down

0 comments on commit 6283677

Please sign in to comment.