diff --git a/breadcast-core/src/caststream.rs b/breadcast-core/src/caststream.rs index de0aa42..da8fa48 100644 --- a/breadcast-core/src/caststream.rs +++ b/breadcast-core/src/caststream.rs @@ -52,12 +52,22 @@ impl Default for VideoParams { // encodes (`breadcast-core/src/pipeline/mod.rs`), not just what // we'd like to send -- this OFFER's resolution is what the // receiver allocates its decoder/output surface for. Advertising - // 1920x1080 while actually sending 1280x720 frames is a real + // a resolution other than what's actually sent is a real // protocol mismatch that plausibly explains a receiver decoder // corrupting/freezing rather than just looking soft. - width: 1280, - height: 720, - max_bitrate_bps: 8_000_000, + width: 1920, + height: 1080, + // Kept equal to `breadcastd::cast_mirror::MAX_BITRATE_KBPS * + // 1000` -- see that constant's doc comment for why 8 Mbps + // (this struct's previous value) isn't used here even though + // 1080p can look better with more headroom: real hardware + // testing showed the AIMD probe pinning to whatever this + // ceiling is for the entire session once the estimator reports + // (unreliably) that there's room, and 8 Mbps sustained was more + // than the previous network+receiver could actually hold, + // producing repeated multi-second freezes rather than just + // softer video. + max_bitrate_bps: 6_000_000, max_frame_rate_numerator: 30, max_frame_rate_denominator: 1, } diff --git a/breadcast-core/src/pipeline/mod.rs b/breadcast-core/src/pipeline/mod.rs index 0a6ce94..110fa38 100644 --- a/breadcast-core/src/pipeline/mod.rs +++ b/breadcast-core/src/pipeline/mod.rs @@ -150,12 +150,19 @@ pub fn build_video_pipeline_for_streaming( ) -> Result<(gst::Pipeline, gst_app::AppSink, gst::Element)> { gst::init().context("failed to initialize GStreamer")?; - // Same 1280x720@30 Main-profile baseline as build_video_pipeline, for - // the same reason (see its doc comment) -- broad decoder compatibility - // first, revisit upward once a specific device's real ceiling is known. + // 1920x1080@30 Main profile -- raised from the earlier 1280x720 + // baseline (kept in `build_video_pipeline`'s HLS path, which targets a + // different, less capable receiver -- see its doc comment) once real + // hardware testing showed the actual bottleneck on the *previous* + // network wasn't resolution but the encoder being driven well past + // what that link/receiver could sustain (see `MAX_BITRATE_KBPS` in + // `breadcastd::cast_mirror`). Must stay equal to `VideoParams::default` + // in `caststream.rs` -- the OFFER's advertised resolution and what's + // actually encoded disagreeing is a protocol-level mismatch, not just + // soft video (see that struct's doc comment for what that caused). let pipeline_str = "pipewiresrc path=%VIDEO_NODE_ID% do-timestamp=true ! \ videoconvert ! videoscale ! videorate ! \ - video/x-raw,format=NV12,width=1280,height=720,framerate=30/1 ! \ + video/x-raw,format=NV12,width=1920,height=1080,framerate=30/1 ! \ vah264enc name=venc bitrate=4000 key-int-max=60 rate-control=cbr ! \ video/x-h264,profile=main ! \ h264parse name=h264parse config-interval=-1 ! \ diff --git a/breadcastd/src/cast_mirror.rs b/breadcastd/src/cast_mirror.rs index c5ba59c..f251067 100644 --- a/breadcastd/src/cast_mirror.rs +++ b/breadcastd/src/cast_mirror.rs @@ -29,14 +29,23 @@ use crate::daemon::DaemonCommand; /// `vah264enc` with, since [`bitrate_control_step`] treats it as the value /// already in effect at t=0. const INITIAL_BITRATE_KBPS: u32 = 4000; -/// Never encode below this. 720p30 below roughly 1 Mbps is a wall of +/// Never encode below this. 1080p30 below roughly 1.5 Mbps is a wall of /// blocking artifacts -- if the link genuinely can't carry that, dropping /// frames is a better failure mode than shipping unwatchable video. -const MIN_BITRATE_KBPS: u32 = 1000; +const MIN_BITRATE_KBPS: u32 = 1500; /// Never encode above this, regardless of how much headroom the estimator /// reports. Matches `VideoParams::default().max_bitrate_bps`, i.e. what the -/// OFFER told the receiver to expect. -const MAX_BITRATE_KBPS: u32 = 8000; +/// OFFER told the receiver to expect -- see that constant's doc comment for +/// why this is 6 Mbps and not higher: real hardware testing showed the AIMD +/// probe below pinning to whatever this ceiling is for the *entire* session +/// (the estimator it trusts read a suspiciously flat ~20 Mbps almost the +/// whole time), and 8 Mbps sustained was more than the previous +/// network+receiver could actually hold without repeated multi-second +/// freezes. 6 Mbps is a solid target for 1080p30 on its own merits, not +/// just a defensive number -- revisit upward only with real evidence this +/// specific link+receiver can sustain more, not just because the estimator +/// claims there's headroom. +const MAX_BITRATE_KBPS: u32 = 6000; pub struct CastMirrorSession { pipeline: gst::Pipeline,