Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1511374959 |
@@ -399,7 +399,7 @@ final class SessionModel: ObservableObject {
|
||||
let hz = UInt32(clamping: effective.refreshHz)
|
||||
let compositor = PunktfunkConnection.Compositor(
|
||||
rawValue: UInt32(clamping: effective.compositor)) ?? .auto
|
||||
let bitrateKbps = UInt32(clamping: effective.bitrateKbps)
|
||||
var bitrateKbps = UInt32(clamping: effective.bitrateKbps)
|
||||
let audioChannels = UInt8(clamping: effective.audioChannels)
|
||||
// The audio format this session ASKS for — the user's choice, at every channel count.
|
||||
//
|
||||
@@ -419,6 +419,15 @@ final class SessionModel: ObservableObject {
|
||||
let (audioRateHz, audioBits) = audioFormat.wire
|
||||
let hdrEnabled = effective.hdrEnabled
|
||||
let preferredCodec = PunktfunkConnection.codecByte(effective.codec)
|
||||
// PyroWave is always Automatic bitrate (ABR overhaul RFC §5.2): a fixed kbps is
|
||||
// ill-defined for the all-intra codec (bpp is the operating point) and used to bypass
|
||||
// the host's operator ceiling — send 0 and let the host pin its per-mode rate. Gated
|
||||
// like the advertisement below: a device that failed the Metal probe never offers the
|
||||
// codec, falls back to H.26x, and the user's rate must survive there. The stored
|
||||
// setting is untouched, so switching codecs back restores it.
|
||||
if preferredCodec == PunktfunkConnection.codecPyroWave, MetalWaveletDecoder.supported {
|
||||
bitrateKbps = 0
|
||||
}
|
||||
let pin = host.pinnedSHA256
|
||||
// Capability gate (main-actor — screen APIs): only advertise HDR when this display can
|
||||
// actually present it, so the host sends a proper SDR stream to an SDR display rather than
|
||||
|
||||
@@ -256,11 +256,25 @@ extension SettingsView {
|
||||
|
||||
/// The automatic-bitrate toggle + manual slider (and the >1 Gbps warning) rows.
|
||||
@ViewBuilder private var bitrateRows: some View {
|
||||
described("Uses the host's default, 20 Mbps. Off to set it yourself.",
|
||||
field: "bitrate_kbps") {
|
||||
Toggle("Automatic bitrate", isOn: automaticBitrate)
|
||||
// PyroWave is always Automatic (ABR overhaul RFC §5.2): the session sends 0 and the
|
||||
// host pins a per-mode rate, so a live rate control here would change nothing. Same
|
||||
// support gate as the codec picker offering the option; the stored rate is untouched,
|
||||
// so switching the codec back restores it.
|
||||
if effective.codec == "pyrowave", MetalWaveletDecoder.supported {
|
||||
described("PyroWave sets its own rate from the stream mode — a fixed bitrate "
|
||||
+ "doesn't apply.",
|
||||
field: "bitrate_kbps") {
|
||||
Toggle("Automatic bitrate", isOn: .constant(true))
|
||||
.disabled(true)
|
||||
}
|
||||
} else {
|
||||
described("Uses the host's default, 20 Mbps. Off to set it yourself.",
|
||||
field: "bitrate_kbps") {
|
||||
Toggle("Automatic bitrate", isOn: automaticBitrate)
|
||||
}
|
||||
}
|
||||
if effective.bitrateKbps != 0 {
|
||||
if effective.codec != "pyrowave" || !MetalWaveletDecoder.supported,
|
||||
effective.bitrateKbps != 0 {
|
||||
HStack(spacing: 12) {
|
||||
Slider(value: bitrateSlider, in: 0...1) {
|
||||
Text("Bitrate")
|
||||
|
||||
@@ -450,15 +450,24 @@ struct SettingsView: View {
|
||||
title: "Render scale",
|
||||
options: RenderScale.presets.map { (label: RenderScale.label($0), tag: $0) },
|
||||
selection: $renderScale)
|
||||
TVSelectionRow(
|
||||
title: "Bitrate",
|
||||
options: SettingsOptions.bitrateOptions(current: bitrateKbps),
|
||||
selection: $bitrateKbps)
|
||||
if bitrateKbps > 1_000_000 {
|
||||
Label(Self.gigabitWarning, systemImage: "exclamationmark.triangle.fill")
|
||||
.font(.geist(20, relativeTo: .caption)) // TV-legible caption size
|
||||
.foregroundStyle(.orange)
|
||||
.multilineTextAlignment(.center)
|
||||
// PyroWave is always Automatic (ABR overhaul RFC §5.2): the session sends 0
|
||||
// and the host pins a per-mode rate. tvOS has no codec picker, so this only
|
||||
// fires on a codec synced from another device — but the row must not offer a
|
||||
// rate the session ignores. The stored value is kept.
|
||||
if codec == "pyrowave", MetalWaveletDecoder.supported {
|
||||
tvCaption("PyroWave sets its own rate from the stream mode — the bitrate "
|
||||
+ "setting doesn't apply.")
|
||||
} else {
|
||||
TVSelectionRow(
|
||||
title: "Bitrate",
|
||||
options: SettingsOptions.bitrateOptions(current: bitrateKbps),
|
||||
selection: $bitrateKbps)
|
||||
if bitrateKbps > 1_000_000 {
|
||||
Label(Self.gigabitWarning, systemImage: "exclamationmark.triangle.fill")
|
||||
.font(.geist(20, relativeTo: .caption)) // TV-legible caption size
|
||||
.foregroundStyle(.orange)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
TVSelectionRow(
|
||||
title: "10-bit HDR",
|
||||
|
||||
@@ -795,6 +795,25 @@ fn pump(
|
||||
// rung at all, so advertising HEVC would promise what this build cannot keep.
|
||||
¶ms.decoder,
|
||||
) & !params.exclude_codecs;
|
||||
// PyroWave is always Automatic bitrate (ABR overhaul RFC §5.2): a fixed kbps is
|
||||
// ill-defined for the all-intra codec (bpp is the operating point) and used to bypass
|
||||
// the host's `PUNKTFUNK_PYROWAVE_MAX_MBPS` ceiling. Send 0 and let the host pin; the
|
||||
// stored profile value is untouched, so switching codecs back restores it. Gated on the
|
||||
// codec actually being ADVERTISED: a pyrowave preference on a device that failed the
|
||||
// decode probe falls back to H.26x, where the user's explicit rate must survive.
|
||||
let bitrate_kbps = if preferred == punktfunk_core::quic::CODEC_PYROWAVE
|
||||
&& advertised_codecs & punktfunk_core::quic::CODEC_PYROWAVE != 0
|
||||
{
|
||||
if params.bitrate_kbps != 0 {
|
||||
tracing::info!(
|
||||
stored_kbps = params.bitrate_kbps,
|
||||
"PyroWave forces Automatic bitrate — asking the host for its per-mode pin"
|
||||
);
|
||||
}
|
||||
0
|
||||
} else {
|
||||
params.bitrate_kbps
|
||||
};
|
||||
if params.exclude_codecs != 0 {
|
||||
tracing::info!(
|
||||
excluded = params.exclude_codecs,
|
||||
@@ -840,7 +859,7 @@ fn pump(
|
||||
params.mode,
|
||||
params.compositor,
|
||||
params.gamepad,
|
||||
params.bitrate_kbps,
|
||||
bitrate_kbps,
|
||||
params.video_caps,
|
||||
params.audio_channels,
|
||||
audio_rate_hz,
|
||||
|
||||
@@ -677,9 +677,12 @@ impl SettingsScreen {
|
||||
let ids = self.row_ids(ctx);
|
||||
self.clamp_cursor(ids.len());
|
||||
// Y on the Bitrate row opens the typed rate; on every other row it means nothing,
|
||||
// and the hint bar only offers it where it does.
|
||||
// and the hint bar only offers it where it does. Not under PyroWave — the row is
|
||||
// dimmed (see `row_spec`) and a typed rate would be as inert as the ladder.
|
||||
if ev == MenuEvent::Secondary {
|
||||
return if ids.get(self.list.cursor) == Some(&RowId::Bitrate) {
|
||||
return if ids.get(self.list.cursor) == Some(&RowId::Bitrate)
|
||||
&& ctx.settings.codec != "pyrowave"
|
||||
{
|
||||
self.custom_bitrate = Some(String::new());
|
||||
Some(MenuPulse::Confirm)
|
||||
} else {
|
||||
@@ -824,6 +827,11 @@ impl SettingsScreen {
|
||||
Hint::new(HintKey::Confirm, "Open"),
|
||||
Hint::new(HintKey::Back, "Done"),
|
||||
],
|
||||
// Dimmed under PyroWave (row_spec): offering "Adjust" on an inert row would
|
||||
// teach a control that answers with a thud.
|
||||
Some(RowId::Bitrate) if ctx.settings.codec == "pyrowave" => {
|
||||
vec![Hint::new(HintKey::Back, "Done")]
|
||||
}
|
||||
// The one row with a value the ladder cannot name every version of.
|
||||
Some(RowId::Bitrate) => vec![
|
||||
Hint::new(HintKey::Adjust, "Adjust"),
|
||||
@@ -910,7 +918,7 @@ impl SettingsScreen {
|
||||
let detail = ids
|
||||
.get(self.list.cursor)
|
||||
.copied()
|
||||
.map_or("", |id| detail(id, ctx.platform));
|
||||
.map_or("", |id| detail(id, ctx));
|
||||
fonts.centered(
|
||||
canvas,
|
||||
detail,
|
||||
@@ -1042,6 +1050,11 @@ fn row_spec(id: RowId, ctx: &Ctx, profiles: &[(String, String)]) -> RowSpec {
|
||||
// that one is different.
|
||||
let enabled = match id {
|
||||
RowId::EchoCancel => s.mic_enabled,
|
||||
// PyroWave is always Automatic bitrate (ABR overhaul RFC §5.2): the session sends 0
|
||||
// whatever this row stores and the host pins a per-mode bpp rate. Dimmed, not live —
|
||||
// a control that changes nothing must say so. The stored rate is kept: switching the
|
||||
// codec back restores it.
|
||||
RowId::Bitrate => s.codec != "pyrowave",
|
||||
// ⚠ Lossless follows the channel count for a reason that has MOVED, and the old reason
|
||||
// is still written down in several places that are now wrong (`hi-res-audio.md` §4.2's
|
||||
// blanket "surround does not fit a datagram", and `trust::Settings::audio_format`'s doc
|
||||
@@ -1301,8 +1314,9 @@ fn row_spec(id: RowId, ctx: &Ctx, profiles: &[(String, String)]) -> RowSpec {
|
||||
/// The focused row's one-line explainer. Takes the platform because two desktop rows
|
||||
/// advertise desktop-only live chords (Ctrl+Alt+Shift+…) that no Android build has — a
|
||||
/// shortcut the device cannot press must not be taught.
|
||||
fn detail(id: RowId, platform: crate::platform::Platform) -> &'static str {
|
||||
fn detail(id: RowId, ctx: &Ctx) -> &'static str {
|
||||
use crate::platform::Platform;
|
||||
let platform = ctx.platform;
|
||||
match id {
|
||||
RowId::Resolution => {
|
||||
"The host creates a virtual display at exactly this size — no scaling. \
|
||||
@@ -1313,6 +1327,10 @@ fn detail(id: RowId, platform: crate::platform::Platform) -> &'static str {
|
||||
"The host renders larger or smaller than the stream mode and this window \
|
||||
resamples — above 1× supersamples, below saves bandwidth."
|
||||
}
|
||||
RowId::Bitrate if ctx.settings.codec == "pyrowave" => {
|
||||
"PyroWave sets its own rate from the stream mode (all-intra) — a fixed bitrate \
|
||||
doesn't apply. Pick another codec to use this setting."
|
||||
}
|
||||
RowId::Bitrate => {
|
||||
"Automatic uses the host's default (20 Mbps). Y types an exact rate, up to 2 Gbps."
|
||||
}
|
||||
@@ -1573,6 +1591,11 @@ fn adjust(id: RowId, delta: i32, wrap: bool, ctx: &mut Ctx) -> bool {
|
||||
.map(|i| s.render_scale = RENDER_SCALES[i])
|
||||
}
|
||||
RowId::Bitrate => {
|
||||
// Inert under PyroWave — a boundary thud, matching what the dimmed row shows
|
||||
// (the host pins the rate; see `row_spec`).
|
||||
if s.codec == "pyrowave" {
|
||||
return false;
|
||||
}
|
||||
// A typed rate (or one a desktop shell's spinner stored) sits BETWEEN rungs, and
|
||||
// the generic step snaps a value it cannot find to the first option — which here
|
||||
// is Automatic, i.e. one nudge throws the custom rate away. Step to the rung the
|
||||
@@ -2145,6 +2168,40 @@ pub(super) mod tests {
|
||||
assert!(ctx.settings.echo_cancel);
|
||||
}
|
||||
|
||||
/// Bitrate follows the codec: dimmed and inert under PyroWave (the host pins a per-mode
|
||||
/// rate and the session sends 0 — ABR overhaul RFC §5.2), live for every other codec,
|
||||
/// and the stored rate survives the dim so switching back restores it.
|
||||
#[test]
|
||||
fn bitrate_dims_under_pyrowave() {
|
||||
let (mut settings, pads) = ctx_parts();
|
||||
settings.codec = "pyrowave".into();
|
||||
settings.bitrate_kbps = 80_000;
|
||||
let library = crate::library::LibraryShared::default();
|
||||
let mut ctx = Ctx {
|
||||
hosts: &[],
|
||||
library: &library,
|
||||
settings: &mut settings,
|
||||
store: crate::store::file_store(),
|
||||
platform: crate::platform::Platform::Desktop,
|
||||
pads: &pads,
|
||||
deck: false,
|
||||
fallback_ui: false,
|
||||
device_name: "t",
|
||||
t: 0.0,
|
||||
};
|
||||
assert!(!row_spec(RowId::Bitrate, &ctx, &[]).enabled);
|
||||
assert!(
|
||||
!adjust(RowId::Bitrate, 1, false, &mut ctx),
|
||||
"pyrowave = thud"
|
||||
);
|
||||
assert!(!adjust(RowId::Bitrate, 1, true, &mut ctx), "A too");
|
||||
assert_eq!(ctx.settings.bitrate_kbps, 80_000, "the stored rate is kept");
|
||||
|
||||
ctx.settings.codec = "hevc".into();
|
||||
assert!(row_spec(RowId::Bitrate, &ctx, &[]).enabled);
|
||||
assert!(adjust(RowId::Bitrate, 1, false, &mut ctx));
|
||||
}
|
||||
|
||||
/// The smoothness buffer is OFFERED only under Smoothness — under Lowest latency it names
|
||||
/// a quantity that doesn't exist, so the row is gone from the Video tab rather than sitting
|
||||
/// there dimmed. This is what the GTK and WinUI shells and the Apple/Android screens have
|
||||
|
||||
@@ -931,7 +931,13 @@ fn resolve_bitrate_kbps(requested: u32) -> u32 {
|
||||
/// an Automatic client (`0`) gets the codec's ~1.6 bpp operating point for the negotiated
|
||||
/// mode instead of the 20 Mbps H.26x default. The rate is then PINNED for the session:
|
||||
/// the client's ABR controller stays off for this codec and the host refuses mid-stream
|
||||
/// retargets. An explicit client rate is honored unchanged (the operator knows the link).
|
||||
/// retargets.
|
||||
///
|
||||
/// PyroWave ignores an explicit client rate too (ABR overhaul RFC §5.2): a fixed rate is
|
||||
/// ill-defined for an all-intra codec (bpp is the operating point, not kbps) and it used to
|
||||
/// bypass the `PUNKTFUNK_PYROWAVE_MAX_MBPS` operator ceiling. Clients grey the control out;
|
||||
/// this arm is the belt-and-braces for embedders that never update their UI. H.26x/AV1
|
||||
/// explicit rates are honored unchanged (the operator knows the link).
|
||||
fn resolve_bitrate_kbps_for(
|
||||
codec: crate::encode::Codec,
|
||||
requested: u32,
|
||||
@@ -939,7 +945,14 @@ fn resolve_bitrate_kbps_for(
|
||||
chroma: crate::encode::ChromaFormat,
|
||||
bit_depth: u8,
|
||||
) -> u32 {
|
||||
if requested == 0 && codec == crate::encode::Codec::PyroWave {
|
||||
if codec == crate::encode::Codec::PyroWave {
|
||||
if requested != 0 {
|
||||
tracing::warn!(
|
||||
requested_kbps = requested,
|
||||
"an explicit bitrate is ill-defined under PyroWave (all-intra bpp semantics) — \
|
||||
treating it as Automatic and resolving the per-mode pin"
|
||||
);
|
||||
}
|
||||
// ~1.6 bpp for 4:2:0. 4:4:4 doubles the samples per pixel (3 vs 1.5) but chroma
|
||||
// compresses better than luma → ×1.625 ≈ 2.6 bpp; 16-bit planes add ~15 % (both
|
||||
// factors measured against the Phase-0 fixture matrix, design/pyrowave-444-hdr.md).
|
||||
@@ -976,7 +989,8 @@ fn resolve_bitrate_kbps_for(
|
||||
|
||||
/// Operator ceiling for PyroWave's open-loop Automatic bitrate pin: `PUNKTFUNK_PYROWAVE_MAX_MBPS`
|
||||
/// (megabits/s) → kbps, or `None` when unset/zero/invalid (no cap — the raw bpp pin stands).
|
||||
/// Only consulted for `requested == 0` PyroWave sessions; an explicit client bitrate bypasses it.
|
||||
/// Consulted for every PyroWave session — an explicit client bitrate resolves through the
|
||||
/// pin too (RFC §5.2), so nothing bypasses the ceiling.
|
||||
fn pyrowave_auto_pin_ceiling_kbps() -> Option<u32> {
|
||||
std::env::var("PUNKTFUNK_PYROWAVE_MAX_MBPS")
|
||||
.ok()
|
||||
@@ -2035,8 +2049,9 @@ async fn serve_session(
|
||||
});
|
||||
let bitrate_kbps = welcome.bitrate_kbps; // resolved encoder bitrate (Hello clamped, or default)
|
||||
// "Automatic" request: the resolved rate is a host default — for PyroWave a per-mode
|
||||
// bpp pin the data plane re-resolves on a mid-stream mode switch.
|
||||
let bitrate_auto = hello.bitrate_kbps == 0;
|
||||
// bpp pin the data plane re-resolves on a mid-stream mode switch. PyroWave is Automatic
|
||||
// unconditionally (`resolve_bitrate_kbps_for` overrode any explicit rate — RFC §5.2).
|
||||
let bitrate_auto = hello.bitrate_kbps == 0 || codec == crate::encode::Codec::PyroWave;
|
||||
let bit_depth = welcome.bit_depth; // resolved encode bit depth (8, or 10 when negotiated)
|
||||
// Resolved chroma — derive the typed value back from the wire byte the Welcome carried (so the
|
||||
// session uses exactly what the client was told). `Yuv444` only when the handshake gate passed.
|
||||
@@ -2501,7 +2516,8 @@ mod tests {
|
||||
),
|
||||
(1920u64 * 1080 * 60 * 26 / 10 * 115 / 100 / 1000) as u32
|
||||
);
|
||||
// An explicit client rate is honored (clamped like any other codec)...
|
||||
// An explicit client rate is overridden to the same pin — a fixed kbps is ill-defined
|
||||
// for the all-intra codec, and it used to skip the operator ceiling (RFC §5.2)...
|
||||
assert_eq!(
|
||||
resolve_bitrate_kbps_for(
|
||||
crate::encode::Codec::PyroWave,
|
||||
@@ -2510,7 +2526,7 @@ mod tests {
|
||||
ChromaFormat::Yuv420,
|
||||
8
|
||||
),
|
||||
130_000
|
||||
1920 * 1080 * 60 * 16 / 10 / 1000
|
||||
);
|
||||
// ...and the H.26x codecs keep the legacy default.
|
||||
assert_eq!(
|
||||
@@ -2559,10 +2575,11 @@ mod tests {
|
||||
resolve_bitrate_kbps_for(Codec::PyroWave, 0, &small, ChromaFormat::Yuv420, 8),
|
||||
1920 * 1080 * 60 * 16 / 10 / 1000
|
||||
);
|
||||
// ...and an explicit client rate bypasses the ceiling entirely.
|
||||
// ...and an explicit client rate no longer bypasses it: PyroWave resolves through the
|
||||
// pin + ceiling whatever the Hello carried (RFC §5.2 — this bypass was the bug).
|
||||
assert_eq!(
|
||||
resolve_bitrate_kbps_for(Codec::PyroWave, 6_000_000, &mode, ChromaFormat::Yuv444, 10),
|
||||
6_000_000
|
||||
4_500_000
|
||||
);
|
||||
// SAFETY: as the set above — single writer, and the readers run on this thread.
|
||||
unsafe { std::env::remove_var("PUNKTFUNK_PYROWAVE_MAX_MBPS") };
|
||||
|
||||
@@ -1106,7 +1106,9 @@ pub(super) async fn negotiate(
|
||||
// "Automatic" — `bitrate_kbps` above is the host's own answer for `mode`, so the build
|
||||
// may re-resolve it if the source turns out to deliver a different size. Sampled here
|
||||
// rather than in the thread body so the closure doesn't have to capture `hello`.
|
||||
let bitrate_auto = hello.bitrate_kbps == 0;
|
||||
// PyroWave is Automatic unconditionally (`resolve_bitrate_kbps_for` overrode any
|
||||
// explicit rate — RFC §5.2).
|
||||
let bitrate_auto = hello.bitrate_kbps == 0 || codec == crate::encode::Codec::PyroWave;
|
||||
let trace = bringup.clone();
|
||||
std::thread::Builder::new()
|
||||
.name("punktfunk1-stream".into())
|
||||
|
||||
@@ -1762,8 +1762,8 @@ pub(super) fn virtual_stream(ctx: SessionContext, prepared: Option<PreparedDispl
|
||||
instead of building twice"
|
||||
);
|
||||
mode = m;
|
||||
// Mirror the loop's rebuild: PyroWave's Automatic bitrate is a per-mode ~1.6 bpp pin, so
|
||||
// a resolution change moves the operating point. Explicit client rates stay put.
|
||||
// Mirror the loop's rebuild: PyroWave's bitrate is a per-mode ~1.6 bpp pin, so a
|
||||
// resolution change moves the operating point (PyroWave is always Automatic — RFC §5.2).
|
||||
if bitrate_auto && plan.codec == crate::encode::Codec::PyroWave {
|
||||
bitrate_kbps =
|
||||
resolve_bitrate_kbps_for(plan.codec, 0, &mode, plan.chroma, plan.bit_depth);
|
||||
@@ -2512,10 +2512,10 @@ pub(super) fn virtual_stream(ctx: SessionContext, prepared: Option<PreparedDispl
|
||||
// new-mode frame — `build_pipeline` waits for it). Total lands in the shared
|
||||
// `resize_ms` slot (→ `session_status`); a failed rebuild abandons it silently.
|
||||
let resize_trace = crate::bringup::Trace::start("resize", resize_ms.clone());
|
||||
// PyroWave's Automatic bitrate is a per-mode ~1.6 bpp pin (resolve_bitrate_kbps_for) —
|
||||
// a resolution change moves the operating point (1080p→4K quadruples the pixel rate),
|
||||
// so re-resolve it for the new mode. Explicit client rates stay put (the operator knows
|
||||
// the link), and the H.26x codecs keep their mode-independent rate (ABR owns it).
|
||||
// PyroWave's bitrate is a per-mode ~1.6 bpp pin (resolve_bitrate_kbps_for) — a
|
||||
// resolution change moves the operating point (1080p→4K quadruples the pixel rate),
|
||||
// so re-resolve it for the new mode (PyroWave is always Automatic — RFC §5.2). The
|
||||
// H.26x codecs keep their mode-independent rate (ABR owns it).
|
||||
let mode_bitrate = if bitrate_auto && plan.codec == crate::encode::Codec::PyroWave {
|
||||
resolve_bitrate_kbps_for(plan.codec, 0, &new_mode, plan.chroma, plan.bit_depth)
|
||||
} else {
|
||||
|
||||
@@ -50,8 +50,10 @@ probe about two seconds in that lets the rate climb past 20 Mbps. An explicit ra
|
||||
session, clamped to **500 kbps – 8 Gbps**. A host card's menu has **Test network speed…** to suggest
|
||||
a value.
|
||||
|
||||
PyroWave has no useful low-rate regime: its Automatic is a fixed per-pixel budget for the negotiated
|
||||
mode (hundreds of Mbps), with adaptive bitrate and the probe off for the whole session.
|
||||
PyroWave is **always Automatic**: the rate is a fixed per-pixel budget for the negotiated mode
|
||||
(hundreds of Mbps), with adaptive bitrate and the probe off for the whole session. A fixed kbps
|
||||
is meaningless for the all-intra codec, so the bitrate setting is disabled while PyroWave is
|
||||
selected — your stored value is kept, and picking another codec restores it.
|
||||
|
||||
**Render scale** — *default: Native (1×).* The host renders and encodes at your mode times this;
|
||||
your device resamples to its window. Above 1× supersamples at more bandwidth and decode work; below
|
||||
@@ -298,7 +300,7 @@ exactly [what a profile can't change](/docs/profiles-and-links#what-a-profile-ca
|
||||
| You ask for | What the host does |
|
||||
|---|---|
|
||||
| Resolution and refresh | Builds a display at exactly that mode. A host pinned to a real monitor keeps that monitor's resolution and you scale locally. A size the encoder can't take — odd, or past the codec's per-axis limit — fails the connect rather than being quietly changed. |
|
||||
| A bitrate | Clamps it to 500 kbps – 8 Gbps, or uses its 20 Mbps default for Automatic (a per-pixel budget for Automatic PyroWave). |
|
||||
| A bitrate | Clamps it to 500 kbps – 8 Gbps, or uses its 20 Mbps default for Automatic. PyroWave ignores the number entirely — every PyroWave session gets the per-pixel budget. |
|
||||
| A codec | Honors it when it can encode it, else the best shared codec in the order HEVC → AV1 → H.264. |
|
||||
| 10-bit HDR | Upgrades only for HDR content on an encoder that can do 10-bit; otherwise 8-bit SDR. |
|
||||
| 4:4:4 chroma | Sends it only when every gate passes; otherwise 4:2:0. |
|
||||
|
||||
@@ -133,7 +133,7 @@ See your desktop page ([KDE](/docs/kde), [GNOME](/docs/gnome)) for when to set t
|
||||
| `PUNKTFUNK_10BIT` | `1` · `0` *(default on)* | Allow 10-bit (HEVC Main10 / AV1 10-bit) sessions at all; `0` forces every session to 8-bit SDR. Which hosts can actually deliver it, and the client half of the switch, are on [HDR](/docs/hdr). |
|
||||
| `PUNKTFUNK_444` | `1` · `0` *(default on)* | Host **policy gate** for full chroma 4:4:4 — sharper text and thin lines, no chroma loss. **On by default**; `0` forces every session to 4:2:0. It only ever *allows*: the client's own 4:4:4 setting (default off) is the real per-session switch, and the codec, capture-path and GPU gates behind it are on [Client settings → Full chroma](/docs/client-settings#video). Which GPUs and which clients can actually do it is in the [support matrix](/docs/support-matrix#encoders); how it interacts with HDR is on [HDR](/docs/hdr). **punktfunk/1 native only** — Moonlight stays 4:2:0. |
|
||||
| `PUNKTFUNK_CHACHA20` | `1` · `0` *(default on)* | ChaCha20-Poly1305 session encryption for clients without hardware AES (old ARM TVs, e.g. webOS), lifting their ~100 Mbps software-AES decrypt ceiling. **On by default** on the host; a session uses it only when the client requests it — everyone else stays on AES-GCM. Purely a performance choice (both ciphers are full-strength); set `0` to force AES-GCM for all sessions. |
|
||||
| `PUNKTFUNK_PYROWAVE_MAX_MBPS` | `N` (Mbps) | Cap the [PyroWave](/docs/pyrowave) Automatic bitrate pin, for a host on a link that the open-loop pin can outrun (e.g. 4:4:4 + HDR at 5120×1440@240 pins ~5.3 Gbps, over a 5GbE link). Unset = no cap. Only affects Automatic (bitrate `0`) PyroWave sessions; an explicit client bitrate bypasses it. |
|
||||
| `PUNKTFUNK_PYROWAVE_MAX_MBPS` | `N` (Mbps) | Cap the [PyroWave](/docs/pyrowave) Automatic bitrate pin, for a host on a link that the open-loop pin can outrun (e.g. 4:4:4 + HDR at 5120×1440@240 pins ~5.3 Gbps, over a 5GbE link). Unset = no cap. Applies to every PyroWave session — a client-requested bitrate is treated as Automatic under PyroWave, so nothing bypasses the ceiling. |
|
||||
| `PUNKTFUNK_DSCP` | `1` | Opt-in DSCP / `SO_PRIORITY` QoS tagging on the media sockets. No-op on the wire on Windows without a qWAVE policy. |
|
||||
| `PUNKTFUNK_OH264_THREADS` / `PUNKTFUNK_OH264_GOP` | `N` | Software (openh264) encoder tuning: encode threads (default 2 — latency over throughput) and GOP length in frames (unset = about ten minutes' worth, `fps × 600`; set `0` for encoder-auto). Only relevant with `PUNKTFUNK_ENCODER=software`. |
|
||||
| `PUNKTFUNK_MAX_FPS` | `N` (fps) *(default: no limit)* | **Frame limiter for the game** — how fast the compositor lets it render. It does *not* cap the stream: the client still negotiates and receives its full rate, because the encode loop re-encodes the held frame whenever the compositor produced no new one (an almost-empty P-frame). A 60-capped game on a 120 Hz session still sends 120 frames a second, and the GPU time the game gives up goes to capture and encode instead — and to heat and battery on a laptop or handheld. **gamescope only today**: it takes this as `--nested-refresh`, the rate it clamps the game to; that is the nested output's rate, so everything gamescope composites moves at it. Other compositors have no equivalent lever and ignore it. ⚠️ On gamescope that one number is also the refresh the session **reports**: Steam's in-session display settings and every game will read the display as `N` Hz, and a game that paces itself to the display will hold itself there. If you want a quieter box without games believing the panel changed, cap the client's requested refresh instead. |
|
||||
|
||||
Reference in New Issue
Block a user