@@ -5,18 +5,12 @@ pub struct BitrateControllerConfig {
55 pub min_bitrate : Bitrate ,
66 pub max_bitrate : Bitrate ,
77 pub default_bitrate : Bitrate ,
8-
9- // Safety headroom (e.g. 0.85 of estimate)
108 pub headroom_factor : f64 ,
11-
12- // Immediate downgrade if target drops below this % of current (e.g. 0.90)
13- pub downgrade_threshold : f64 ,
14-
15- // Number of ticks to hold a higher target before committing
16- // Since input is already smoothed, 5-8 ticks is usually enough stability.
9+ // Max percentage to decrease per tick (e.g. 0.95 = 5% max drop)
10+ pub max_decay_factor : f64 ,
11+ // Drop immediately if target is below this % of current (e.g. 0.50)
12+ pub emergency_drop_threshold : f64 ,
1713 pub required_up_samples : usize ,
18-
19- // Step size to prevent micro-fluctuations (e.g. 50kbps)
2014 pub quantization_step : Bitrate ,
2115}
2216
@@ -27,8 +21,9 @@ impl Default for BitrateControllerConfig {
2721 max_bitrate : Bitrate :: mbps ( 5 ) ,
2822 default_bitrate : Bitrate :: kbps ( 300 ) ,
2923 headroom_factor : 1.0 ,
30- downgrade_threshold : 0.90 ,
31- required_up_samples : 1 ,
24+ max_decay_factor : 0.95 ,
25+ emergency_drop_threshold : 0.50 ,
26+ required_up_samples : 5 ,
3227 quantization_step : Bitrate :: kbps ( 10 ) ,
3328 }
3429 }
@@ -44,8 +39,7 @@ pub struct BitrateController {
4439
4540impl Default for BitrateController {
4641 fn default ( ) -> Self {
47- let config = BitrateControllerConfig :: default ( ) ;
48- Self :: new ( config)
42+ Self :: new ( BitrateControllerConfig :: default ( ) )
4943 }
5044}
5145
@@ -60,12 +54,9 @@ impl BitrateController {
6054 }
6155
6256 pub fn update ( & mut self , available_bandwidth : Bitrate ) -> Bitrate {
63- // 1. Give headroom
6457 let raw_bw = available_bandwidth. as_f64 ( ) ;
6558 let safe_bw = raw_bw * self . config . headroom_factor ;
6659
67- // 2. Quantize (Floor)
68- // This stabilizes the input "jitter" into steps
6960 let step = self . config . quantization_step . as_f64 ( ) ;
7061 let quantized_target = ( safe_bw / step) . floor ( ) * step;
7162
@@ -74,30 +65,23 @@ impl BitrateController {
7465 self . config . max_bitrate . as_f64 ( ) ,
7566 ) ;
7667
77- // 3. Logic
78-
79- // A: Immediate Downgrade
80- // If external BWE says we dropped significant bandwidth, believe it immediately.
81- if target < self . current_bitrate * self . config . downgrade_threshold {
82- self . current_bitrate = target;
68+ if target < self . current_bitrate {
8369 self . reset_stability ( ) ;
84- return self . current ( ) ;
85- }
8670
87- // B: Debounced Upgrade
88- // If target is higher, wait for N samples to confirm it's not a temporary spike.
89- if target > self . current_bitrate {
71+ if target < self . current_bitrate * self . config . emergency_drop_threshold {
72+ self . current_bitrate = target;
73+ } else {
74+ let decay_limit = self . current_bitrate * self . config . max_decay_factor ;
75+ self . current_bitrate = target. max ( decay_limit) ;
76+ }
77+ } else if target > self . current_bitrate {
9078 match self . pending_target {
9179 Some ( mut pending) => {
92- // Track the "Floor" of the new bandwidth during the window.
93- // If bandwidth dips during the wait, lower our expectations
94- // to that dip, but keep the counter running.
9580 if target < pending {
9681 pending = target;
9782 self . pending_target = Some ( pending) ;
9883 }
9984
100- // If the dip made it drop below current, the upgrade is invalid.
10185 if pending <= self . current_bitrate {
10286 self . reset_stability ( ) ;
10387 } else {
@@ -117,7 +101,6 @@ impl BitrateController {
117101 self . reset_stability ( ) ;
118102 }
119103 } else {
120- // Target is roughly equal or slightly below (within threshold). Stable.
121104 self . reset_stability ( ) ;
122105 }
123106
@@ -133,3 +116,97 @@ impl BitrateController {
133116 Bitrate :: from ( self . current_bitrate )
134117 }
135118}
119+
120+ #[ cfg( test) ]
121+ mod tests {
122+ use super :: * ;
123+
124+ #[ test]
125+ fn test_steady_state ( ) {
126+ let mut ctrl = BitrateController :: default ( ) ;
127+ let bw = Bitrate :: kbps ( 500 ) ;
128+
129+ // Stabilize at 500
130+ for _ in 0 ..10 {
131+ ctrl. update ( bw) ;
132+ }
133+ assert_eq ! ( ctrl. current( ) . as_f64( ) , 500_000.0 ) ;
134+ }
135+
136+ #[ test]
137+ fn test_transient_drop_filtering ( ) {
138+ let mut ctrl = BitrateController :: default ( ) ;
139+ // Start stable at 1000 kbps
140+ for _ in 0 ..10 {
141+ ctrl. update ( Bitrate :: kbps ( 1000 ) ) ;
142+ }
143+ assert_eq ! ( ctrl. current( ) . as_f64( ) , 1_000_000.0 ) ;
144+
145+ // One tick drop to 300 kbps (simulating bad estimate/jitter)
146+ // With decay 0.95, should only drop to ~950k, not 300k
147+ let res = ctrl. update ( Bitrate :: kbps ( 300 ) ) ;
148+ assert ! ( res. as_f64( ) > 900_000.0 ) ;
149+ assert ! ( res. as_f64( ) < 1_000_000.0 ) ;
150+
151+ // Input recovers immediately
152+ let res = ctrl. update ( Bitrate :: kbps ( 1000 ) ) ;
153+
154+ // Should climb back up (or stay high) rather than waiting for full debounce
155+ // because we never actually dropped low.
156+ assert ! ( res. as_f64( ) > 900_000.0 ) ;
157+ }
158+
159+ #[ test]
160+ fn test_real_congestion_decay ( ) {
161+ let mut ctrl = BitrateController :: default ( ) ;
162+ for _ in 0 ..10 {
163+ ctrl. update ( Bitrate :: kbps ( 1000 ) ) ;
164+ }
165+
166+ // Sustained drop to 500
167+ for _ in 0 ..10 {
168+ ctrl. update ( Bitrate :: kbps ( 500 ) ) ;
169+ }
170+
171+ // Should be sliding down
172+ let current = ctrl. current ( ) . as_f64 ( ) ;
173+ assert ! ( current < 1_000_000.0 ) ;
174+ assert ! ( current > 500_000.0 ) ;
175+ }
176+
177+ #[ test]
178+ fn test_emergency_drop ( ) {
179+ let mut ctrl = BitrateController :: default ( ) ;
180+ for _ in 0 ..10 {
181+ ctrl. update ( Bitrate :: kbps ( 2000 ) ) ;
182+ }
183+
184+ // Massive drop (e.g. WiFi loss), < 50%
185+ let res = ctrl. update ( Bitrate :: kbps ( 300 ) ) ;
186+
187+ // Should snap immediately
188+ assert_eq ! ( res. as_f64( ) , 300_000.0 ) ;
189+ }
190+
191+ #[ test]
192+ fn test_debounce_up ( ) {
193+ let config = BitrateControllerConfig {
194+ required_up_samples : 3 ,
195+ ..Default :: default ( )
196+ } ;
197+ let mut ctrl = BitrateController :: new ( config) ;
198+
199+ // Start 300
200+ ctrl. update ( Bitrate :: kbps ( 300 ) ) ;
201+
202+ // Jump to 500
203+ ctrl. update ( Bitrate :: kbps ( 500 ) ) ; // Tick 1 (Pending)
204+ assert_eq ! ( ctrl. current( ) . as_f64( ) , 300_000.0 ) ;
205+
206+ ctrl. update ( Bitrate :: kbps ( 500 ) ) ; // Tick 2
207+ assert_eq ! ( ctrl. current( ) . as_f64( ) , 300_000.0 ) ;
208+
209+ ctrl. update ( Bitrate :: kbps ( 500 ) ) ; // Tick 3 (Commit)
210+ assert_eq ! ( ctrl. current( ) . as_f64( ) , 500_000.0 ) ;
211+ }
212+ }
0 commit comments