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:
+29
-1
@@ -198,9 +198,13 @@ impl StatePortal {
|
||||
enc_height,
|
||||
self.args.fps,
|
||||
);
|
||||
// 码率:未指定时按分辨率 × 帧率动态计算
|
||||
// 码率:WebRTC 模式用保守默认(BWE 连接后立即覆盖),MP4 用公式
|
||||
let actual_bitrate = self.args.bitrate.unwrap_or_else(|| {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user