fix(webrtc): conservative resolution-aware startup bitrate (closes #21)

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
This commit is contained in:
dailz
2026-06-21 10:10:19 +08:00
parent a06a41f5f2
commit 727893fdc2
+30 -2
View File
@@ -198,9 +198,13 @@ impl StatePortal {
enc_height, enc_height,
self.args.fps, self.args.fps,
); );
// 码率:未指定时按分辨率 × 帧率动态计算 // 码率:WebRTC 模式用保守默认(BWE 连接后立即覆盖),MP4 用公式
let actual_bitrate = self.args.bitrate.unwrap_or_else(|| { 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 模式使用较大的 GOPfps*2,最低20),MP4 模式使用 fps // GOP 大小:WebRTC 模式使用较大的 GOPfps*2,最低20),MP4 模式使用 fps
let actual_gop_size = self.args.gop_size.unwrap_or_else(|| { 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 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. /// Select resolution tier based on BWE estimate.
/// Returns (width, height) for the selected tier. /// Returns (width, height) for the selected tier.
fn select_resolution(current_w: u32, current_h: u32, bwe_bps: u64, fps: u32) -> (u32, u32) { 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); 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] #[test]
fn select_resolution_downscales_one_tier_below_sixty_percent() { fn select_resolution_downscales_one_tier_below_sixty_percent() {
let fps = 30; let fps = 30;