180864e953
* New webrtc build scripts (Still not integrated within the SDK) * New livekit-api crate (Integrate the server sdk protocol of livekit) * Moved livekit-utils to livekit-protocol
398 lines
12 KiB
Rust
398 lines
12 KiB
Rust
use crate::prelude::*;
|
|
use livekit_protocol as proto;
|
|
use livekit_webrtc::prelude::*;
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
pub enum VideoCodec {
|
|
VP8,
|
|
H264,
|
|
AV1,
|
|
}
|
|
|
|
impl VideoCodec {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
VideoCodec::VP8 => "vp8",
|
|
VideoCodec::H264 => "h264",
|
|
VideoCodec::AV1 => "av1",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct VideoResolution {
|
|
pub width: u32,
|
|
pub height: u32,
|
|
pub frame_rate: f64,
|
|
pub aspect_ratio: f32,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct VideoEncoding {
|
|
pub max_bitrate: u64,
|
|
pub max_framerate: f64,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct VideoPreset {
|
|
pub encoding: VideoEncoding,
|
|
pub width: u32,
|
|
pub height: u32,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct AudioEncoding {
|
|
pub max_bitrate: u64,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct AudioPreset {
|
|
pub encoding: AudioEncoding,
|
|
}
|
|
|
|
impl AudioPreset {
|
|
pub const fn new(max_bitrate: u64) -> Self {
|
|
Self {
|
|
encoding: AudioEncoding { max_bitrate },
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct AudioCaptureOptions {
|
|
pub echo_cancellation: bool,
|
|
pub noise_suppression: bool,
|
|
pub auto_gain_control: bool,
|
|
}
|
|
|
|
impl Default for AudioCaptureOptions {
|
|
fn default() -> Self {
|
|
Self {
|
|
echo_cancellation: true,
|
|
noise_suppression: true,
|
|
auto_gain_control: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct VideoCaptureOptions {
|
|
pub resolution: VideoResolution,
|
|
}
|
|
|
|
impl Default for VideoCaptureOptions {
|
|
fn default() -> Self {
|
|
Self {
|
|
resolution: video::H720.resolution(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct TrackPublishOptions {
|
|
// If the encodings aren't set, LiveKit will compute the most appropriate ones
|
|
pub video_encoding: Option<VideoEncoding>,
|
|
pub audio_encoding: Option<AudioEncoding>,
|
|
pub video_codec: VideoCodec,
|
|
pub dtx: bool,
|
|
pub red: bool,
|
|
pub simulcast: bool,
|
|
pub name: String,
|
|
pub source: TrackSource,
|
|
}
|
|
|
|
impl Default for TrackPublishOptions {
|
|
fn default() -> Self {
|
|
Self {
|
|
video_encoding: None,
|
|
audio_encoding: None,
|
|
video_codec: VideoCodec::VP8,
|
|
dtx: true,
|
|
red: true,
|
|
simulcast: true,
|
|
name: "unnamed track".to_owned(),
|
|
source: TrackSource::Unknown,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl VideoPreset {
|
|
pub const fn new(width: u32, height: u32, max_bitrate: u64, max_framerate: f64) -> Self {
|
|
Self {
|
|
width,
|
|
height,
|
|
encoding: VideoEncoding {
|
|
max_bitrate,
|
|
max_framerate,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn resolution(&self) -> VideoResolution {
|
|
VideoResolution {
|
|
width: self.width,
|
|
height: self.height,
|
|
frame_rate: self.encoding.max_framerate,
|
|
aspect_ratio: self.width as f32 / self.height as f32,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Compute appropriate RtpEncodingParameters from the video resolution.
|
|
/// TrackPublishOptions helps to find the most appropriate encodings
|
|
pub fn compute_video_encodings(
|
|
width: u32,
|
|
height: u32,
|
|
options: &TrackPublishOptions,
|
|
) -> Vec<RtpEncodingParameters> {
|
|
let screenshare = options.source == TrackSource::Screenshare;
|
|
let encoding = compute_appropriate_encoding(screenshare, width, height);
|
|
|
|
let initial_preset = VideoPreset {
|
|
width,
|
|
height,
|
|
encoding: VideoEncoding {
|
|
max_bitrate: encoding.max_bitrate,
|
|
max_framerate: encoding.max_framerate,
|
|
},
|
|
};
|
|
|
|
if !options.simulcast {
|
|
return into_rtp_encodings(width, height, &[initial_preset]);
|
|
}
|
|
|
|
let mut simulcast_presets = compute_default_simulcast_presets(screenshare, &initial_preset);
|
|
|
|
let mid_preset = simulcast_presets.pop();
|
|
let low_preset = simulcast_presets.pop();
|
|
|
|
let size = u32::max(width, height);
|
|
if size >= 960 && low_preset.is_some() {
|
|
return into_rtp_encodings(
|
|
width,
|
|
height,
|
|
&[low_preset.unwrap(), mid_preset.unwrap(), initial_preset],
|
|
);
|
|
} else if size >= 480 {
|
|
return into_rtp_encodings(width, height, &[mid_preset.unwrap(), initial_preset]);
|
|
}
|
|
|
|
// Other layers not needed
|
|
into_rtp_encodings(width, height, &[initial_preset])
|
|
}
|
|
|
|
/// Return an appropriate VideoEncdoding for the specified resolution based on our presets
|
|
pub fn compute_appropriate_encoding(
|
|
is_screenshare: bool,
|
|
width: u32,
|
|
height: u32,
|
|
) -> VideoEncoding {
|
|
let presets = compute_presets_for_resolution(is_screenshare, width, height);
|
|
let size = u32::max(width, height);
|
|
|
|
for preset in presets {
|
|
if preset.width >= size {
|
|
return preset.encoding.clone();
|
|
}
|
|
}
|
|
|
|
unreachable!()
|
|
}
|
|
|
|
pub fn compute_presets_for_resolution(
|
|
is_screenshare: bool,
|
|
width: u32,
|
|
height: u32,
|
|
) -> &'static [VideoPreset] {
|
|
if is_screenshare {
|
|
return screenshare::PRESETS;
|
|
}
|
|
|
|
// Check how close width & height are from 16/9 or 4/3
|
|
let ar = landscape_aspect_ratio(width, height);
|
|
if f32::abs(ar - 16.0 / 9.0) < f32::abs(ar - 4.0 / 3.0) {
|
|
return video::PRESETS;
|
|
}
|
|
|
|
video43::PRESETS
|
|
}
|
|
|
|
/// Returns our most appropriate default presets
|
|
pub fn compute_default_simulcast_presets(
|
|
is_screenshare: bool,
|
|
initial: &VideoPreset,
|
|
) -> Vec<VideoPreset> {
|
|
if is_screenshare {
|
|
return vec![screenshare::compute_default_simulcast_preset(initial)];
|
|
}
|
|
|
|
let ar = landscape_aspect_ratio(initial.width, initial.height);
|
|
if f32::abs(ar - 16.0 / 9.0) < f32::abs(ar - 4.0 / 3.0) {
|
|
return video::DEFAULT_SIMULCAST_PRESETS.to_owned();
|
|
}
|
|
|
|
video43::DEFAULT_SIMULCAST_PRESETS.to_owned()
|
|
}
|
|
|
|
pub fn landscape_aspect_ratio(width: u32, height: u32) -> f32 {
|
|
if width > height {
|
|
width as f32 / height as f32
|
|
} else {
|
|
height as f32 / width as f32
|
|
}
|
|
}
|
|
|
|
/// Presets must be ordered
|
|
pub fn into_rtp_encodings(
|
|
initial_width: u32,
|
|
initial_height: u32,
|
|
presets: &[VideoPreset],
|
|
) -> Vec<RtpEncodingParameters> {
|
|
let mut encodings = Vec::with_capacity(presets.len());
|
|
let size = u32::min(initial_width, initial_height);
|
|
for (i, preset) in presets.iter().enumerate() {
|
|
encodings.push(RtpEncodingParameters {
|
|
rid: VIDEO_RIDS[i].to_string(),
|
|
scale_resolution_down_by: Some(f64::max(
|
|
1.0,
|
|
size as f64 / u32::min(preset.width, preset.height) as f64,
|
|
)),
|
|
max_bitrate: Some(preset.encoding.max_bitrate),
|
|
max_framerate: Some(preset.encoding.max_framerate),
|
|
..Default::default()
|
|
})
|
|
}
|
|
|
|
encodings
|
|
}
|
|
|
|
pub fn video_quality_for_rid(rid: &str) -> Option<proto::VideoQuality> {
|
|
match rid {
|
|
"f" => Some(proto::VideoQuality::High),
|
|
"h" => Some(proto::VideoQuality::Medium),
|
|
"q" => Some(proto::VideoQuality::Low),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn video_layers_from_encodings(
|
|
width: u32,
|
|
height: u32,
|
|
encodings: &[RtpEncodingParameters],
|
|
) -> Vec<proto::VideoLayer> {
|
|
if encodings.is_empty() {
|
|
return vec![proto::VideoLayer {
|
|
quality: proto::VideoQuality::High as i32,
|
|
width,
|
|
height,
|
|
bitrate: 0,
|
|
ssrc: 0,
|
|
}];
|
|
}
|
|
|
|
let mut layers = Vec::with_capacity(encodings.len());
|
|
for encoding in encodings {
|
|
let scale = encoding.scale_resolution_down_by.unwrap_or(1.0);
|
|
let quality = video_quality_for_rid(&encoding.rid).unwrap_or(proto::VideoQuality::High);
|
|
|
|
layers.push(proto::VideoLayer {
|
|
quality: quality as i32,
|
|
width: (width as f64 / scale) as u32,
|
|
height: (height as f64 / scale) as u32,
|
|
bitrate: encoding.max_bitrate.unwrap_or(0) as u32,
|
|
ssrc: 0,
|
|
});
|
|
}
|
|
|
|
layers
|
|
}
|
|
|
|
const VIDEO_RIDS: &[char] = &['q', 'h', 'f'];
|
|
|
|
pub mod audio {
|
|
use super::AudioPreset;
|
|
|
|
pub const TELEPHONE: AudioPreset = AudioPreset::new(12_000);
|
|
pub const SPEECH: AudioPreset = AudioPreset::new(20_000);
|
|
pub const MUSIC: AudioPreset = AudioPreset::new(32_000);
|
|
pub const MUSIC_STEREO: AudioPreset = AudioPreset::new(48_000);
|
|
pub const MUSIC_HIGH_QUALITY: AudioPreset = AudioPreset::new(64_000);
|
|
pub const MUSIC_HIGH_QUALITY_STEREO: AudioPreset = AudioPreset::new(96_000);
|
|
|
|
pub const PRESETS: &[AudioPreset] = &[
|
|
TELEPHONE,
|
|
SPEECH,
|
|
MUSIC,
|
|
MUSIC_STEREO,
|
|
MUSIC_HIGH_QUALITY,
|
|
MUSIC_HIGH_QUALITY_STEREO,
|
|
];
|
|
}
|
|
|
|
pub mod video {
|
|
use super::VideoPreset;
|
|
|
|
pub const H90: VideoPreset = VideoPreset::new(160, 90, 60_000, 15.0);
|
|
pub const H180: VideoPreset = VideoPreset::new(320, 180, 120_000, 15.0);
|
|
pub const H216: VideoPreset = VideoPreset::new(384, 216, 180_000, 15.0);
|
|
pub const H360: VideoPreset = VideoPreset::new(640, 360, 300_000, 20.0);
|
|
pub const H540: VideoPreset = VideoPreset::new(960, 540, 600_000, 25.0);
|
|
pub const H720: VideoPreset = VideoPreset::new(1280, 720, 1_700_000, 30.0);
|
|
pub const H1080: VideoPreset = VideoPreset::new(1920, 1080, 3_000_000, 30.0);
|
|
pub const H1440: VideoPreset = VideoPreset::new(2560, 1440, 5_000_000, 30.0);
|
|
pub const H2160: VideoPreset = VideoPreset::new(3840, 2160, 8_000_000, 30.0);
|
|
|
|
pub const PRESETS: &[VideoPreset] = &[H90, H180, H216, H360, H540, H720, H1080, H1440, H2160];
|
|
pub const DEFAULT_SIMULCAST_PRESETS: &[VideoPreset] = &[H180, H360];
|
|
}
|
|
|
|
pub mod video43 {
|
|
use super::VideoPreset;
|
|
|
|
pub const H120: VideoPreset = VideoPreset::new(160, 120, 80_000, 15.0);
|
|
pub const H180: VideoPreset = VideoPreset::new(240, 180, 100_000, 15.0);
|
|
pub const H240: VideoPreset = VideoPreset::new(320, 240, 150_000, 15.0);
|
|
pub const H360: VideoPreset = VideoPreset::new(480, 360, 225_000, 20.0);
|
|
pub const H480: VideoPreset = VideoPreset::new(640, 480, 300_000, 20.0);
|
|
pub const H540: VideoPreset = VideoPreset::new(720, 540, 450_000, 25.0);
|
|
pub const H720: VideoPreset = VideoPreset::new(960, 720, 1_500_000, 30.0);
|
|
pub const H1080: VideoPreset = VideoPreset::new(1440, 1080, 2_500_000, 30.0);
|
|
pub const H1440: VideoPreset = VideoPreset::new(1920, 1440, 3_500_000, 30.0);
|
|
|
|
pub const PRESETS: &[VideoPreset] = &[H120, H180, H240, H360, H480, H540, H720, H1080, H1440];
|
|
pub const DEFAULT_SIMULCAST_PRESETS: &[VideoPreset] = &[H180, H360];
|
|
}
|
|
|
|
pub mod screenshare {
|
|
/// The screenshare presets are optimized for quality.
|
|
/// When simulcasting, we prefer to reduce the FPS.
|
|
use super::VideoPreset;
|
|
|
|
pub const H360_FPS3: VideoPreset = VideoPreset::new(640, 360, 200_000, 3.0);
|
|
pub const H720_FPS5: VideoPreset = VideoPreset::new(1280, 720, 400_000, 5.0);
|
|
pub const H720_FPS15: VideoPreset = VideoPreset::new(1280, 720, 1_000_000, 15.0);
|
|
pub const H1080_FPS15: VideoPreset = VideoPreset::new(1920, 1080, 1_500_000, 15.0);
|
|
pub const H1080_FPS30: VideoPreset = VideoPreset::new(1920, 1080, 3_000_000, 30.0);
|
|
|
|
pub const PRESETS: &[VideoPreset] =
|
|
&[H360_FPS3, H720_FPS5, H720_FPS15, H1080_FPS15, H1080_FPS30];
|
|
|
|
/// Only one additional layer for screenshares. (Prioritize quality)
|
|
pub fn compute_default_simulcast_preset(initial: &VideoPreset) -> VideoPreset {
|
|
const SCALE_DOWN_FACTOR: u32 = 2;
|
|
const FPS: f64 = 3.0;
|
|
|
|
VideoPreset::new(
|
|
initial.width / SCALE_DOWN_FACTOR,
|
|
initial.height / SCALE_DOWN_FACTOR,
|
|
u64::max(
|
|
150_000,
|
|
initial.encoding.max_bitrate as u64
|
|
/ (SCALE_DOWN_FACTOR.pow(2) as u64
|
|
* (initial.encoding.max_framerate / FPS) as u64),
|
|
),
|
|
FPS,
|
|
)
|
|
}
|
|
}
|