Skip to main content

o_sfu_core/engine/room/source_policy/video/
projection.rs

1use o_sfu_router::MediaKind;
2
3use super::{
4    super::action::{ConsumerPacketSelectionUpdate, RouteBudgetOutcome},
5    solver::{AdaptationCounts, PlannedReceiverRoute, ReceiverRouteSelection, RouteOutcome},
6};
7use crate::engine::{
8    media_transport::SourcePacketGate,
9    source_model::{PublishedSourceDescriptor, ReceiverVideoBudgetDiagnostics, SourceSelector},
10};
11
12/// Projects a room selector into a source-worker packet gate.
13///
14/// Returns `None` when the selected encoding is unknown or has no negotiated RID.
15pub(super) fn source_packet_gate_for_selector(
16    source: &PublishedSourceDescriptor,
17    selector: SourceSelector,
18) -> Option<SourcePacketGate> {
19    match selector {
20        SourceSelector::Open => Some(SourcePacketGate::Open),
21        SourceSelector::Encoding(encoding_id) => {
22            // Never fall back to `Open`: it would forward every encoding while
23            // room state records one selected encoding.
24            let encoding = source.encoding(encoding_id)?;
25            let rid = encoding.rid()?;
26            Some(SourcePacketGate::Rid(rid.as_str().to_owned()))
27        }
28    }
29}
30
31pub(super) fn consumer_packet_selection_update(
32    planned_route: &PlannedReceiverRoute<'_>,
33    selection: ReceiverRouteSelection,
34    budget: ReceiverVideoBudgetDiagnostics,
35) -> Option<ConsumerPacketSelectionUpdate> {
36    let input = planned_route.input;
37    let current_selection = input.current_selection;
38    if selection.selector == current_selection.selector()
39        && selection.policy_pause_reason == current_selection.policy_pause_reason()
40        && budget == current_selection.budget()
41        && selection.counts == AdaptationCounts::from_current(current_selection)
42    {
43        return None;
44    }
45    let packet_gate = if selection.selector == current_selection.selector() {
46        None
47    } else {
48        Some(source_packet_gate_for_selector(
49            input.source,
50            selection.selector,
51        )?)
52    };
53    let route_activity_changed =
54        selection.policy_pause_reason != current_selection.policy_pause_reason();
55    // A newly selected RID or resumed route may not share the receiver's last
56    // decodable reference chain, so request a keyframe for either transition.
57    let request_keyframe = selection.policy_pause_reason.is_none()
58        && (selection.request_keyframe
59            || selection.selector != current_selection.selector()
60            || !current_selection.policy_allows_delivery());
61    let outcome = route_outcome(planned_route, selection);
62    Some(ConsumerPacketSelectionUpdate {
63        key: input.key.clone(),
64        source_id: input.source.source_id(),
65        route: input.route.clone(),
66        selector: selection.selector,
67        policy_pause_reason: selection.policy_pause_reason,
68        budget,
69        outcome,
70        pressure_observations: selection.counts.pressure,
71        upgrade_observations: selection.counts.upgrade,
72        packet_gate,
73        route_activity_changed,
74        request_keyframe: request_keyframe && input.source.media_kind() == MediaKind::Video,
75    })
76}
77
78fn route_outcome(
79    route: &PlannedReceiverRoute<'_>,
80    selection: ReceiverRouteSelection,
81) -> Option<RouteBudgetOutcome> {
82    match (
83        selection.policy_pause_reason,
84        route.input.current_selection.policy_pause_reason(),
85    ) {
86        (None, Some(_reason)) => Some(RouteBudgetOutcome::Resumed),
87        (Some(reason), current_reason) if current_reason != Some(reason) => {
88            Some(RouteBudgetOutcome::Paused)
89        }
90        _ => match route.outcome {
91            RouteOutcome::Neutral => None,
92            RouteOutcome::Degraded => Some(RouteBudgetOutcome::Degraded),
93            RouteOutcome::Paused => Some(RouteBudgetOutcome::Paused),
94        },
95    }
96}