Skip to main content

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

1use std::collections::BTreeSet;
2
3use o_sfu_router::MediaKind;
4
5use super::super::input::SourcePolicySnapshot;
6use crate::{
7    Bitrate,
8    engine::{
9        UserId, VideoLayoutIntent,
10        media_transport::TransportConsumerRoute,
11        room::{
12            media_graph::{PendingUpgrade, SubscriptionKey},
13            state::RoomState,
14        },
15        source_model::{
16            ConsumerSourceSelection, PublishedSourceDescriptor, SourceAdaptationPolicy,
17            SourceRoomPolicySelector,
18        },
19    },
20};
21
22pub(super) fn receiver_video_routes<'a>(
23    state: &RoomState,
24    input: &SourcePolicySnapshot<'a>,
25) -> Vec<ReceiverVideoRouteInput<'a>> {
26    let mut routes = Vec::with_capacity(input.routes.len());
27    for route in &input.routes {
28        let source = &route.source.descriptor;
29        if source.media_kind() != MediaKind::Video
30            || (source.policy().adaptation() == SourceAdaptationPolicy::None
31                && source.policy().video_bitrate_cap().is_none())
32        {
33            continue;
34        }
35        let layout_role = state.receiver_video_layout_role(
36            &route.key.receiver,
37            source,
38            &input.featured_source_user_ids,
39        );
40        routes.push(ReceiverVideoRouteInput {
41            user_count: input.user_count,
42            source,
43            key: route.key,
44            route: route.route,
45            current_selection: route.selection,
46            pending_upgrade: route.pending_upgrade,
47            layout_role,
48            visible_scalable_route_count: 1,
49            active_speaker_rank: input
50                .active_speaker_rank_by_user
51                .get(source.owner().user_id())
52                .copied(),
53            receiver_bandwidth: input
54                .receiver_bandwidth_by_connection
55                .get(&route.route.consumer_session_key().connection_id())
56                .copied(),
57            source_bitrate: input
58                .source_bitrate_by_media
59                .get(&route.route.source_transport_media_id())
60                .copied(),
61            audio_budget_reserve: input
62                .audio_reserve_by_connection
63                .get(&route.route.consumer_session_key().connection_id())
64                .copied()
65                .unwrap_or_else(Bitrate::zero),
66        });
67    }
68    for receiver_routes in
69        routes.chunk_by_mut(|left, right| left.key.receiver == right.key.receiver)
70    {
71        let visible_scalable_route_count = receiver_routes
72            .iter()
73            .filter(|route| {
74                route.source.policy().adaptation() == SourceAdaptationPolicy::ScalableVideo
75                    && route.layout_role.counts_toward_visible_budget()
76            })
77            .count()
78            .max(1);
79        for route in receiver_routes {
80            route.visible_scalable_route_count = visible_scalable_route_count;
81        }
82    }
83    routes
84}
85
86/// Video route with adaptation or a source bitrate cap.
87#[derive(Debug)]
88pub(super) struct ReceiverVideoRouteInput<'a> {
89    pub(super) user_count: usize,
90    pub(super) source: &'a PublishedSourceDescriptor,
91    pub(super) key: &'a SubscriptionKey,
92    pub(super) route: &'a TransportConsumerRoute,
93    pub(super) current_selection: ConsumerSourceSelection,
94    pub(super) pending_upgrade: Option<&'a PendingUpgrade>,
95    pub(super) layout_role: SourceRoomPolicySelector,
96    pub(super) visible_scalable_route_count: usize,
97    pub(super) active_speaker_rank: Option<usize>,
98    pub(super) receiver_bandwidth: Option<Bitrate>,
99    pub(super) source_bitrate: Option<Bitrate>,
100    pub(super) audio_budget_reserve: Bitrate,
101}
102
103impl RoomState {
104    #[must_use]
105    pub(in crate::engine::room) fn receiver_video_layout_role(
106        &self,
107        consumer_user_id: &UserId,
108        source: &PublishedSourceDescriptor,
109        active_speaker_source_user_ids: &BTreeSet<UserId>,
110    ) -> SourceRoomPolicySelector {
111        let preference = layout_preference(self, consumer_user_id, source);
112        source
113            .policy()
114            .layout()
115            .map_or(SourceRoomPolicySelector::Hidden, |policy| {
116                policy.resolve(
117                    preference,
118                    active_speaker_source_user_ids.contains(source.owner().user_id()),
119                )
120            })
121    }
122
123    #[must_use]
124    pub(in crate::engine::room) fn diagnostics_video_layout_role(
125        &self,
126        consumer_user_id: &UserId,
127        source: &PublishedSourceDescriptor,
128    ) -> Option<SourceRoomPolicySelector> {
129        let policy = source.policy().layout()?;
130        let preference = layout_preference(self, consumer_user_id, source);
131        let active_speaker = self
132            .users
133            .get(source.owner().user_id())
134            .is_some_and(|user| user.featured() == Some(true));
135        Some(policy.resolve(preference, active_speaker))
136    }
137}
138
139fn layout_preference(
140    state: &RoomState,
141    consumer_user_id: &UserId,
142    source: &PublishedSourceDescriptor,
143) -> Option<VideoLayoutIntent> {
144    state
145        .topology
146        .subscription_intent(&SubscriptionKey::new(
147            consumer_user_id,
148            source.owner().user_id(),
149            source.stream_id(),
150        ))
151        .layout()
152}