Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion cmake/moonlight-dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function(_moonlight_compute_ffmpeg_signature out_var nxdk_dir ffmpeg_source_dir)
set(signature_inputs
"FFMPEG_REVISION=${ffmpeg_revision}"
"NXDK_DIR=${nxdk_dir}"
"FFMPEG_PROFILE=h264-opus-xbox"
"FFMPEG_PROFILE=h264-mpeg2-h263-opus-xbox"
"FFMPEG_TARGET_OS=none"
"FFMPEG_ARCH=x86"
"FFMPEG_CC_WRAPPER_SHA256=${ffmpeg_cc_wrapper_hash}"
Expand Down Expand Up @@ -201,7 +201,13 @@ function(_moonlight_get_ffmpeg_configure_args out_var)
--enable-swscale
--enable-swresample
--enable-parser=h264
--enable-parser=h263
--enable-parser=mpegvideo
--enable-decoder=h264
--enable-decoder=h263
--enable-decoder=h263i
--enable-decoder=h263p
--enable-decoder=mpeg2video
--enable-decoder=opus)

set(${out_var} "${ffmpeg_configure_args}" PARENT_SCOPE)
Expand Down
58 changes: 58 additions & 0 deletions src/app/client_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ namespace {
constexpr int DEFAULT_STREAM_BITRATE_KBPS = 1000;
constexpr std::array<int, 6> STREAM_FRAMERATE_OPTIONS {15, 20, 24, 25, 30, 60};
constexpr std::array<int, 9> STREAM_BITRATE_OPTIONS {500, 750, 1000, 1500, 2000, 2500, 3000, 4000, 5000};
constexpr std::array<app::VideoDecoderSelection, 4> VIDEO_DECODER_OPTIONS {
app::VideoDecoderSelection::autoDetect,
app::VideoDecoderSelection::h264,
app::VideoDecoderSelection::mpeg2,
app::VideoDecoderSelection::h263p,
};

/**
* @brief Describes the keypad characters available for the active add-host field.
Expand Down Expand Up @@ -144,6 +150,27 @@ namespace {
return std::to_string(videoMode.width) + "x" + std::to_string(videoMode.height);
}

/**
* @brief Return the settings label for one video decoder preference.
*
* @param selection Decoder preference to describe.
* @return User-facing decoder preference label.
*/
const char *video_decoder_selection_label(app::VideoDecoderSelection selection) {
switch (selection) {
case app::VideoDecoderSelection::autoDetect:
return "Auto";
case app::VideoDecoderSelection::h264:
return "H.264";
case app::VideoDecoderSelection::mpeg2:
return "MPEG-2/H.262";
case app::VideoDecoderSelection::h263p:
return "H.263+";
}

return "Auto";
}

/**
* @brief Return the selected stream-resolution index inside the detected mode list.
*
Expand Down Expand Up @@ -213,6 +240,22 @@ namespace {
state.settings.streamBitrateKbps = STREAM_BITRATE_OPTIONS[nextIndex];
}

/**
* @brief Advance the preferred video decoder to the next supported option.
*
* @param state Current client state containing the preferred decoder.
*/
void cycle_video_decoder(app::ClientState &state) {
const auto current = std::find(VIDEO_DECODER_OPTIONS.begin(), VIDEO_DECODER_OPTIONS.end(), state.settings.videoDecoder);
if (current == VIDEO_DECODER_OPTIONS.end()) {
state.settings.videoDecoder = app::VideoDecoderSelection::autoDetect;
return;
}

const std::size_t nextIndex = (static_cast<std::size_t>(std::distance(VIDEO_DECODER_OPTIONS.begin(), current)) + 1U) % VIDEO_DECODER_OPTIONS.size();
state.settings.videoDecoder = VIDEO_DECODER_OPTIONS[nextIndex];
}

std::string pairing_reset_endpoint_key(std::string_view address, uint16_t port) {
return app::normalize_ipv4_address(address) + ":" + std::to_string(app::effective_host_port(port));
}
Expand Down Expand Up @@ -544,6 +587,12 @@ namespace {
"Cycle through the preferred video bitrate. Lower bitrates reduce bandwidth use and can help when running Sunshine and xemu on the same NATed host.",
true,
},
{
"cycle-video-decoder",
std::string("Video Decoder: ") + video_decoder_selection_label(state.settings.videoDecoder),
"Choose automatic codec negotiation or force one FFmpeg video decoder for new streams.",
true,
},
{
"toggle-play-audio-on-pc",
std::string("Play Audio on PC: ") + (state.settings.playAudioOnPc ? "On" : "Off"),
Expand Down Expand Up @@ -1405,6 +1454,7 @@ namespace app {
state.settings.xemuConsoleLoggingLevel = logging::LogLevel::none;
state.settings.streamFramerate = DEFAULT_STREAM_FRAMERATE;
state.settings.streamBitrateKbps = DEFAULT_STREAM_BITRATE_KBPS;
state.settings.videoDecoder = VideoDecoderSelection::autoDetect;
state.settings.playAudioOnPc = false;
state.settings.showPerformanceStats = false;
state.settings.playAudioOnXbox = true;
Expand Down Expand Up @@ -1996,6 +2046,14 @@ namespace app {
rebuild_menu(state, "cycle-stream-bitrate");
return;
}
if (detailUpdate.activatedItemId == "cycle-video-decoder") {
cycle_video_decoder(state);
state.settings.dirty = true;
update->persistence.settingsChanged = true;
state.shell.statusMessage = std::string("Video decoder set to ") + video_decoder_selection_label(state.settings.videoDecoder);
rebuild_menu(state, "cycle-video-decoder");
return;
}
if (detailUpdate.activatedItemId == "toggle-play-audio-on-pc") {
state.settings.playAudioOnPc = !state.settings.playAudioOnPc;
state.settings.dirty = true;
Expand Down
11 changes: 11 additions & 0 deletions src/app/client_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ namespace app {
right, ///< Dock the log viewer on the right side of the split layout.
};

/**
* @brief Video decoder preference applied to new streaming sessions.
*/
enum class VideoDecoderSelection {
autoDetect, ///< Let moonlight-common-c negotiate the best mutually supported video format.
h264, ///< Force H.264 video decode.
mpeg2, ///< Force MPEG-2/H.262 video decode.
h263p, ///< Force H.263+ video decode.
};

/**
* @brief Focus areas used by the two-pane settings screen.
*/
Expand Down Expand Up @@ -321,6 +331,7 @@ namespace app {
bool preferredVideoModeSet = false; ///< True when preferredVideoMode contains a user-selected or default mode.
int streamFramerate = 30; ///< Preferred stream frame rate in frames per second.
int streamBitrateKbps = 1000; ///< Preferred stream bitrate in kilobits per second.
VideoDecoderSelection videoDecoder = VideoDecoderSelection::autoDetect; ///< Preferred video decoder for new streams.
bool playAudioOnPc = false; ///< True when the host PC should continue local audio playback during streaming.
bool showPerformanceStats = false; ///< True when stream telemetry should be shown after streaming ends.
bool playAudioOnXbox = true; ///< True when the Xbox should decode and play streamed audio locally.
Expand Down
91 changes: 90 additions & 1 deletion src/app/settings_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,27 @@ namespace {
return "full";
}

/**
* @brief Convert a decoder preference to the persisted TOML token.
*
* @param selection Decoder preference to serialize.
* @return Stable lowercase settings value.
*/
const char *video_decoder_selection_text(app::VideoDecoderSelection selection) {
switch (selection) {
case app::VideoDecoderSelection::autoDetect:
return "auto";
case app::VideoDecoderSelection::h264:
return "h264";
case app::VideoDecoderSelection::mpeg2:
return "mpeg2";
case app::VideoDecoderSelection::h263p:
return "h263p";
}

return "auto";
}

bool try_parse_logging_level(std::string_view text, logging::LogLevel *level) {
const std::string normalized = ascii_lowercase(text);
if (normalized == "trace") {
Expand Down Expand Up @@ -193,6 +214,43 @@ namespace {
return false;
}

/**
* @brief Parse a decoder preference from the persisted TOML token.
*
* @param text Settings value to parse.
* @param selection Receives the parsed decoder preference on success.
* @return True when @p text is a supported decoder preference.
*/
bool try_parse_video_decoder_selection(std::string_view text, app::VideoDecoderSelection *selection) {
const std::string normalized = ascii_lowercase(text);
if (normalized == "auto") {
if (selection != nullptr) {
*selection = app::VideoDecoderSelection::autoDetect;
}
return true;
}
if (normalized == "h264" || normalized == "h.264") {
if (selection != nullptr) {
*selection = app::VideoDecoderSelection::h264;
}
return true;
}
if (normalized == "mpeg2" || normalized == "mpeg-2" || normalized == "h262" || normalized == "h.262") {
if (selection != nullptr) {
*selection = app::VideoDecoderSelection::mpeg2;
}
return true;
}
if (normalized == "h263p" || normalized == "h.263p" || normalized == "h263+" || normalized == "h.263+") {
if (selection != nullptr) {
*selection = app::VideoDecoderSelection::h263p;
}
return true;
}

return false;
}

void append_invalid_value_warning(std::vector<std::string> *warnings, const std::string &filePath, std::string_view keyPath, std::string_view valueText) {
if (warnings == nullptr) {
return;
Expand Down Expand Up @@ -263,6 +321,34 @@ namespace {
append_invalid_value_warning(warnings, filePath, "ui.log_viewer_placement", "<non-string>");
}

/**
* @brief Load one decoder preference setting when present.
*
* @param settingNode TOML node to parse.
* @param filePath Settings file path used in warnings.
* @param selection Receives the parsed decoder preference on success.
* @param warnings Warning collection updated for invalid values.
*/
void load_video_decoder_selection_setting(
toml::node_view<const toml::node> settingNode,
const std::string &filePath,
app::VideoDecoderSelection *selection,
std::vector<std::string> *warnings
) {
if (!settingNode) {
return;
}

if (const auto videoDecoderText = settingNode.value<std::string>(); videoDecoderText) {
if (!try_parse_video_decoder_selection(*videoDecoderText, selection)) {
append_invalid_value_warning(warnings, filePath, "streaming.video_decoder", *videoDecoderText);
}
return;
}

append_invalid_value_warning(warnings, filePath, "streaming.video_decoder", "<non-string>");
}

/**
* @brief Load one integer settings value when present.
*
Expand Down Expand Up @@ -346,6 +432,8 @@ namespace {
content += "# Preferred streaming parameters.\n";
content += std::string("fps = ") + std::to_string(settings.streamFramerate) + "\n";
content += std::string("bitrate_kbps = ") + std::to_string(settings.streamBitrateKbps) + "\n";
content += "# Video decoder preference. Use auto to keep normal host negotiation.\n";
content += std::string("video_decoder = \"") + video_decoder_selection_text(settings.videoDecoder) + "\"\n";
content += std::string("play_audio_on_pc = ") + (settings.playAudioOnPc ? "true" : "false") + "\n";
content += std::string("play_audio_on_xbox = ") + (settings.playAudioOnXbox ? "true" : "false") + "\n";
content += "# Show stream telemetry after streaming ends.\n";
Expand Down Expand Up @@ -392,7 +480,7 @@ namespace {
void inspect_streaming_keys(const toml::table &streamingTable, const std::string &filePath, app::LoadAppSettingsResult *result) {
for (const auto &[rawKey, node] : streamingTable) {
const std::string key(rawKey.str());
if (key == "video_width" || key == "video_height" || key == "video_bpp" || key == "video_refresh" || key == "video_mode_selected" || key == "fps" || key == "bitrate_kbps" || key == "play_audio_on_pc" || key == "play_audio_on_xbox" || key == "show_performance_stats") {
if (key == "video_width" || key == "video_height" || key == "video_bpp" || key == "video_refresh" || key == "video_mode_selected" || key == "fps" || key == "bitrate_kbps" || key == "video_decoder" || key == "play_audio_on_pc" || key == "play_audio_on_xbox" || key == "show_performance_stats") {
continue;
}

Expand Down Expand Up @@ -490,6 +578,7 @@ namespace app {
load_boolean_setting(settingsTable["streaming"]["video_mode_selected"], filePath, "streaming.video_mode_selected", &result.settings.preferredVideoModeSet, &result.warnings);
load_integer_setting(settingsTable["streaming"]["fps"], filePath, "streaming.fps", &result.settings.streamFramerate, &result.warnings);
load_integer_setting(settingsTable["streaming"]["bitrate_kbps"], filePath, "streaming.bitrate_kbps", &result.settings.streamBitrateKbps, &result.warnings);
load_video_decoder_selection_setting(settingsTable["streaming"]["video_decoder"], filePath, &result.settings.videoDecoder, &result.warnings);
load_boolean_setting(settingsTable["streaming"]["play_audio_on_pc"], filePath, "streaming.play_audio_on_pc", &result.settings.playAudioOnPc, &result.warnings);
load_boolean_setting(settingsTable["streaming"]["play_audio_on_xbox"], filePath, "streaming.play_audio_on_xbox", &result.settings.playAudioOnXbox, &result.warnings);
load_boolean_setting(settingsTable["streaming"]["show_performance_stats"], filePath, "streaming.show_performance_stats", &result.settings.showPerformanceStats, &result.warnings);
Expand Down
1 change: 1 addition & 0 deletions src/app/settings_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ namespace app {
bool preferredVideoModeSet = false; ///< True when preferredVideoMode contains a saved user preference.
int streamFramerate = 30; ///< Preferred stream frame rate in frames per second.
int streamBitrateKbps = 1000; ///< Preferred stream bitrate in kilobits per second.
app::VideoDecoderSelection videoDecoder = app::VideoDecoderSelection::autoDetect; ///< Preferred video decoder for new streams.
bool playAudioOnPc = false; ///< True when the host PC should continue local audio playback during streaming.
bool showPerformanceStats = false; ///< True when stream telemetry should be shown after streaming ends.
bool playAudioOnXbox = true; ///< True when the Xbox should decode and play streamed audio locally.
Expand Down
1 change: 1 addition & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ namespace {
state.settings.preferredVideoModeSet = settings.preferredVideoModeSet;
state.settings.streamFramerate = settings.streamFramerate;
state.settings.streamBitrateKbps = settings.streamBitrateKbps;
state.settings.videoDecoder = settings.videoDecoder;
state.settings.playAudioOnPc = settings.playAudioOnPc;
state.settings.showPerformanceStats = settings.showPerformanceStats;
state.settings.playAudioOnXbox = settings.playAudioOnXbox;
Expand Down
8 changes: 6 additions & 2 deletions src/startup/video_mode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ namespace startup {
return left.width == right.width && left.height == right.height;
}

int stream_video_mode_area(const VIDEO_MODE &videoMode) {
return videoMode.width * videoMode.height;
}

bool is_smaller_video_mode(const VIDEO_MODE &left, const VIDEO_MODE &right) {
const int leftArea = left.width * left.height;
if (const int rightArea = right.width * right.height; leftArea != rightArea) {
const int leftArea = stream_video_mode_area(left);
if (const int rightArea = stream_video_mode_area(right); leftArea != rightArea) {
return leftArea < rightArea;
}
return left.width < right.width;
Expand Down
Loading
Loading