Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(display): Configure display device based on user config #3441

Merged
merged 1 commit into from
Jan 8, 2025
Merged
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
241 changes: 241 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,247 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</table>

### dd_configuration_option

<table>
<tr>
<td>Description</td>
<td colspan="2">
Perform mandatory verification and additional configuration for the display device.
@note{Applies to Windows only.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}verify_only@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
dd_configuration_option = ensure_only_display
@endcode</td>
</tr>
<tr>
<td rowspan="5">Choices</td>
<td>disabled</td>
<td>Perform no additional configuration (disables all `dd_` configuration options).</td>
</tr>
<tr>
<td>verify_only</td>
<td>Verify that display is active only (this is a mandatory step without any extra steps to verify display state).</td>
</tr>
<tr>
<td>ensure_active</td>
<td>Activate the display if it's currently inactive.</td>
</tr>
<tr>
<td>ensure_primary</td>
<td>Activate the display if it's currently inactive and make it primary.</td>
</tr>
<tr>
<td>ensure_only_display</td>
<td>Activate the display if it's currently inactive and disable all others.</td>
</tr>
</table>

### dd_resolution_option

<table>
<tr>
<td>Description</td>
<td colspan="2">
Perform additional resolution configuration for the display device.
@note{"Optimize game settings" must be enabled in Moonlight for this option to work.}
@note{Applies to Windows only.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}auto@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
dd_resolution_option = manual
@endcode</td>
</tr>
<tr>
<td rowspan="3">Choices</td>
<td>disabled</td>
<td>Perform no additional configuration.</td>
</tr>
<tr>
<td>auto</td>
<td>Change resolution to the requested resolution from the client.</td>
</tr>
<tr>
<td>manual</td>
<td>Change resolution to the user specified one (set via [dd_manual_resolution](#dd_manual_resolution)).</td>
</tr>
</table>

### dd_manual_resolution

<table>
<tr>
<td>Description</td>
<td colspan="2">
Specify manual resolution to be used.
@note{[dd_resolution_option](#dd_resolution_option) must be set to `manual`}
@note{Applies to Windows only.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">n/a</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
dd_manual_resolution = 1920x1080
@endcode</td>
</tr>
</table>

### dd_refresh_rate_option

<table>
<tr>
<td>Description</td>
<td colspan="2">
Perform additional refresh rate configuration for the display device.
@note{Applies to Windows only.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}auto@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
dd_refresh_rate_option = manual
@endcode</td>
</tr>
<tr>
<td rowspan="3">Choices</td>
<td>disabled</td>
<td>Perform no additional configuration.</td>
</tr>
<tr>
<td>auto</td>
<td>Change refresh rate to the requested FPS value from the client.</td>
</tr>
<tr>
<td>manual</td>
<td>Change refresh rate to the user specified one (set via [dd_manual_refresh_rate](#dd_manual_refresh_rate)).</td>
</tr>
</table>

### dd_manual_refresh_rate

<table>
<tr>
<td>Description</td>
<td colspan="2">
Specify manual refresh rate to be used.
@note{[dd_refresh_rate_option](#dd_refresh_rate_option) must be set to `manual`}
@note{Applies to Windows only.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">n/a</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
dd_manual_resolution = 120
dd_manual_resolution = 59.95
@endcode</td>
</tr>
</table>

### dd_hdr_option

<table>
<tr>
<td>Description</td>
<td colspan="2">
Perform additional HDR configuration for the display device.
@note{Applies to Windows only.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}auto@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
dd_hdr_option = disabled
@endcode</td>
</tr>
<tr>
<td rowspan="2">Choices</td>
<td>disabled</td>
<td>Perform no additional configuration.</td>
</tr>
<tr>
<td>auto</td>
<td>Change HDR to the requested state from the client if the display supports it.</td>
</tr>
</table>

### dd_wa_hdr_toggle

<table>
<tr>
<td>Description</td>
<td colspan="2">
When using virtual display device as for streaming, it might display incorrect (high-contrast) color.
With this option enabled, Sunshine will try to mitigate this issue.
@note{This option works independently of [dd_hdr_option](#dd_hdr_option)}
@note{Applies to Windows only.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
disabled
@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
dd_wa_hdr_toggle = enabled
@endcode</td>
</tr>
</table>

### dd_config_revert_delay

<table>
<tr>
<td>Description</td>
<td colspan="2">
Additional delay in milliseconds to wait before reverting configuration when the app has been closed or the last session terminated.
Main purpose is to provide a smoother transition when quickly switching between apps.
@note{Applies to Windows only.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}3000@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
dd_config_revert_delay = 1500
@endcode</td>
</tr>
</table>

### min_fps_factor

<table>
Expand Down
34 changes: 21 additions & 13 deletions src/audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,6 @@ namespace audio {
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<float>>>;

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;

bool restore_sink;
platf::sink_t sink;
};

static int
start_audio_control(audio_ctx_t &ctx);
static void
Expand Down Expand Up @@ -95,8 +85,6 @@ namespace audio {
},
};

auto control_shared = safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control);

void
encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
auto packets = mail::man->queue<packet_t>(mail::audio_packets);
Expand Down Expand Up @@ -149,7 +137,7 @@ namespace audio {
apply_surround_params(stream, config.customStreamParams);
}

auto ref = control_shared.ref();
auto ref = get_audio_ctx_ref();
if (!ref) {
return;
}
Expand Down Expand Up @@ -255,6 +243,26 @@ namespace audio {
}
}

audio_ctx_ref_t
get_audio_ctx_ref() {
static auto control_shared { safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control) };
return control_shared.ref();
}

bool
is_audio_ctx_sink_available(const audio_ctx_t &ctx) {
if (!ctx.control) {
return false;
}

const std::string &sink = ctx.sink.host.empty() ? config::audio.sink : ctx.sink.host;
if (sink.empty()) {
return false;
}

return ctx.control->is_sink_available(sink);
}

int
map_stream(int channels, bool quality) {
int shift = quality ? 1 : 0;
Expand Down
44 changes: 44 additions & 0 deletions src/audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
#pragma once

// local includes
#include "platform/common.h"
#include "thread_safe.h"
#include "utility.h"

Expand Down Expand Up @@ -55,8 +57,50 @@ namespace audio {
std::bitset<MAX_FLAGS> flags;
};

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;

bool restore_sink;
platf::sink_t sink;
};

using buffer_t = util::buffer_t<std::uint8_t>;
using packet_t = std::pair<void *, buffer_t>;
using audio_ctx_ref_t = safe::shared_t<audio_ctx_t>::ptr_t;

void
capture(safe::mail_t mail, config_t config, void *channel_data);

/**
* @brief Get the reference to the audio context.
* @returns A shared pointer reference to audio context.
* @note Aside from the configuration purposes, it can be used to extend the
* audio sink lifetime to capture sink earlier and restore it later.
*
* @examples
* audio_ctx_ref_t audio = get_audio_ctx_ref()
* @examples_end
*/
audio_ctx_ref_t
get_audio_ctx_ref();

/**
* @brief Check if the audio sink held by audio context is available.
* @returns True if available (and can probably be restored), false otherwise.
* @note Useful for delaying the release of audio context shared pointer (which
* tries to restore original sink).
*
* @examples
* audio_ctx_ref_t audio = get_audio_ctx_ref()
* if (audio.get()) {
* return is_audio_ctx_sink_available(*audio.get());
* }
* return false;
* @examples_end
*/
bool
is_audio_ctx_sink_available(const audio_ctx_t &ctx);
} // namespace audio
Loading
Loading