o_sfu_core/engine/room/source_policy/video/
input.rs1use 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#[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}