From 727893fdc2e3f037a52bd4f9db2985785c4f60b7 Mon Sep 17 00:00:00 2001 From: dailz Date: Sun, 21 Jun 2026 10:10:19 +0800 Subject: [PATCH] fix(webrtc): conservative resolution-aware startup bitrate (closes #21) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WebRTC mode now uses tier-based conservative defaults for initial encoder bitrate instead of the aggressive formula. BWE estimate arrives within milliseconds of client connect and overrides this; the startup value only affects the first IDR frame. Before (both modes used same formula): 5 * W * H * fps / 100 1440p@30fps = 5_529_600 bps (5.5 Mbps) 1440p@60fps = 11_059_200 bps (11 Mbps) 4K@30fps = 8_294_400 bps (8.3 Mbps) After (WebRTC uses conservative tier-based, MP4 keeps formula): fn webrtc_startup_bitrate_bps(width, height) -> u64: pixels <= 1_000_000 (720p): 1 Mbps pixels <= 2_500_000 (1080p): 2 Mbps pixels <= 4_500_000 (1440p): 4 Mbps else (4K+): 8 Mbps Why this is safe for WebRTC: 1. BWE_INITIAL = 5 Mbps in RtcConfig (webrtc.rs) 2. Client connect triggers BWE estimate within ~10ms 3. Encoder bitrate immediately updated via BitrateCommand::UpdateBitrate 4. First IDR frame is the only output affected by startup value 5. With #23's VBV buffer_size = bitrate/4, first IDR is bounded to ~170KB regardless of startup bitrate Why MP4 keeps the formula: MP4 mode has no BWE feedback channel. The formula provides reasonable quality for file output. Users who want specific bitrate can pass --bitrate. Resolution tiers chosen to match common display resolutions: 720p (1280x720 = 921_600 pixels) → 1 Mbps 1080p (1920x1080 = 2_073_600 pixels) → 2 Mbps 1440p (2560x1440 = 3_686_400 pixels) → 4 Mbps 4K (3840x2160 = 8_294_400 pixels) → 8 Mbps (= --max-bitrate cap) User-supplied --bitrate flag still takes precedence in both modes. Tests: - cargo build --release: 0 new warnings (19 baseline preserved) - cargo test: 97 lib + 3 integration, 0 failed - New webrtc_startup_bitrate_tiers_by_pixel_count test covers all 4 tiers - SAFETY comments preserved verbatim - 1 file changed --- src/state_portal.rs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/state_portal.rs b/src/state_portal.rs index fad2075..15eb327 100644 --- a/src/state_portal.rs +++ b/src/state_portal.rs @@ -198,9 +198,13 @@ impl StatePortal { enc_height, self.args.fps, ); - // 码率:未指定时按分辨率 × 帧率动态计算 + // 码率:WebRTC 模式用保守默认(BWE 连接后立即覆盖),MP4 用公式 let actual_bitrate = self.args.bitrate.unwrap_or_else(|| { - 5 * (enc_width as u64) * (enc_height as u64) * (self.args.fps as u64) / 100 + if self.webrtc.is_some() { + webrtc_startup_bitrate_bps(enc_width, enc_height) + } else { + 5 * (enc_width as u64) * (enc_height as u64) * (self.args.fps as u64) / 100 + } }); // GOP 大小:WebRTC 模式使用较大的 GOP(fps*2,最低20),MP4 模式使用 fps let actual_gop_size = self.args.gop_size.unwrap_or_else(|| { @@ -848,6 +852,22 @@ fn resolution_bitrate_bps(width: u32, height: u32, fps: u32) -> u64 { 5 * u64::from(width) * u64::from(height) * u64::from(fps) / 100 } +/// Conservative startup bitrate for WebRTC mode, tier-based by total pixel count. +/// BWE estimate arrives within milliseconds of client connect and overrides this; +/// the startup value only affects the first IDR. See issue #21. +fn webrtc_startup_bitrate_bps(width: u32, height: u32) -> u64 { + let pixels = u64::from(width) * u64::from(height); + if pixels <= 1_000_000 { + 1_000_000 + } else if pixels <= 2_500_000 { + 2_000_000 + } else if pixels <= 4_500_000 { + 4_000_000 + } else { + 8_000_000 + } +} + /// Select resolution tier based on BWE estimate. /// Returns (width, height) for the selected tier. fn select_resolution(current_w: u32, current_h: u32, bwe_bps: u64, fps: u32) -> (u32, u32) { @@ -1026,6 +1046,14 @@ mod tests { assert_eq!(result, None); } + #[test] + fn webrtc_startup_bitrate_tiers_by_pixel_count() { + assert_eq!(webrtc_startup_bitrate_bps(1280, 720), 1_000_000); + assert_eq!(webrtc_startup_bitrate_bps(1920, 1080), 2_000_000); + assert_eq!(webrtc_startup_bitrate_bps(2560, 1440), 4_000_000); + assert_eq!(webrtc_startup_bitrate_bps(3840, 2160), 8_000_000); + } + #[test] fn select_resolution_downscales_one_tier_below_sixty_percent() { let fps = 30;