1use gosuto_libwebrtc::prelude::*;
16use livekit_protocol as proto;
17
18use crate::prelude::*;
19
20#[derive(Debug, Copy, Clone, PartialEq, Eq)]
21pub enum VideoCodec {
22 VP8,
23 H264,
24 VP9,
25 AV1,
26 H265,
27}
28
29impl VideoCodec {
30 pub fn as_str(&self) -> &'static str {
31 match self {
32 VideoCodec::VP8 => "vp8",
33 VideoCodec::H264 => "h264",
34 VideoCodec::VP9 => "vp9",
35 VideoCodec::AV1 => "av1",
36 VideoCodec::H265 => "h265",
37 }
38 }
39}
40
41#[derive(Debug, Clone)]
42pub struct VideoResolution {
43 pub width: u32,
44 pub height: u32,
45 pub frame_rate: f64,
46 pub aspect_ratio: f32,
47}
48
49#[derive(Debug, Clone)]
50pub struct VideoEncoding {
51 pub max_bitrate: u64,
52 pub max_framerate: f64,
53}
54
55#[derive(Debug, Clone)]
56pub struct VideoPreset {
57 pub encoding: VideoEncoding,
58 pub width: u32,
59 pub height: u32,
60}
61
62#[derive(Debug, Clone)]
63pub struct AudioEncoding {
64 pub max_bitrate: u64,
65}
66
67#[derive(Debug, Clone)]
68pub struct AudioPreset {
69 pub encoding: AudioEncoding,
70}
71
72impl AudioPreset {
73 pub const fn new(max_bitrate: u64) -> Self {
74 Self { encoding: AudioEncoding { max_bitrate } }
75 }
76}
77
78#[derive(Clone, Debug)]
79pub struct TrackPublishOptions {
80 pub video_encoding: Option<VideoEncoding>,
82 pub audio_encoding: Option<AudioEncoding>,
83 pub video_codec: VideoCodec,
84 pub dtx: bool,
85 pub red: bool,
86 pub simulcast: bool,
87 pub source: TrackSource,
89 pub stream: String,
90 pub preconnect_buffer: bool,
91}
92
93impl Default for TrackPublishOptions {
94 fn default() -> Self {
95 Self {
96 video_encoding: None,
97 audio_encoding: None,
98 video_codec: VideoCodec::VP8,
99 dtx: true,
100 red: true,
101 simulcast: true,
102 source: TrackSource::Unknown,
103 stream: "".to_string(),
104 preconnect_buffer: false,
105 }
106 }
107}
108
109impl VideoPreset {
110 pub const fn new(width: u32, height: u32, max_bitrate: u64, max_framerate: f64) -> Self {
111 Self { width, height, encoding: VideoEncoding { max_bitrate, max_framerate } }
112 }
113
114 pub fn resolution(&self) -> VideoResolution {
115 VideoResolution {
116 width: self.width,
117 height: self.height,
118 frame_rate: self.encoding.max_framerate,
119 aspect_ratio: self.width as f32 / self.height as f32,
120 }
121 }
122}
123
124pub fn compute_video_encodings(
127 width: u32,
128 height: u32,
129 options: &TrackPublishOptions,
130) -> Vec<RtpEncodingParameters> {
131 let screenshare = options.source == TrackSource::Screenshare;
132 let encoding = match options.video_encoding.clone() {
133 Some(encoding) => encoding,
134 None => compute_appropriate_encoding(screenshare, width, height, options.video_codec),
135 };
136
137 let initial_preset = VideoPreset {
138 width,
139 height,
140 encoding: VideoEncoding {
141 max_bitrate: encoding.max_bitrate,
142 max_framerate: encoding.max_framerate,
143 },
144 };
145
146 if !options.simulcast {
147 return into_rtp_encodings(width, height, &[initial_preset]);
148 }
149
150 let mut simulcast_presets = compute_default_simulcast_presets(screenshare, &initial_preset);
151
152 let mid_preset = simulcast_presets.pop();
153 let low_preset = simulcast_presets.pop();
154
155 let size = u32::max(width, height);
156
157 if size >= 960 && low_preset.is_some() {
158 #[allow(clippy::unnecessary_unwrap)]
159 return into_rtp_encodings(
160 width,
161 height,
162 &[low_preset.unwrap(), mid_preset.unwrap(), initial_preset],
163 );
164 } else if size >= 480 {
165 return into_rtp_encodings(width, height, &[mid_preset.unwrap(), initial_preset]);
166 }
167
168 into_rtp_encodings(width, height, &[initial_preset])
170}
171
172pub fn compute_appropriate_encoding(
174 is_screenshare: bool,
175 width: u32,
176 height: u32,
177 codec: VideoCodec,
178) -> VideoEncoding {
179 let presets = compute_presets_for_resolution(is_screenshare, width, height);
180 let size = u32::max(width, height);
181
182 let mut encoding = presets.first().unwrap().encoding.clone();
183
184 for preset in presets {
185 encoding = preset.encoding.clone();
186 if preset.width > size {
187 break;
188 }
189 }
190
191 match codec {
192 VideoCodec::VP9 => encoding.max_bitrate = (encoding.max_bitrate as f32 * 0.85) as u64,
193 VideoCodec::AV1 => encoding.max_bitrate = (encoding.max_bitrate as f32 * 0.7) as u64,
194 _ => {}
195 }
196
197 encoding
198}
199
200pub fn compute_presets_for_resolution(
201 is_screenshare: bool,
202 width: u32,
203 height: u32,
204) -> &'static [VideoPreset] {
205 if is_screenshare {
206 return screenshare::PRESETS;
207 }
208
209 let ar = landscape_aspect_ratio(width, height);
211 if f32::abs(ar - 16.0 / 9.0) < f32::abs(ar - 4.0 / 3.0) {
212 return video::PRESETS;
213 }
214
215 video43::PRESETS
216}
217
218pub fn compute_default_simulcast_presets(
220 is_screenshare: bool,
221 initial: &VideoPreset,
222) -> Vec<VideoPreset> {
223 if is_screenshare {
224 return vec![screenshare::compute_default_simulcast_preset(initial)];
225 }
226
227 let ar = landscape_aspect_ratio(initial.width, initial.height);
228 if f32::abs(ar - 16.0 / 9.0) < f32::abs(ar - 4.0 / 3.0) {
229 return video::DEFAULT_SIMULCAST_PRESETS.to_owned();
230 }
231
232 video43::DEFAULT_SIMULCAST_PRESETS.to_owned()
233}
234
235pub fn landscape_aspect_ratio(width: u32, height: u32) -> f32 {
236 if width > height {
237 width as f32 / height as f32
238 } else {
239 height as f32 / width as f32
240 }
241}
242
243pub fn into_rtp_encodings(
245 initial_width: u32,
246 initial_height: u32,
247 presets: &[VideoPreset],
248) -> Vec<RtpEncodingParameters> {
249 let mut encodings = Vec::with_capacity(presets.len());
250 let size = u32::min(initial_width, initial_height);
251 for (i, preset) in presets.iter().enumerate() {
252 encodings.push(RtpEncodingParameters {
253 rid: VIDEO_RIDS[i].to_string(),
254 scale_resolution_down_by: Some(f64::max(
255 1.0,
256 size as f64 / u32::min(preset.width, preset.height) as f64,
257 )),
258 max_bitrate: Some(preset.encoding.max_bitrate),
259 max_framerate: Some(preset.encoding.max_framerate),
260 ..Default::default()
261 })
262 }
263
264 encodings.reverse();
265 encodings
266}
267
268pub fn video_quality_for_rid(rid: &str) -> Option<proto::VideoQuality> {
269 match rid {
270 "f" => Some(proto::VideoQuality::High),
271 "h" => Some(proto::VideoQuality::Medium),
272 "q" => Some(proto::VideoQuality::Low),
273 _ => None,
274 }
275}
276
277pub fn video_layers_from_encodings(
278 width: u32,
279 height: u32,
280 encodings: &[RtpEncodingParameters],
281) -> Vec<proto::VideoLayer> {
282 if encodings.is_empty() {
283 return vec![proto::VideoLayer {
284 quality: proto::VideoQuality::High as i32,
285 width,
286 height,
287 bitrate: 0,
288 ssrc: 0,
289 ..Default::default()
290 }];
291 }
292
293 let mut layers = Vec::with_capacity(encodings.len());
294 for encoding in encodings {
295 let scale = encoding.scale_resolution_down_by.unwrap_or(1.0);
296 let quality = video_quality_for_rid(&encoding.rid).unwrap_or(proto::VideoQuality::High);
297
298 layers.push(proto::VideoLayer {
299 quality: quality as i32,
300 width: (width as f64 / scale) as u32,
301 height: (height as f64 / scale) as u32,
302 bitrate: encoding.max_bitrate.unwrap_or(0) as u32,
303 ssrc: 0,
304 ..Default::default()
305 });
306 }
307
308 layers
309}
310
311const VIDEO_RIDS: &[char] = &['q', 'h', 'f'];
312
313pub mod audio {
314 use super::AudioPreset;
315
316 pub const TELEPHONE: AudioPreset = AudioPreset::new(12_000);
317 pub const SPEECH: AudioPreset = AudioPreset::new(24_000);
318 pub const MUSIC: AudioPreset = AudioPreset::new(48_000);
319 pub const MUSIC_STEREO: AudioPreset = AudioPreset::new(64_000);
320 pub const MUSIC_HIGH_QUALITY: AudioPreset = AudioPreset::new(96_000);
321 pub const MUSIC_HIGH_QUALITY_STEREO: AudioPreset = AudioPreset::new(128_000);
322
323 pub const PRESETS: &[AudioPreset] =
324 &[TELEPHONE, SPEECH, MUSIC, MUSIC_STEREO, MUSIC_HIGH_QUALITY, MUSIC_HIGH_QUALITY_STEREO];
325}
326
327pub mod video {
328 use super::VideoPreset;
329
330 pub const H90: VideoPreset = VideoPreset::new(160, 90, 90_000, 15.0);
331 pub const H180: VideoPreset = VideoPreset::new(320, 180, 160_000, 15.0);
332 pub const H216: VideoPreset = VideoPreset::new(384, 216, 180_000, 15.0);
333 pub const H360: VideoPreset = VideoPreset::new(640, 360, 450_000, 20.0);
334 pub const H540: VideoPreset = VideoPreset::new(960, 540, 800_000, 25.0);
335 pub const H720: VideoPreset = VideoPreset::new(1280, 720, 1_700_000, 30.0);
336 pub const H1080: VideoPreset = VideoPreset::new(1920, 1080, 3_000_000, 30.0);
337 pub const H1440: VideoPreset = VideoPreset::new(2560, 1440, 5_000_000, 30.0);
338 pub const H2160: VideoPreset = VideoPreset::new(3840, 2160, 8_000_000, 30.0);
339
340 pub const PRESETS: &[VideoPreset] = &[H90, H180, H216, H360, H540, H720, H1080, H1440, H2160];
341 pub const DEFAULT_SIMULCAST_PRESETS: &[VideoPreset] = &[H180, H360];
342}
343
344pub mod video43 {
345 use super::VideoPreset;
346
347 pub const H120: VideoPreset = VideoPreset::new(160, 120, 80_000, 15.0);
348 pub const H180: VideoPreset = VideoPreset::new(240, 180, 100_000, 15.0);
349 pub const H240: VideoPreset = VideoPreset::new(320, 240, 150_000, 15.0);
350 pub const H360: VideoPreset = VideoPreset::new(480, 360, 225_000, 20.0);
351 pub const H480: VideoPreset = VideoPreset::new(640, 480, 300_000, 20.0);
352 pub const H540: VideoPreset = VideoPreset::new(720, 540, 450_000, 25.0);
353 pub const H720: VideoPreset = VideoPreset::new(960, 720, 1_500_000, 30.0);
354 pub const H1080: VideoPreset = VideoPreset::new(1440, 1080, 2_500_000, 30.0);
355 pub const H1440: VideoPreset = VideoPreset::new(1920, 1440, 3_500_000, 30.0);
356
357 pub const PRESETS: &[VideoPreset] = &[H120, H180, H240, H360, H480, H540, H720, H1080, H1440];
358 pub const DEFAULT_SIMULCAST_PRESETS: &[VideoPreset] = &[H180, H360];
359}
360
361pub mod screenshare {
362 use super::VideoPreset;
365
366 pub const H360_FPS3: VideoPreset = VideoPreset::new(640, 360, 200_000, 3.0);
367 pub const H720_FPS5: VideoPreset = VideoPreset::new(1280, 720, 400_000, 5.0);
368 pub const H720_FPS15: VideoPreset = VideoPreset::new(1280, 720, 1_000_000, 15.0);
369 pub const H1080_FPS15: VideoPreset = VideoPreset::new(1920, 1080, 1_500_000, 15.0);
370 pub const H1080_FPS30: VideoPreset = VideoPreset::new(1920, 1080, 3_000_000, 30.0);
371
372 pub const PRESETS: &[VideoPreset] =
373 &[H360_FPS3, H720_FPS5, H720_FPS15, H1080_FPS15, H1080_FPS30];
374
375 pub fn compute_default_simulcast_preset(initial: &VideoPreset) -> VideoPreset {
377 const SCALE_DOWN_FACTOR: u32 = 2;
378 const FPS: f64 = 3.0;
379
380 VideoPreset::new(
381 initial.width / SCALE_DOWN_FACTOR,
382 initial.height / SCALE_DOWN_FACTOR,
383 u64::max(
384 150_000,
385 initial.encoding.max_bitrate
386 / (SCALE_DOWN_FACTOR.pow(2) as u64
387 * (initial.encoding.max_framerate / FPS) as u64),
388 ),
389 FPS,
390 )
391 }
392}