Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/add_dynacast_support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
libwebrtc: minor
livekit: patch
livekit-ffi: patch
---

Add dynacast support - #1003 (@chenosaurus, @stephen-derosa)

This includes a minor breaking change for `libwebrtc`: `RtpParameters` now
contains additional RTP sender state that must be preserved when round-tripping
through `set_parameters()`.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Use this SDK to add realtime video, audio and data features to your Rust app. By
- [x] Simulcast
- [x] SVC codecs (AV1/VP9)
- [ ] Adaptive Streaming
- [ ] Dynacast
- [x] Dynacast
- [x] Hardware video enc/dec
- [x] H.264, H.265 using VideoToolbox (MacOS/iOS)
- [x] H.264, H.265 on NVidia discrete GPUs (Linux)
Expand Down
31 changes: 28 additions & 3 deletions examples/local_video/src/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ struct Args {
#[arg(long, default_value_t = false)]
attach_timestamp: bool,

/// Enable dynacast (pause unused simulcast layers based on subscriber demand)
#[arg(long, default_value_t = false)]
dynacast: bool,

/// Burn the attached timestamp into each video frame; does nothing unless --attach-timestamp is also enabled
#[arg(long, default_value_t = false)]
burn_timestamp: bool,
Expand Down Expand Up @@ -924,8 +928,8 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {

info!("Connecting to LiveKit room '{}' as '{}'...", args.room_name, args.identity);
let mut room_options = RoomOptions::default();
room_options.auto_subscribe = false;
room_options.dynacast = true;
room_options.auto_subscribe = true;
room_options.dynacast = args.dynacast;

// Configure E2EE if an encryption key is provided
if let Some(ref e2ee_key) = args.e2ee_key {
Expand Down Expand Up @@ -1253,6 +1257,7 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {
let capture_task = tokio::spawn(run_capture_loop(
capture_config,
ctrl_c_received.clone(),
track.clone(),
rtc_source,
video_input,
width,
Expand All @@ -1277,6 +1282,7 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {
let capture_result = run_capture_loop(
capture_config,
ctrl_c_received,
track,
rtc_source,
video_input,
width,
Expand All @@ -1297,6 +1303,7 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {
async fn run_capture_loop(
config: CaptureConfig,
ctrl_c_received: Arc<AtomicBool>,
track: LocalVideoTrack,
rtc_source: NativeVideoSource,
mut video_input: VideoInput,
width: u32,
Expand Down Expand Up @@ -1646,11 +1653,29 @@ async fn run_capture_loop(
if last_fps_log.elapsed() >= std::time::Duration::from_secs(2) {
let secs = last_fps_log.elapsed().as_secs_f64();
let fps_est = frames as f64 / secs;
let layers = track.publishing_layers();
let layers_str = if layers.is_empty() {
"n/a".to_string()
} else {
layers
.iter()
.map(|layer| {
format!(
"{}({})={}",
layer.rid,
layer.quality,
if layer.active { "on" } else { "off" }
)
})
.collect::<Vec<_>>()
.join(", ")
};
info!(
"Video status: {}x{} | ~{:.1} fps | target {:.2} ms",
"Video status: {}x{} | ~{:.1} fps | layers: [{}] | target {:.2} ms",
width,
height,
fps_est,
layers_str,
target.as_secs_f64() * 1000.0,
);
info!("{}", format_timing_line(&timings));
Expand Down
1 change: 1 addition & 0 deletions examples/local_video/src/subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,7 @@ impl eframe::App for VideoApp {
let resp = ui.selectable_label(is_selected, label);
if resp.clicked() {
if let Some(ref pub_remote) = sc.publication {
info!("Requesting layer: {:?}", q);
pub_remote.set_video_quality(q);
sc.requested_quality = Some(q);
}
Expand Down
87 changes: 68 additions & 19 deletions libwebrtc/src/native/rtp_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ impl From<sys_rp::ffi::RtpParameters> for RtpParameters {
Self {
codecs: value.codecs.into_iter().map(Into::into).collect(),
header_extensions: value.header_extensions.into_iter().map(Into::into).collect(),
encodings: value.encodings.into_iter().map(Into::into).collect(),
rtcp: value.rtcp.into(),
transaction_id: value.transaction_id,
mid: value.mid,
has_degradation_preference: value.has_degradation_preference,
degradation_preference: value.degradation_preference.repr,
}
}
}
Expand All @@ -51,13 +56,35 @@ impl From<sys_rp::ffi::RtpCodecParameters> for RtpCodecParameters {
payload_type: value.payload_type as u8,
clock_rate: value.has_clock_rate.then_some(value.clock_rate as u64),
channels: value.has_num_channels.then_some(value.num_channels as u16),
name: value.name,
kind: value.kind.repr,
has_max_ptime: value.has_max_ptime,
max_ptime: value.max_ptime,
has_ptime: value.has_ptime,
ptime: value.ptime,
rtcp_feedback: value
.rtcp_feedback
.into_iter()
.map(|f| CodecFeedback {
feedback_type: f.feedback_type.repr,
has_message_type: f.has_message_type,
message_type: f.message_type.repr,
})
.collect(),
parameters: value.parameters.into_iter().map(|kv| (kv.key, kv.value)).collect(),
}
}
}

impl From<sys_rp::ffi::RtcpParameters> for RtcpParameters {
fn from(value: sys_rp::ffi::RtcpParameters) -> Self {
Self { cname: value.cname, reduced_size: value.reduced_size }
Self {
cname: value.cname,
reduced_size: value.reduced_size,
mux: value.mux,
has_ssrc: value.has_ssrc,
ssrc: value.ssrc,
}
}
}

Expand All @@ -73,6 +100,8 @@ impl From<sys_rp::ffi::RtpEncodingParameters> for RtpEncodingParameters {
.has_scale_resolution_down_by
.then_some(value.scale_resolution_down_by),
scalability_mode: value.has_scalability_mode.then_some(value.scalability_mode),
has_ssrc: value.has_ssrc,
ssrc: value.ssrc,
}
}
}
Expand Down Expand Up @@ -140,36 +169,56 @@ impl From<RtpHeaderExtensionParameters> for sys_rp::ffi::RtpExtension {

impl From<RtpParameters> for sys_rp::ffi::RtpParameters {
fn from(value: RtpParameters) -> Self {
let degradation_preference =
sys_rp::ffi::DegradationPreference { repr: value.degradation_preference };
Self {
codecs: value.codecs.into_iter().map(Into::into).collect(),
header_extensions: value.header_extensions.into_iter().map(Into::into).collect(),
encodings: Vec::new(),
encodings: value.encodings.into_iter().map(Into::into).collect(),
rtcp: value.rtcp.into(),
transaction_id: "".to_string(),
mid: "".to_string(),
has_degradation_preference: false,
degradation_preference: sys_rp::ffi::DegradationPreference::Balanced,
transaction_id: value.transaction_id,
mid: value.mid,
has_degradation_preference: value.has_degradation_preference,
degradation_preference,
}
}
}

impl From<RtpCodecParameters> for sys_rp::ffi::RtpCodecParameters {
fn from(value: RtpCodecParameters) -> Self {
let kind = sys_webrtc::ffi::MediaType { repr: value.kind };
Self {
payload_type: value.payload_type as i32,
mime_type: value.mime_type,
has_clock_rate: value.clock_rate.is_some(),
clock_rate: value.clock_rate.unwrap_or_default() as i32,
has_num_channels: value.channels.is_some(),
num_channels: value.channels.unwrap_or_default() as i32,
name: "".to_string(),
kind: sys_rp::ffi::MediaType::Audio,
has_max_ptime: false,
max_ptime: 0,
has_ptime: false,
ptime: 0,
rtcp_feedback: Vec::new(),
parameters: Vec::new(),
name: value.name,
kind,
has_max_ptime: value.has_max_ptime,
max_ptime: value.max_ptime,
has_ptime: value.has_ptime,
ptime: value.ptime,
rtcp_feedback: value
.rtcp_feedback
.into_iter()
.map(|f| {
let feedback_type = sys_rp::ffi::RtcpFeedbackType { repr: f.feedback_type };
let message_type =
sys_rp::ffi::RtcpFeedbackMessageType { repr: f.message_type };
sys_rp::ffi::RtcpFeedback {
feedback_type,
has_message_type: f.has_message_type,
message_type,
}
})
.collect(),
parameters: value
.parameters
.into_iter()
.map(|(key, value)| sys_rp::ffi::StringKeyValue { key, value })
.collect(),
}
}
}
Expand All @@ -179,9 +228,9 @@ impl From<RtcpParameters> for sys_rp::ffi::RtcpParameters {
Self {
cname: value.cname,
reduced_size: value.reduced_size,
has_ssrc: false,
ssrc: 0,
mux: false,
has_ssrc: value.has_ssrc,
ssrc: value.ssrc,
mux: value.mux,
}
}
}
Expand All @@ -206,8 +255,8 @@ impl From<RtpEncodingParameters> for sys_rp::ffi::RtpEncodingParameters {
num_temporal_layers: 0,
has_scalability_mode: value.scalability_mode.is_some(),
scalability_mode: value.scalability_mode.unwrap_or_default(),
has_ssrc: false,
ssrc: 0,
has_ssrc: value.has_ssrc,
ssrc: value.ssrc,
}
}
}
Expand Down
31 changes: 31 additions & 0 deletions libwebrtc/src/rtp_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,22 @@ pub struct RtpHeaderExtensionParameters {
pub struct RtpParameters {
pub codecs: Vec<RtpCodecParameters>,
pub header_extensions: Vec<RtpHeaderExtensionParameters>,
pub encodings: Vec<RtpEncodingParameters>,
pub rtcp: RtcpParameters,
/// Opaque token used by WebRTC to pair getParameters/setParameters calls.
/// Must be preserved when round-tripping through set_parameters().
pub(crate) transaction_id: String,
Comment thread
ladvoc marked this conversation as resolved.
pub(crate) mid: String,
pub(crate) has_degradation_preference: bool,
pub(crate) degradation_preference: i32,
}

/// Mirrors webrtc_sys RtcpFeedback for round-trip fidelity.
#[derive(Debug, Clone, Default)]
pub(crate) struct CodecFeedback {
pub feedback_type: i32,
pub has_message_type: bool,
pub message_type: i32,
}

#[derive(Debug, Clone, Default)]
Expand All @@ -42,12 +57,23 @@ pub struct RtpCodecParameters {
pub mime_type: String, // read-only
pub clock_rate: Option<u64>,
pub channels: Option<u16>,
pub(crate) name: String,
pub(crate) kind: i32,
pub(crate) has_max_ptime: bool,
pub(crate) max_ptime: i32,
pub(crate) has_ptime: bool,
pub(crate) ptime: i32,
pub(crate) rtcp_feedback: Vec<CodecFeedback>,
pub(crate) parameters: Vec<(String, String)>,
}

#[derive(Debug, Clone, Default)]
pub struct RtcpParameters {
pub cname: String,
pub reduced_size: bool,
pub(crate) mux: bool,
pub(crate) has_ssrc: bool,
pub(crate) ssrc: u32,
}

#[derive(Debug, Clone)]
Expand All @@ -61,6 +87,9 @@ pub struct RtpEncodingParameters {
/// RTP scalability mode (e.g. "L3T3_KEY"). Required to enable true
/// SVC for codecs that support it (VP9, AV1).
pub scalability_mode: Option<String>,
/// Preserved for round-trip fidelity with WebRTC's getParameters/setParameters.
pub has_ssrc: bool,
pub ssrc: u32,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -93,6 +122,8 @@ impl Default for RtpEncodingParameters {
rid: String::default(),
scale_resolution_down_by: None,
scalability_mode: None,
has_ssrc: false,
ssrc: 0,
}
}
}
7 changes: 4 additions & 3 deletions livekit/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ pub use crate::{
publication::{LocalTrackPublication, RemoteTrackPublication, TrackPublication},
track::{
AudioTrack, LocalAudioTrack, LocalTrack, LocalVideoTrack, PublishTimingEvent,
PublishTimingEventStream, PublishTimingStage, RemoteAudioTrack, RemoteTrack,
RemoteVideoTrack, StreamState, SubscribeTimingEvent, SubscribeTimingEventStream,
SubscribeTimingStage, Track, TrackDimension, TrackKind, TrackSource, VideoTrack,
PublishTimingEventStream, PublishTimingStage, PublishingLayer, PublishingLayerQuality,
RemoteAudioTrack, RemoteTrack, RemoteVideoTrack, StreamState, SubscribeTimingEvent,
SubscribeTimingEventStream, SubscribeTimingStage, Track, TrackDimension, TrackKind,
TrackSource, VideoTrack,
},
ConnectionState, DataPacket, DataPacketKind, Room, RoomError, RoomEvent, RoomOptions,
RoomResult, RoomSdkOptions, SipDTMF, Transcription, TranscriptionSegment,
Expand Down
Loading
Loading