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