1use std::{cmp::Reverse, collections::BTreeMap};
7
8use itertools::Itertools;
9
10use super::{
11 super::{input::SourcePolicySnapshot, turn::SourcePolicyTransaction},
12 input::{ReceiverVideoRouteInput, receiver_video_routes},
13 projection,
14};
15use crate::{
16 Bitrate, VideoAdaptationTuning,
17 engine::{
18 UserId,
19 media_transport::ReceiverBweTargetUpdate,
20 room::state::RoomState,
21 source_model::{
22 ConsumerSourceSelection, PolicyPauseReason, PublishedSourceDescriptor,
23 PublishedSourceId, ReceiverVideoBudgetDiagnostics, SourceAdaptationPolicy,
24 SourceEncodingDescriptor, SourceRoomPolicySelector, SourceRoutePriority,
25 SourceSelector, UploadLayerPolicyRole,
26 },
27 },
28};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
32pub struct VideoAdmissionRank {
33 priority: u8,
35 active_speaker_rank: usize,
37 source_id: u64,
39}
40
41impl VideoAdmissionRank {
42 pub const fn new(
43 priority: SourceRoutePriority,
44 active_speaker_rank: Option<usize>,
45 source_id: PublishedSourceId,
46 ) -> Self {
47 Self {
48 priority: match priority {
49 SourceRoutePriority::PinnedOrFeatured => 0,
50 SourceRoutePriority::ReadableDetail => 1,
51 SourceRoutePriority::ActiveSpeaker => 2,
52 SourceRoutePriority::VisibleThumbnail => 3,
53 SourceRoutePriority::HiddenOrOverflow => 4,
54 },
55 active_speaker_rank: match active_speaker_rank {
56 Some(rank) => rank,
57 None => usize::MAX,
58 },
59 source_id: source_id.as_u64(),
60 }
61 }
62}
63
64#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
66pub(super) struct AdaptationCounts {
67 pub(super) pressure: u8,
69 pub(super) upgrade: u8,
71}
72
73impl AdaptationCounts {
74 const fn reset() -> Self {
75 Self {
76 pressure: 0,
77 upgrade: 0,
78 }
79 }
80
81 pub(super) fn from_current(selection: ConsumerSourceSelection) -> Self {
82 Self {
83 pressure: selection.pressure_observations(),
84 upgrade: selection.upgrade_observations(),
85 }
86 }
87
88 fn next_pressure(selection: ConsumerSourceSelection, limit: u8) -> Self {
89 Self {
90 pressure: selection
91 .pressure_observations()
92 .saturating_add(1)
93 .min(limit),
94 upgrade: 0,
95 }
96 }
97
98 fn next_upgrade(selection: ConsumerSourceSelection, limit: u8) -> Self {
99 Self {
100 pressure: 0,
101 upgrade: selection
102 .upgrade_observations()
103 .saturating_add(1)
104 .min(limit),
105 }
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub(super) struct ReceiverRouteSelection {
112 pub(super) selector: SourceSelector,
114 pub(super) policy_pause_reason: Option<PolicyPauseReason>,
116 pub(super) counts: AdaptationCounts,
118 pub(super) request_keyframe: bool,
120}
121
122impl ReceiverRouteSelection {
123 const fn send(
124 selector: SourceSelector,
125 counts: AdaptationCounts,
126 request_keyframe: bool,
127 ) -> Self {
128 Self {
129 selector,
130 policy_pause_reason: None,
131 counts,
132 request_keyframe,
133 }
134 }
135
136 const fn pause(
137 current: ConsumerSourceSelection,
138 reason: PolicyPauseReason,
139 counts: AdaptationCounts,
140 ) -> Self {
141 Self {
142 selector: current.selector(),
143 policy_pause_reason: Some(reason),
144 counts,
145 request_keyframe: false,
146 }
147 }
148
149 const fn hold(
150 current: ConsumerSourceSelection,
151 policy_pause_reason: Option<PolicyPauseReason>,
152 counts: AdaptationCounts,
153 ) -> Self {
154 Self {
155 selector: current.selector(),
156 policy_pause_reason,
157 counts,
158 request_keyframe: false,
159 }
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub(super) enum RouteOutcome {
166 Neutral,
168 Degraded,
170 Paused,
172}
173
174#[derive(Debug, Clone, Copy)]
179pub(super) struct PlannedReceiverRoute<'a> {
180 pub(super) input: &'a ReceiverVideoRouteInput<'a>,
182 pub(super) selected_bitrate: Bitrate,
184 pub(super) selection: ReceiverRouteSelection,
186 pub(super) outcome: RouteOutcome,
188}
189
190impl<'a> PlannedReceiverRoute<'a> {
191 fn new(input: &'a ReceiverVideoRouteInput<'a>, selection: ReceiverRouteSelection) -> Self {
192 let selected_bitrate = if selection.policy_pause_reason.is_some() {
193 Bitrate::zero()
194 } else {
195 selector_bitrate(input, selection.selector).unwrap_or_default()
196 };
197 let current_bitrate =
198 selector_bitrate(input, input.current_selection.selector()).unwrap_or_default();
199 let outcome = if selected_bitrate < current_bitrate {
200 RouteOutcome::Degraded
201 } else {
202 RouteOutcome::Neutral
203 };
204 Self {
205 input,
206 selected_bitrate,
207 selection,
208 outcome,
209 }
210 }
211
212 fn send(&mut self, selector: SourceSelector, selected_bitrate: Bitrate, outcome: RouteOutcome) {
213 self.selected_bitrate = selected_bitrate;
214 self.selection = ReceiverRouteSelection::send(
215 selector,
216 self.selection.counts,
217 self.selection.request_keyframe,
218 );
219 self.outcome = outcome;
220 }
221
222 fn pause(&mut self, reason: PolicyPauseReason, outcome: RouteOutcome) {
223 self.selected_bitrate = Bitrate::zero();
224 self.selection = ReceiverRouteSelection::pause(
225 self.input.current_selection,
226 reason,
227 self.selection.counts,
228 );
229 self.outcome = outcome;
230 }
231}
232
233pub(in crate::engine::room::source_policy) fn append_receiver_video_policy(
234 tx: &mut SourcePolicyTransaction,
235 state: &RoomState,
236 input: &SourcePolicySnapshot<'_>,
237 mut receiver_bwe_targets: BTreeMap<UserId, ReceiverBweTargetUpdate>,
238) {
239 let routes = receiver_video_routes(state, input);
240 let max_video_downloads_per_receiver = input.media_limits.max_video_downloads_per_receiver();
241 let tuning = input.video_adaptation_tuning;
242 for receiver_routes in routes.chunk_by(|left, right| left.key.receiver == right.key.receiver) {
246 let Some(first_route) = receiver_routes.first() else {
247 continue;
248 };
249 let consumer_user_id = &first_route.key.receiver;
250 let selected_video = append_receiver_policy_updates(
251 tx,
252 receiver_routes,
253 max_video_downloads_per_receiver,
254 tuning,
255 );
256 if let Some(update) = receiver_bwe_targets.get_mut(consumer_user_id) {
259 update.set_target(update.target().saturating_add(selected_video));
260 }
261 }
262 tx.set_receiver_bwe_targets(receiver_bwe_targets.into_values().collect());
263}
264
265fn append_receiver_policy_updates<'a>(
327 tx: &mut SourcePolicyTransaction,
328 receiver_routes: &'a [ReceiverVideoRouteInput<'a>],
329 max_video_downloads_per_receiver: usize,
330 tuning: VideoAdaptationTuning,
331) -> Bitrate {
332 let receiver_bandwidth = receiver_routes
333 .iter()
334 .find_map(|route| route.receiver_bandwidth);
335 let audio_reserve = receiver_routes
336 .first()
337 .map_or_else(Bitrate::zero, |route| route.audio_budget_reserve);
338 let video_budget = receiver_bandwidth
339 .map(|bandwidth| effective_video_budget(bandwidth, tuning, audio_reserve));
340 let mut planned_routes = receiver_routes
341 .iter()
342 .filter_map(|route| {
343 route_plan(route, tuning).map(|selection| PlannedReceiverRoute::new(route, selection))
344 })
345 .collect::<Vec<_>>();
346 apply_video_download_limit(&mut planned_routes, max_video_downloads_per_receiver);
349 if let Some(video_budget) = video_budget {
350 apply_overload_policy(&mut planned_routes, video_budget);
351 }
352 let budget_diagnostics =
353 receiver_video_budget_diagnostics(&planned_routes, receiver_bandwidth, video_budget);
354 for planned_route in planned_routes {
355 let selection = resolve_hysteresis(&planned_route, tuning);
356 let Some(update) = projection::consumer_packet_selection_update(
357 &planned_route,
358 selection,
359 budget_diagnostics,
360 ) else {
361 continue;
362 };
363 if update.requires_media_transport_effect() {
364 tx.push_route_update(update);
365 } else {
366 tx.push_state_update(update);
367 }
368 }
369 budget_diagnostics.selected_video_bitrate()
370}
371
372fn route_plan(
373 route: &ReceiverVideoRouteInput<'_>,
374 tuning: VideoAdaptationTuning,
375) -> Option<ReceiverRouteSelection> {
376 let policy = route.source.policy().adaptation();
377 let source_cap = route.source.policy().video_bitrate_cap();
378 if source_cap.is_some_and(|cap| source_exceeds_bitrate_cap(route, cap)) {
379 return Some(ReceiverRouteSelection::pause(
380 route.current_selection,
381 PolicyPauseReason::SourceBitrateLimit,
382 AdaptationCounts::reset(),
383 ));
384 }
385 let selection = match policy {
386 SourceAdaptationPolicy::ReadableDetail
387 if route.source.selectable_encoding_count() < 2 && source_cap.is_none() =>
388 {
389 None
390 }
391 SourceAdaptationPolicy::ReadableDetail => highest_allowed_plan(route),
392 SourceAdaptationPolicy::ScalableVideo => scalable_plan(route, tuning),
393 SourceAdaptationPolicy::None if source_cap.is_some() => highest_allowed_plan(route),
394 SourceAdaptationPolicy::None => None,
395 }
396 .or_else(|| match policy {
397 SourceAdaptationPolicy::None if source_cap.is_none() => None,
398 _ if source_cap.is_some() => Some(ReceiverRouteSelection::pause(
399 route.current_selection,
400 PolicyPauseReason::SourceBitrateLimit,
401 AdaptationCounts::reset(),
402 )),
403 _ => Some(adaptation_hold(
404 route.current_selection,
405 AdaptationCounts::from_current(route.current_selection),
406 )),
407 })?;
408 if selection.policy_pause_reason.is_none()
409 && selector_bitrate(route, selection.selector).is_none()
410 {
411 return Some(ReceiverRouteSelection::pause(
412 route.current_selection,
413 PolicyPauseReason::MissingUsableLayer,
414 AdaptationCounts::reset(),
415 ));
416 }
417 Some(selection)
418}
419
420fn source_exceeds_bitrate_cap(route: &ReceiverVideoRouteInput<'_>, cap: Bitrate) -> bool {
421 if route.source.selectable_encoding_count() > 1 {
422 return false;
423 }
424 route.source_bitrate.map_or_else(
427 || {
428 route.current_selection.policy_pause_reason()
429 == Some(PolicyPauseReason::SourceBitrateLimit)
430 },
431 |observed| observed > cap,
432 )
433}
434
435fn selector_bitrate(
436 route: &ReceiverVideoRouteInput<'_>,
437 selector: SourceSelector,
438) -> Option<Bitrate> {
439 let source = route.source;
440 let declared = selector.selected_encoding().map_or_else(
441 || {
442 source
443 .selectable_encodings()
444 .filter_map(SourceEncodingDescriptor::max_bitrate)
445 .max()
446 },
447 |encoding_id| {
448 source
449 .selectable_encodings()
450 .find(|encoding| encoding.encoding_id() == encoding_id)
451 .and_then(SourceEncodingDescriptor::max_bitrate)
452 },
453 );
454 let observed = route
457 .source_bitrate
458 .filter(|_| source.selectable_encoding_count() <= 1);
459 declared.max(observed)
460}
461
462fn highest_allowed_plan(route: &ReceiverVideoRouteInput<'_>) -> Option<ReceiverRouteSelection> {
463 let source_cap = route.source.policy().video_bitrate_cap();
464 if route.source.selectable_encoding_count() == 0 {
465 return source_cap
466 .is_none_or(|cap| route.source_bitrate.is_some_and(|rate| rate <= cap))
467 .then(|| adaptation_send(SourceSelector::Open, false));
468 }
469 let target_index = allowed_encoding_indices(route.source, source_cap)
470 .next_back()
471 .or_else(|| {
472 (route.source.selectable_encoding_count() == 1
473 && source_cap
474 .is_some_and(|cap| route.source_bitrate.is_some_and(|rate| rate <= cap)))
475 .then_some(0)
476 })?;
477 let target_selector = SourceSelector::Encoding(
478 route
479 .source
480 .selectable_encoding_by_rank(target_index)?
481 .encoding_id(),
482 );
483 Some(adaptation_send(
484 target_selector,
485 target_selector != route.current_selection.selector(),
486 ))
487}
488
489fn cheapest_useful_selector(
490 route: &ReceiverVideoRouteInput<'_>,
491) -> Option<(SourceSelector, Bitrate)> {
492 let source_cap = route.source.policy().video_bitrate_cap();
493 route
494 .source
495 .selectable_encodings()
496 .filter(|encoding| {
497 !matches!(
498 encoding.policy_role(),
499 Some(UploadLayerPolicyRole::Featured)
500 )
501 })
502 .chain(route.source.selectable_encodings())
503 .find_map(|encoding| {
504 let bitrate = encoding.max_bitrate()?;
505 source_cap
506 .is_none_or(|source_cap| bitrate <= source_cap)
507 .then_some((SourceSelector::Encoding(encoding.encoding_id()), bitrate))
508 })
509}
510
511fn effective_video_budget(
514 receiver_bandwidth: Bitrate,
515 tuning: VideoAdaptationTuning,
516 audio_reserve: Bitrate,
517) -> Bitrate {
518 let usable_percent = 100u64.saturating_sub(u64::from(tuning.receiver_budget_headroom_percent));
519 let after_overhead =
520 Bitrate::from_bps(receiver_bandwidth.as_bps().saturating_mul(usable_percent) / 100);
521 after_overhead.saturating_sub(audio_reserve)
522}
523
524fn step_down_selector(
529 route: &ReceiverVideoRouteInput<'_>,
530 current: SourceSelector,
531) -> Option<(SourceSelector, Bitrate)> {
532 let source = route.source;
533 let source_cap = source.policy().video_bitrate_cap();
534 let current_index = selector_index(source, current);
535 (0..current_index).rev().find_map(|index| {
536 let encoding = source.selectable_encoding_by_rank(index)?;
537 if matches!(
538 encoding.policy_role(),
539 Some(UploadLayerPolicyRole::Featured)
540 ) {
541 return None;
542 }
543 let bitrate = encoding.max_bitrate()?;
544 source_cap
545 .is_none_or(|cap| bitrate <= cap)
546 .then_some((SourceSelector::Encoding(encoding.encoding_id()), bitrate))
547 })
548}
549
550fn scalable_plan(
551 route: &ReceiverVideoRouteInput<'_>,
552 tuning: VideoAdaptationTuning,
553) -> Option<ReceiverRouteSelection> {
554 let source_cap = route.source.policy().video_bitrate_cap();
555 if route.source.selectable_encoding_count() < 2 && source_cap.is_none() {
556 return None;
557 }
558 let current = route.current_selection;
559 let current_index = selector_index(route.source, current.selector());
560 let target_index = desired_encoding_index(route, source_cap, tuning)?;
561 let target_selector = SourceSelector::Encoding(
562 route
563 .source
564 .selectable_encoding_by_rank(target_index)?
565 .encoding_id(),
566 );
567 let request_keyframe = target_selector != current.selector();
568 if target_index == current_index || route.receiver_bandwidth.is_none() {
569 return Some(adaptation_send(target_selector, request_keyframe));
570 }
571 if target_index < current_index {
572 if source_cap.is_some_and(|cap| {
573 selector_bitrate(route, current.selector()).is_some_and(|bitrate| bitrate > cap)
574 }) {
575 return Some(adaptation_send(target_selector, request_keyframe));
576 }
577 let pressure_limit = tuning.downswitch_pressure_observations;
578 let counts = AdaptationCounts::next_pressure(current, pressure_limit);
579 if counts.pressure >= pressure_limit {
580 return Some(adaptation_send(target_selector, request_keyframe));
581 }
582 return Some(adaptation_hold(current, counts));
583 }
584 let stable_limit = tuning.upswitch_stable_observations;
585 let counts = AdaptationCounts::next_upgrade(current, stable_limit);
586 if counts.upgrade >= stable_limit {
587 return Some(adaptation_send(target_selector, true));
588 }
589 Some(adaptation_hold(current, counts))
590}
591
592const fn adaptation_send(
593 selector: SourceSelector,
594 request_keyframe: bool,
595) -> ReceiverRouteSelection {
596 ReceiverRouteSelection::send(selector, AdaptationCounts::reset(), request_keyframe)
597}
598
599const fn adaptation_hold(
600 current: ConsumerSourceSelection,
601 counts: AdaptationCounts,
602) -> ReceiverRouteSelection {
603 ReceiverRouteSelection::send(current.selector(), counts, false)
604}
605
606fn desired_encoding_index(
607 route: &ReceiverVideoRouteInput<'_>,
608 source_cap: Option<Bitrate>,
609 tuning: VideoAdaptationTuning,
610) -> Option<usize> {
611 if route.user_count < tuning.multiparty_scalable_video_threshold {
612 return allowed_encoding_indices(route.source, source_cap).next_back();
613 }
614 let uses_featured_quality = route.layout_role.uses_featured_quality();
615 let Some(receiver_bandwidth) = route.receiver_bandwidth else {
616 return if uses_featured_quality {
617 allowed_encoding_indices(route.source, source_cap).next_back()
618 } else {
619 allowed_encoding_indices(route.source, source_cap).next()
620 };
621 };
622 let receiver_bandwidth =
623 effective_video_budget(receiver_bandwidth, tuning, route.audio_budget_reserve);
624 let budget = if uses_featured_quality || route.visible_scalable_route_count <= 1 {
625 receiver_bandwidth
626 } else {
627 let divisor = u64::try_from(route.visible_scalable_route_count)
628 .unwrap_or(u64::MAX)
629 .saturating_mul(tuning.thumbnail_budget_divisor);
631 receiver_bandwidth.divided_by(divisor)
632 };
633 highest_affordable_encoding_index(route.source, budget, uses_featured_quality, source_cap)
634}
635
636fn highest_affordable_encoding_index(
637 source: &PublishedSourceDescriptor,
638 budget: Bitrate,
639 uses_featured_quality: bool,
640 source_cap: Option<Bitrate>,
641) -> Option<usize> {
642 if source
643 .selectable_encodings()
644 .all(|encoding| encoding.max_bitrate().is_none())
645 {
646 return source_cap.is_none().then_some(if uses_featured_quality {
647 source.selectable_encoding_count().saturating_sub(1)
648 } else {
649 0
650 });
651 }
652 allowed_encoding_indices(source, source_cap)
653 .rev()
654 .find_or_last(|index| {
655 source
656 .selectable_encoding_by_rank(*index)
657 .and_then(SourceEncodingDescriptor::max_bitrate)
658 .is_some_and(|bitrate| bitrate <= budget)
659 })
660}
661
662fn selector_index(source: &PublishedSourceDescriptor, selector: SourceSelector) -> usize {
663 selector
664 .selected_encoding()
665 .and_then(|encoding_id| {
666 source
667 .selectable_encodings()
668 .position(|encoding| encoding.encoding_id() == encoding_id)
669 })
670 .unwrap_or_else(|| source.selectable_encoding_count().saturating_sub(1))
671}
672
673fn allowed_encoding_indices(
674 source: &PublishedSourceDescriptor,
675 source_cap: Option<Bitrate>,
676) -> impl DoubleEndedIterator<Item = usize> + '_ {
677 (0..source.selectable_encoding_count()).filter(move |index| {
678 source_cap.is_none_or(|source_cap| {
679 source
680 .selectable_encoding_by_rank(*index)
681 .and_then(SourceEncodingDescriptor::max_bitrate)
682 .is_some_and(|bitrate| bitrate <= source_cap)
683 })
684 })
685}
686
687fn apply_video_download_limit(
704 routes: &mut [PlannedReceiverRoute<'_>],
705 max_video_downloads_per_receiver: usize,
706) {
707 let routes_to_pause =
708 active_route_count(routes).saturating_sub(max_video_downloads_per_receiver);
709 if routes_to_pause == 0 {
710 return;
711 }
712 for (_rank_key, route) in routes
713 .iter_mut()
714 .filter(|route| route.selection.policy_pause_reason.is_none())
715 .enumerate()
716 .map(|(position, route)| ((video_download_rank(route), position), route))
717 .k_largest_by_key(routes_to_pause, |(rank_key, _route)| *rank_key)
718 .rev()
719 {
720 route.pause(PolicyPauseReason::VideoDownloadLimit, RouteOutcome::Paused);
721 }
722}
723
724fn active_route_count(routes: &[PlannedReceiverRoute<'_>]) -> usize {
725 routes
726 .iter()
727 .filter(|route| route.selection.policy_pause_reason.is_none())
728 .count()
729}
730
731fn video_download_rank(route: &PlannedReceiverRoute<'_>) -> VideoAdmissionRank {
732 let input = route.input;
733 VideoAdmissionRank::new(
734 input.layout_role.priority(),
735 input.active_speaker_rank,
736 input.source.source_id(),
737 )
738}
739
740fn apply_overload_policy(routes: &mut [PlannedReceiverRoute<'_>], video_budget: Bitrate) {
741 let mut total_bitrate = selected_active_video_bitrate(routes);
742 if total_bitrate <= video_budget {
743 return;
744 }
745 let mut missing_ladders = routes
747 .iter_mut()
748 .filter(|route| {
749 route_can_downgrade(route) && cheapest_useful_selector(route.input).is_none()
750 })
751 .map(|route| (video_download_rank(route), route))
752 .collect::<Vec<_>>();
753 missing_ladders.sort_by_key(|(rank, _route)| Reverse(*rank));
754 for (_rank, route) in missing_ladders {
755 if total_bitrate <= video_budget {
756 break;
757 }
758 let selected_bitrate = route.selected_bitrate;
759 route.pause(PolicyPauseReason::MissingUsableLayer, RouteOutcome::Neutral);
760 total_bitrate = total_bitrate.saturating_sub(selected_bitrate);
761 }
762 while total_bitrate > video_budget {
765 let Some((route, selector, bitrate)) = routes
766 .iter_mut()
767 .filter(|route| route_can_downgrade(route))
768 .filter_map(|route| {
769 step_down_selector(route.input, route.selection.selector)
770 .map(|(selector, bitrate)| (route, selector, bitrate))
771 })
772 .max_by_key(|(route, _selector, _bitrate)| {
773 (video_download_rank(route), route.selected_bitrate)
774 })
775 else {
776 break;
777 };
778 let selected_bitrate = route.selected_bitrate;
779 total_bitrate = total_bitrate
780 .saturating_sub(selected_bitrate)
781 .saturating_add(bitrate);
782 route.send(selector, bitrate, RouteOutcome::Degraded);
783 }
784 if total_bitrate <= video_budget {
785 return;
786 }
787 let mut pause_order = Vec::with_capacity(routes.len());
788 for route in routes.iter_mut() {
789 if route.selection.policy_pause_reason.is_some() {
790 continue;
791 }
792 pause_order.push((video_download_rank(route), route));
793 }
794 pause_order.sort_by_key(|(rank, _)| Reverse(*rank));
795 for (_rank, route) in pause_order {
796 if total_bitrate <= video_budget {
797 break;
798 }
799 let selected_bitrate = route.selected_bitrate;
800 let pause_reason = pause_reason_for_route(route);
801 route.pause(pause_reason, RouteOutcome::Paused);
802 total_bitrate = total_bitrate.saturating_sub(selected_bitrate);
803 }
804}
805
806fn receiver_video_budget_diagnostics(
807 routes: &[PlannedReceiverRoute<'_>],
808 receiver_bandwidth: Option<Bitrate>,
809 video_budget: Option<Bitrate>,
810) -> ReceiverVideoBudgetDiagnostics {
811 let selected_video_bitrate = selected_active_video_bitrate(routes);
812 ReceiverVideoBudgetDiagnostics::new(
813 receiver_bandwidth,
814 video_budget,
815 active_route_count(routes),
816 selected_video_bitrate,
817 )
818}
819
820fn selected_active_video_bitrate(routes: &[PlannedReceiverRoute<'_>]) -> Bitrate {
821 routes
822 .iter()
823 .filter(|route| route.selection.policy_pause_reason.is_none())
824 .fold(Bitrate::zero(), |total, route| {
825 total.saturating_add(route.selected_bitrate)
826 })
827}
828
829fn route_can_downgrade(route: &PlannedReceiverRoute<'_>) -> bool {
830 let input = route.input;
831 route.selection.policy_pause_reason.is_none()
832 && input.source.policy().adaptation() == SourceAdaptationPolicy::ScalableVideo
833 && matches!(
834 input.layout_role.priority(),
835 SourceRoutePriority::VisibleThumbnail | SourceRoutePriority::HiddenOrOverflow
836 )
837}
838
839fn pause_reason_for_route(route: &PlannedReceiverRoute<'_>) -> PolicyPauseReason {
840 let role = route.input.layout_role;
841 match role.priority() {
842 SourceRoutePriority::HiddenOrOverflow => match role {
843 SourceRoomPolicySelector::Hidden => PolicyPauseReason::HiddenTile,
844 SourceRoomPolicySelector::Overflow => PolicyPauseReason::OverflowTile,
845 _ => PolicyPauseReason::BudgetPressure,
846 },
847 _ => PolicyPauseReason::BudgetPressure,
848 }
849}
850
851fn resolve_hysteresis(
852 route: &PlannedReceiverRoute<'_>,
853 tuning: VideoAdaptationTuning,
854) -> ReceiverRouteSelection {
855 let current = route.input.current_selection;
856 let current_pause_reason = current.policy_pause_reason();
857 let selection = route.selection;
858 match (selection.policy_pause_reason, current_pause_reason) {
859 (None, Some(PolicyPauseReason::VideoDownloadLimit)) => {
862 ReceiverRouteSelection::send(selection.selector, AdaptationCounts::reset(), true)
863 }
864 (None, Some(reason)) => {
865 let stable_limit = tuning.upswitch_stable_observations;
866 let counts = AdaptationCounts::next_upgrade(current, stable_limit);
867 if counts.upgrade >= stable_limit {
868 ReceiverRouteSelection::send(selection.selector, AdaptationCounts::reset(), true)
869 } else {
870 ReceiverRouteSelection::hold(current, Some(reason), counts)
871 }
872 }
873 (Some(reason), pause_reason) if pause_reason != Some(reason) => {
874 if matches!(
878 reason,
879 PolicyPauseReason::VideoDownloadLimit | PolicyPauseReason::SourceBitrateLimit
880 ) {
881 return ReceiverRouteSelection::pause(current, reason, AdaptationCounts::reset());
882 }
883 let pressure_limit = tuning.downswitch_pressure_observations;
884 let counts = AdaptationCounts::next_pressure(current, pressure_limit);
885 if counts.pressure >= pressure_limit {
886 ReceiverRouteSelection::pause(current, reason, AdaptationCounts::reset())
887 } else {
888 ReceiverRouteSelection::hold(current, None, counts)
889 }
890 }
891 _ => selection,
892 }
893}
894
895#[cfg(test)]
896#[path = "TESTS/solver.rs"]
897mod tests;