mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-12-23 02:26:35 +00:00
rtpbin2: add support for RFC 4585 (RTP/AVPF)
Implements the timing rules for RTP/AVPF Co-Authored-By: Sebastian Dröge <sebastian@centricular.com> Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1426>
This commit is contained in:
parent
27ad26c258
commit
2c86f18a99
4 changed files with 624 additions and 117 deletions
|
@ -7263,6 +7263,18 @@
|
||||||
"type": "guint",
|
"type": "guint",
|
||||||
"writable": true
|
"writable": true
|
||||||
},
|
},
|
||||||
|
"rtp-profile": {
|
||||||
|
"blurb": "RTP Profile to use",
|
||||||
|
"conditionally-available": false,
|
||||||
|
"construct": false,
|
||||||
|
"construct-only": false,
|
||||||
|
"controllable": false,
|
||||||
|
"default": "avp (0)",
|
||||||
|
"mutable": "ready",
|
||||||
|
"readable": true,
|
||||||
|
"type": "GstRtpBin2Profile",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"blurb": "Statistics about the session",
|
"blurb": "Statistics about the session",
|
||||||
"conditionally-available": false,
|
"conditionally-available": false,
|
||||||
|
|
|
@ -12,7 +12,9 @@ use futures::StreamExt;
|
||||||
use gst::{glib, prelude::*, subclass::prelude::*};
|
use gst::{glib, prelude::*, subclass::prelude::*};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use super::session::{RecvReply, RtcpRecvReply, SendReply, Session, RTCP_MIN_REPORT_INTERVAL};
|
use super::session::{
|
||||||
|
RecvReply, RtcpRecvReply, RtpProfile, SendReply, Session, RTCP_MIN_REPORT_INTERVAL,
|
||||||
|
};
|
||||||
use super::source::{ReceivedRb, SourceState};
|
use super::source::{ReceivedRb, SourceState};
|
||||||
|
|
||||||
use crate::rtpbin2::RUNTIME;
|
use crate::rtpbin2::RUNTIME;
|
||||||
|
@ -28,10 +30,40 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, glib::Enum)]
|
||||||
|
#[repr(u32)]
|
||||||
|
#[enum_type(name = "GstRtpBin2Profile")]
|
||||||
|
enum Profile {
|
||||||
|
#[default]
|
||||||
|
#[enum_value(name = "AVP profile as specified in RFC 3550", nick = "avp")]
|
||||||
|
Avp,
|
||||||
|
#[enum_value(name = "AVPF profile as specified in RFC 4585", nick = "avpf")]
|
||||||
|
Avpf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RtpProfile> for Profile {
|
||||||
|
fn from(value: RtpProfile) -> Self {
|
||||||
|
match value {
|
||||||
|
RtpProfile::Avp => Self::Avp,
|
||||||
|
RtpProfile::Avpf => Self::Avpf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Profile> for RtpProfile {
|
||||||
|
fn from(value: Profile) -> Self {
|
||||||
|
match value {
|
||||||
|
Profile::Avp => Self::Avp,
|
||||||
|
Profile::Avpf => Self::Avpf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Settings {
|
struct Settings {
|
||||||
latency: gst::ClockTime,
|
latency: gst::ClockTime,
|
||||||
min_rtcp_interval: Duration,
|
min_rtcp_interval: Duration,
|
||||||
|
profile: Profile,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
|
@ -39,6 +71,7 @@ impl Default for Settings {
|
||||||
Settings {
|
Settings {
|
||||||
latency: DEFAULT_LATENCY,
|
latency: DEFAULT_LATENCY,
|
||||||
min_rtcp_interval: DEFAULT_MIN_RTCP_INTERVAL,
|
min_rtcp_interval: DEFAULT_MIN_RTCP_INTERVAL,
|
||||||
|
profile: Profile::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,10 +154,15 @@ struct BinSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BinSession {
|
impl BinSession {
|
||||||
fn new(id: usize, min_rtcp_interval: Duration) -> Self {
|
fn new(id: usize, settings: &Settings) -> Self {
|
||||||
|
let mut inner = BinSessionInner::new(id);
|
||||||
|
inner
|
||||||
|
.session
|
||||||
|
.set_min_rtcp_interval(settings.min_rtcp_interval);
|
||||||
|
inner.session.set_profile(settings.profile.into());
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
inner: Arc::new(Mutex::new(BinSessionInner::new(id, min_rtcp_interval))),
|
inner: Arc::new(Mutex::new(inner)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,9 +194,7 @@ struct BinSessionInner {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BinSessionInner {
|
impl BinSessionInner {
|
||||||
fn new(id: usize, min_rtcp_interval: Duration) -> Self {
|
fn new(id: usize) -> Self {
|
||||||
let mut session = Session::new();
|
|
||||||
session.set_min_rtcp_interval(min_rtcp_interval);
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
|
|
||||||
|
@ -939,6 +975,12 @@ impl ObjectImpl for RtpBin2 {
|
||||||
.blurb("Statistics about the session")
|
.blurb("Statistics about the session")
|
||||||
.read_only()
|
.read_only()
|
||||||
.build(),
|
.build(),
|
||||||
|
glib::ParamSpecEnum::builder::<Profile>("rtp-profile")
|
||||||
|
.nick("RTP Profile")
|
||||||
|
.blurb("RTP Profile to use")
|
||||||
|
.default_value(Profile::default())
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -966,6 +1008,10 @@ impl ObjectImpl for RtpBin2 {
|
||||||
value.get::<u32>().expect("type checked upstream").into(),
|
value.get::<u32>().expect("type checked upstream").into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
"rtp-profile" => {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
settings.profile = value.get::<Profile>().expect("Type checked upstream");
|
||||||
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -984,6 +1030,10 @@ impl ObjectImpl for RtpBin2 {
|
||||||
let state = self.state.lock().unwrap();
|
let state = self.state.lock().unwrap();
|
||||||
state.stats().to_value()
|
state.stats().to_value()
|
||||||
}
|
}
|
||||||
|
"rtp-profile" => {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
settings.profile.to_value()
|
||||||
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1070,7 +1120,7 @@ impl ElementImpl for RtpBin2 {
|
||||||
_caps: Option<&gst::Caps>, // XXX: do something with caps?
|
_caps: Option<&gst::Caps>, // XXX: do something with caps?
|
||||||
) -> Option<gst::Pad> {
|
) -> Option<gst::Pad> {
|
||||||
let this = self.obj();
|
let this = self.obj();
|
||||||
let min_rtcp_interval = self.settings.lock().unwrap().min_rtcp_interval;
|
let settings = self.settings.lock().unwrap().clone();
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
let max_session_id = state.max_session_id;
|
let max_session_id = state.max_session_id;
|
||||||
|
|
||||||
|
@ -1135,7 +1185,7 @@ impl ElementImpl for RtpBin2 {
|
||||||
new_pad(&mut session)
|
new_pad(&mut session)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let session = BinSession::new(id, min_rtcp_interval);
|
let session = BinSession::new(id, &settings);
|
||||||
let mut inner = session.inner.lock().unwrap();
|
let mut inner = session.inner.lock().unwrap();
|
||||||
let ret = new_pad(&mut inner);
|
let ret = new_pad(&mut inner);
|
||||||
drop(inner);
|
drop(inner);
|
||||||
|
@ -1178,7 +1228,7 @@ impl ElementImpl for RtpBin2 {
|
||||||
new_pad(&mut session)
|
new_pad(&mut session)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let session = BinSession::new(id, min_rtcp_interval);
|
let session = BinSession::new(id, &settings);
|
||||||
let mut inner = session.inner.lock().unwrap();
|
let mut inner = session.inner.lock().unwrap();
|
||||||
let ret = new_pad(&mut inner);
|
let ret = new_pad(&mut inner);
|
||||||
drop(inner);
|
drop(inner);
|
||||||
|
|
|
@ -20,7 +20,6 @@ use gst::prelude::MulDiv;
|
||||||
|
|
||||||
// TODO: make configurable
|
// TODO: make configurable
|
||||||
pub const RTCP_MIN_REPORT_INTERVAL: Duration = Duration::from_secs(5);
|
pub const RTCP_MIN_REPORT_INTERVAL: Duration = Duration::from_secs(5);
|
||||||
// TODO: reduced minimum interval? (360 / session bandwidth)
|
|
||||||
|
|
||||||
const RTCP_SOURCE_TIMEOUT_N_INTERVALS: u32 = 5;
|
const RTCP_SOURCE_TIMEOUT_N_INTERVALS: u32 = 5;
|
||||||
const RTCP_ADDRESS_CONFLICT_TIMEOUT: Duration = RTCP_MIN_REPORT_INTERVAL.saturating_mul(12);
|
const RTCP_ADDRESS_CONFLICT_TIMEOUT: Duration = RTCP_MIN_REPORT_INTERVAL.saturating_mul(12);
|
||||||
|
@ -42,19 +41,36 @@ struct ByeState {
|
||||||
pmembers: usize,
|
pmembers: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum RtpProfile {
|
||||||
|
#[default]
|
||||||
|
Avp,
|
||||||
|
Avpf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RtpProfile {
|
||||||
|
fn is_feedback(&self) -> bool {
|
||||||
|
self == &Self::Avpf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum RequestEarlyRtcpResult {
|
||||||
|
NotScheduled,
|
||||||
|
Scheduled,
|
||||||
|
TimerReconsideration,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
// settings
|
// settings
|
||||||
min_rtcp_interval: Duration,
|
min_rtcp_interval: Duration,
|
||||||
|
profile: RtpProfile,
|
||||||
// state
|
// state
|
||||||
local_senders: HashMap<u32, LocalSendSource>,
|
local_senders: HashMap<u32, LocalSendSource>,
|
||||||
local_receivers: HashMap<u32, LocalReceiveSource>,
|
local_receivers: HashMap<u32, LocalReceiveSource>,
|
||||||
remote_receivers: HashMap<u32, RemoteReceiveSource>,
|
remote_receivers: HashMap<u32, RemoteReceiveSource>,
|
||||||
remote_senders: HashMap<u32, RemoteSendSource>,
|
remote_senders: HashMap<u32, RemoteSendSource>,
|
||||||
last_rtcp_sent_times: VecDeque<Instant>,
|
|
||||||
// holds the next rtcp send time and the number of members at the time when the time was
|
|
||||||
// calculated
|
|
||||||
next_rtcp_send: RtcpTimeMembers,
|
|
||||||
average_rtcp_size: usize,
|
average_rtcp_size: usize,
|
||||||
last_sent_data: Option<Instant>,
|
last_sent_data: Option<Instant>,
|
||||||
hold_buffer_counter: usize,
|
hold_buffer_counter: usize,
|
||||||
|
@ -64,6 +80,20 @@ pub struct Session {
|
||||||
// used when we have not sent anything but need a ssrc for Rr
|
// used when we have not sent anything but need a ssrc for Rr
|
||||||
internal_rtcp_sender_src: Option<u32>,
|
internal_rtcp_sender_src: Option<u32>,
|
||||||
bye_state: Option<ByeState>,
|
bye_state: Option<ByeState>,
|
||||||
|
is_point_to_point: bool,
|
||||||
|
|
||||||
|
// rtcp scheduling state
|
||||||
|
// T_rr: holds the interval used to calculate the current `next_rtcp_send`
|
||||||
|
rtcp_interval: Option<Duration>,
|
||||||
|
// tp: last time that any rtcp was handled
|
||||||
|
last_rtcp_handle_time: Option<Instant>,
|
||||||
|
// tn: holds the next regular rtcp send time and the number of members at the
|
||||||
|
// time when the time was calculated
|
||||||
|
next_rtcp_send: RtcpTimeMembers,
|
||||||
|
// T_rr_last: last two times a regular rtcp packet was sent
|
||||||
|
last_rtcp_sent_times: VecDeque<Instant>,
|
||||||
|
// time for the next early rtcp to be sent
|
||||||
|
next_early_rtcp_time: Option<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -117,12 +147,14 @@ impl Session {
|
||||||
sdes.insert(SdesItem::CNAME, cname);
|
sdes.insert(SdesItem::CNAME, cname);
|
||||||
Self {
|
Self {
|
||||||
min_rtcp_interval: RTCP_MIN_REPORT_INTERVAL,
|
min_rtcp_interval: RTCP_MIN_REPORT_INTERVAL,
|
||||||
|
profile: RtpProfile::default(),
|
||||||
local_senders: HashMap::new(),
|
local_senders: HashMap::new(),
|
||||||
// also known as remote_senders
|
// also known as remote_senders
|
||||||
local_receivers: HashMap::new(),
|
local_receivers: HashMap::new(),
|
||||||
remote_receivers: HashMap::new(),
|
remote_receivers: HashMap::new(),
|
||||||
remote_senders: HashMap::new(),
|
remote_senders: HashMap::new(),
|
||||||
last_rtcp_sent_times: VecDeque::new(),
|
last_rtcp_sent_times: VecDeque::new(),
|
||||||
|
rtcp_interval: None,
|
||||||
next_rtcp_send: RtcpTimeMembers {
|
next_rtcp_send: RtcpTimeMembers {
|
||||||
time: None,
|
time: None,
|
||||||
p_members: 0,
|
p_members: 0,
|
||||||
|
@ -135,14 +167,22 @@ impl Session {
|
||||||
conflicting_addresses: HashMap::new(),
|
conflicting_addresses: HashMap::new(),
|
||||||
internal_rtcp_sender_src: None,
|
internal_rtcp_sender_src: None,
|
||||||
bye_state: None,
|
bye_state: None,
|
||||||
|
next_early_rtcp_time: None,
|
||||||
|
last_rtcp_handle_time: None,
|
||||||
|
is_point_to_point: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the minimum RTCP interval to use for this session
|
/// Set the minimum (regular) RTCP interval to use for this session
|
||||||
pub fn set_min_rtcp_interval(&mut self, min_rtcp_interval: Duration) {
|
pub fn set_min_rtcp_interval(&mut self, min_rtcp_interval: Duration) {
|
||||||
self.min_rtcp_interval = min_rtcp_interval;
|
self.min_rtcp_interval = min_rtcp_interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the RTP profile to use.
|
||||||
|
pub fn set_profile(&mut self, profile: RtpProfile) {
|
||||||
|
self.profile = profile;
|
||||||
|
}
|
||||||
|
|
||||||
fn n_members(&self) -> usize {
|
fn n_members(&self) -> usize {
|
||||||
self.bye_state
|
self.bye_state
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -191,6 +231,28 @@ impl Session {
|
||||||
.unwrap_or(self.next_rtcp_send.p_members)
|
.unwrap_or(self.next_rtcp_send.p_members)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_point_to_point(&mut self) {
|
||||||
|
// This definition of point to point is from RFC 8108
|
||||||
|
// i.e. If there are multiple remote CNAMEs, then we are not point to point.
|
||||||
|
//
|
||||||
|
// XXX: We currently don't provide external overriding for this
|
||||||
|
let mut cname = None;
|
||||||
|
self.is_point_to_point = self
|
||||||
|
.remote_senders
|
||||||
|
.values()
|
||||||
|
.filter_map(|source| source.sdes().get(&SdesItem::CNAME))
|
||||||
|
.chain(
|
||||||
|
self.remote_receivers
|
||||||
|
.values()
|
||||||
|
.filter_map(|source| source.sdes().get(&SdesItem::CNAME)),
|
||||||
|
)
|
||||||
|
.all(|cn| {
|
||||||
|
let ret = cname.is_none() || cname == Some(cn);
|
||||||
|
cname = cname.or(Some(cn));
|
||||||
|
ret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the RTP clock rate for a particular payload type
|
/// Set the RTP clock rate for a particular payload type
|
||||||
pub fn set_pt_clock_rate(&mut self, pt: u8, clock_rate: u32) {
|
pub fn set_pt_clock_rate(&mut self, pt: u8, clock_rate: u32) {
|
||||||
self.pt_map.insert(pt, clock_rate);
|
self.pt_map.insert(pt, clock_rate);
|
||||||
|
@ -409,6 +471,8 @@ impl Session {
|
||||||
let dur = prev.saturating_duration_since(now);
|
let dur = prev.saturating_duration_since(now);
|
||||||
if self.next_rtcp_send.p_members > 0 {
|
if self.next_rtcp_send.p_members > 0 {
|
||||||
let member_factor = initial_n_members as f64 / self.next_rtcp_send.p_members as f64;
|
let member_factor = initial_n_members as f64 / self.next_rtcp_send.p_members as f64;
|
||||||
|
// FIXME: Does something have to be adjusted here in feedback profiles for
|
||||||
|
// T_rr_interval?
|
||||||
*prev = now + dur.mul_f64(member_factor);
|
*prev = now + dur.mul_f64(member_factor);
|
||||||
self.next_rtcp_send.p_members = n_members;
|
self.next_rtcp_send.p_members = n_members;
|
||||||
if let Some(last_rtcp) = self.last_rtcp_sent_times.front_mut() {
|
if let Some(last_rtcp) = self.last_rtcp_sent_times.front_mut() {
|
||||||
|
@ -592,9 +656,12 @@ impl Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Packet::Unknown(_unk)) => (),
|
Ok(Packet::Unknown(_unk)) => (),
|
||||||
|
// TODO: in RFC4585 profile, need to listen for feedback messages and remove any
|
||||||
|
// that we would have sent
|
||||||
Err(_) => (),
|
Err(_) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.update_point_to_point();
|
||||||
replies
|
replies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,6 +670,8 @@ impl Session {
|
||||||
mut rtcp: CompoundBuilder<'a>,
|
mut rtcp: CompoundBuilder<'a>,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
ntp_now: SystemTime,
|
ntp_now: SystemTime,
|
||||||
|
minimum: bool, // RFC 4585
|
||||||
|
ssrcs_reported: &mut Vec<u32>,
|
||||||
) -> CompoundBuilder<'a> {
|
) -> CompoundBuilder<'a> {
|
||||||
let ntp_time = system_time_to_ntp_time_u64(ntp_now);
|
let ntp_time = system_time_to_ntp_time_u64(ntp_now);
|
||||||
if self
|
if self
|
||||||
|
@ -640,7 +709,6 @@ impl Session {
|
||||||
// assume that the rtp times and clock times advance at a rate
|
// assume that the rtp times and clock times advance at a rate
|
||||||
// close to 1.0 and do a direct linear extrapolation to get the rtp
|
// close to 1.0 and do a direct linear extrapolation to get the rtp
|
||||||
// time for 'now'
|
// time for 'now'
|
||||||
trace!("clock-rate {clock_rate}");
|
|
||||||
(dur_since_last_rtp.as_nanos() as u64).mul_div_round(
|
(dur_since_last_rtp.as_nanos() as u64).mul_div_round(
|
||||||
clock_rate as u64,
|
clock_rate as u64,
|
||||||
gst::ClockTime::SECOND.nseconds(),
|
gst::ClockTime::SECOND.nseconds(),
|
||||||
|
@ -657,15 +725,29 @@ impl Session {
|
||||||
.rtp_timestamp(rtp_timestamp);
|
.rtp_timestamp(rtp_timestamp);
|
||||||
|
|
||||||
sender_srs.push((sender.ssrc(), ntp_now, ntp_time, rtp_timestamp));
|
sender_srs.push((sender.ssrc(), ntp_now, ntp_time, rtp_timestamp));
|
||||||
|
if !ssrcs_reported.iter().any(|&ssrc| ssrc == sender.ssrc()) {
|
||||||
|
ssrcs_reported.push(sender.ssrc());
|
||||||
|
}
|
||||||
|
|
||||||
for sender in self.remote_senders.values() {
|
// Don't include RBs in minimal RTCP packets
|
||||||
if sender.state() != SourceState::Normal {
|
if !minimum {
|
||||||
continue;
|
for sender in self.remote_senders.values() {
|
||||||
|
if sender.state() != SourceState::Normal {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let rb = sender.generate_report_block(ntp_now);
|
||||||
|
sr = sr.add_report_block(rb.into());
|
||||||
|
if !ssrcs_reported.iter().any(|&ssrc| ssrc == sender.ssrc()) {
|
||||||
|
ssrcs_reported.push(sender.ssrc());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let rb = sender.generate_report_block(ntp_now);
|
|
||||||
sr = sr.add_report_block(rb.into());
|
|
||||||
}
|
}
|
||||||
rtcp = rtcp.add_packet(sr);
|
rtcp = rtcp.add_packet(sr);
|
||||||
|
|
||||||
|
// A minimal RTCP packet only contains a single SR
|
||||||
|
if minimum {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (ssrc, ntp_now, ntp_time, rtp_timestamp) in sender_srs {
|
for (ssrc, ntp_now, ntp_time, rtp_timestamp) in sender_srs {
|
||||||
self.local_senders
|
self.local_senders
|
||||||
|
@ -711,6 +793,8 @@ impl Session {
|
||||||
mut rtcp: CompoundBuilder<'a>,
|
mut rtcp: CompoundBuilder<'a>,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
ntp_now: SystemTime,
|
ntp_now: SystemTime,
|
||||||
|
minimum: bool, // RFC 4585
|
||||||
|
ssrcs_reported: &mut Vec<u32>,
|
||||||
) -> CompoundBuilder<'a> {
|
) -> CompoundBuilder<'a> {
|
||||||
if self
|
if self
|
||||||
.local_senders
|
.local_senders
|
||||||
|
@ -729,32 +813,44 @@ impl Session {
|
||||||
.entry(ssrc)
|
.entry(ssrc)
|
||||||
.and_modify(|source| source.set_last_activity(now));
|
.and_modify(|source| source.set_last_activity(now));
|
||||||
let mut rr = ReceiverReport::builder(ssrc);
|
let mut rr = ReceiverReport::builder(ssrc);
|
||||||
for sender in self.remote_senders.values() {
|
|
||||||
if sender.state() != SourceState::Normal {
|
// Don't include RBs in minimal RTCP packets
|
||||||
continue;
|
if !minimum {
|
||||||
|
for sender in self.remote_senders.values() {
|
||||||
|
if sender.state() != SourceState::Normal {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let rb = sender.generate_report_block(ntp_now);
|
||||||
|
rr = rr.add_report_block(rb.into());
|
||||||
|
if !ssrcs_reported.iter().any(|&ssrc| ssrc == sender.ssrc()) {
|
||||||
|
ssrcs_reported.push(sender.ssrc());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let rb = sender.generate_report_block(ntp_now);
|
|
||||||
rr = rr.add_report_block(rb.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rtcp = rtcp.add_packet(rr);
|
rtcp = rtcp.add_packet(rr);
|
||||||
}
|
}
|
||||||
rtcp
|
rtcp
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_sdes<'a>(&self, rtcp: CompoundBuilder<'a>) -> CompoundBuilder<'a> {
|
fn generate_sdes<'a>(
|
||||||
|
&self,
|
||||||
|
rtcp: CompoundBuilder<'a>,
|
||||||
|
minimum: bool, // RFC 4585
|
||||||
|
) -> CompoundBuilder<'a> {
|
||||||
let mut sdes = Sdes::builder();
|
let mut sdes = Sdes::builder();
|
||||||
let mut have_chunk = false;
|
let mut have_chunk = false;
|
||||||
if !self.local_senders.is_empty() {
|
for sender in self.local_senders.values() {
|
||||||
for sender in self.local_senders.values() {
|
let sdes_map = sender.sdes();
|
||||||
let sdes_map = sender.sdes();
|
if !sdes_map.is_empty() {
|
||||||
if !sdes_map.is_empty() {
|
let mut chunk = SdesChunk::builder(sender.ssrc());
|
||||||
let mut chunk = SdesChunk::builder(sender.ssrc());
|
for (ty, val) in sdes_map {
|
||||||
for (ty, val) in sdes_map {
|
if !minimum || *ty == SdesItem::CNAME {
|
||||||
chunk = chunk.add_item_owned(SdesItem::builder(*ty, val));
|
chunk = chunk.add_item_owned(SdesItem::builder(*ty, val));
|
||||||
}
|
}
|
||||||
have_chunk = true;
|
|
||||||
sdes = sdes.add_chunk(chunk);
|
|
||||||
}
|
}
|
||||||
|
have_chunk = true;
|
||||||
|
sdes = sdes.add_chunk(chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for receiver in self.local_receivers.values() {
|
for receiver in self.local_receivers.values() {
|
||||||
|
@ -762,7 +858,9 @@ impl Session {
|
||||||
if !sdes_map.is_empty() {
|
if !sdes_map.is_empty() {
|
||||||
let mut chunk = SdesChunk::builder(receiver.ssrc());
|
let mut chunk = SdesChunk::builder(receiver.ssrc());
|
||||||
for (ty, val) in sdes_map {
|
for (ty, val) in sdes_map {
|
||||||
chunk = chunk.add_item_owned(SdesItem::builder(*ty, val));
|
if !minimum || *ty == SdesItem::CNAME {
|
||||||
|
chunk = chunk.add_item_owned(SdesItem::builder(*ty, val));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
have_chunk = true;
|
have_chunk = true;
|
||||||
sdes = sdes.add_chunk(chunk);
|
sdes = sdes.add_chunk(chunk);
|
||||||
|
@ -834,7 +932,10 @@ impl Session {
|
||||||
// RFC 3550 6.3.5
|
// RFC 3550 6.3.5
|
||||||
fn handle_timeouts(&mut self, now: Instant) {
|
fn handle_timeouts(&mut self, now: Instant) {
|
||||||
trace!("handling rtcp timeouts");
|
trace!("handling rtcp timeouts");
|
||||||
let td = RTCP_SOURCE_TIMEOUT_N_INTERVALS * self.deterministic_rtcp_duration(false);
|
let td = RTCP_SOURCE_TIMEOUT_N_INTERVALS
|
||||||
|
* self
|
||||||
|
.deterministic_rtcp_duration(false)
|
||||||
|
.max(Duration::from_secs(5));
|
||||||
|
|
||||||
// delete all sources that are too old
|
// delete all sources that are too old
|
||||||
self.local_receivers
|
self.local_receivers
|
||||||
|
@ -884,26 +985,57 @@ impl Session {
|
||||||
.retain(|_addr, time| now - *time < RTCP_ADDRESS_CONFLICT_TIMEOUT);
|
.retain(|_addr, time| now - *time < RTCP_ADDRESS_CONFLICT_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce a RTCP packet (or None if it is too early to send a RTCP packet). After this call returns
|
/// Produce a RTCP packet (or `None` if it is too early to send a RTCP packet). After this call returns,
|
||||||
/// a packet, the next time to send a RTCP packet can be retrieved from `poll_rtcp_send_timeout`
|
/// the next time to send a RTCP packet can be retrieved from `poll_rtcp_send_timeout`
|
||||||
// TODO: return RtcpPacketBuilder thing
|
// TODO: return RtcpPacketBuilder thing
|
||||||
pub fn poll_rtcp_send(&mut self, now: Instant, ntp_now: SystemTime) -> Option<Vec<u8>> {
|
pub fn poll_rtcp_send(&mut self, now: Instant, ntp_now: SystemTime) -> Option<Vec<u8>> {
|
||||||
let Some(next_rtcp_send) = self.next_rtcp_send.time else {
|
let Some(next_rtcp_send) = self.next_rtcp_send.time else {
|
||||||
|
trace!("no next check time yet");
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
if now < next_rtcp_send {
|
let is_early = self.next_early_rtcp_time.is_some() && !self.last_rtcp_sent_times.is_empty();
|
||||||
return None;
|
|
||||||
|
if is_early {
|
||||||
|
if let Some(next_early_rtcp_time) = self.next_early_rtcp_time {
|
||||||
|
if now < next_early_rtcp_time {
|
||||||
|
trace!("next early time {next_early_rtcp_time:?} not reached at {now:?}, nothing to produce");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if now < next_rtcp_send {
|
||||||
|
trace!("next time {next_rtcp_send:?} not reached at {now:?}, nothing to produce");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// timer reconsideration
|
||||||
|
self.update_point_to_point();
|
||||||
|
let interval = self.rtcp_interval();
|
||||||
|
let test_next_rtcp_time = self.last_rtcp_handle_time.unwrap() + interval;
|
||||||
|
if test_next_rtcp_time > now {
|
||||||
|
trace!("timer reconsideration considers this wakeup {now:?} too early, nothing to produce. reconsidered time {test_next_rtcp_time:?}");
|
||||||
|
self.next_rtcp_send.time = Some(test_next_rtcp_time);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("generating rtcp packet at {now:?}, ntp:{ntp_now:?}");
|
debug!(
|
||||||
|
"generating rtcp packet at {now:?}, ntp:{ntp_now:?}, duration since last rtcp {:?}",
|
||||||
|
self.last_rtcp_sent_times
|
||||||
|
.front()
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(now)
|
||||||
|
.duration_since(now)
|
||||||
|
);
|
||||||
|
|
||||||
let data = {
|
let (data, ssrcs_reported) = {
|
||||||
let mut rtcp = Compound::builder();
|
let mut rtcp = Compound::builder();
|
||||||
|
let mut ssrcs_reported = vec![];
|
||||||
|
|
||||||
// TODO: implement round robin of sr/rrs
|
// TODO: implement round robin of sr/rrs
|
||||||
rtcp = self.generate_sr(rtcp, now, ntp_now);
|
rtcp = self.generate_sr(rtcp, now, ntp_now, is_early, &mut ssrcs_reported);
|
||||||
rtcp = self.generate_rr(rtcp, now, ntp_now);
|
rtcp = self.generate_rr(rtcp, now, ntp_now, is_early, &mut ssrcs_reported);
|
||||||
rtcp = self.generate_sdes(rtcp);
|
rtcp = self.generate_sdes(rtcp, is_early);
|
||||||
rtcp = self.generate_bye(rtcp, now);
|
rtcp = self.generate_bye(rtcp, now);
|
||||||
|
|
||||||
let size = rtcp.calculate_size().unwrap();
|
let size = rtcp.calculate_size().unwrap();
|
||||||
|
@ -911,36 +1043,68 @@ impl Session {
|
||||||
assert!(size < RTCP_MTU);
|
assert!(size < RTCP_MTU);
|
||||||
let mut data = vec![0; size];
|
let mut data = vec![0; size];
|
||||||
rtcp.write_into(&mut data).unwrap();
|
rtcp.write_into(&mut data).unwrap();
|
||||||
data
|
(data, ssrcs_reported)
|
||||||
};
|
};
|
||||||
|
|
||||||
for receiver in self.remote_senders.values_mut() {
|
for ssrc in ssrcs_reported {
|
||||||
receiver.update_last_rtcp();
|
if let Some(receiver) = self.remote_senders.get_mut(&ssrc) {
|
||||||
|
receiver.update_last_rtcp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_rtcp_average(data.len() + UDP_IP_OVERHEAD_BYTES);
|
self.update_rtcp_average(data.len() + UDP_IP_OVERHEAD_BYTES);
|
||||||
|
|
||||||
self.handle_timeouts(now);
|
if !is_early {
|
||||||
|
self.handle_timeouts(now);
|
||||||
|
|
||||||
self.next_rtcp_send = RtcpTimeMembers {
|
self.update_point_to_point();
|
||||||
time: Some(self.next_rtcp_time(now)),
|
let mut interval = self.rtcp_interval();
|
||||||
p_members: self.n_members(),
|
self.rtcp_interval = Some(interval);
|
||||||
};
|
// Handle T_rr_interval
|
||||||
self.last_rtcp_sent_times.push_front(now);
|
if self.profile.is_feedback() && !self.min_rtcp_interval.is_zero() {
|
||||||
while self.last_rtcp_sent_times.len() > 2 {
|
let new_interval = interval.max(rtcp_dither(self.min_rtcp_interval));
|
||||||
self.last_rtcp_sent_times.pop_back();
|
trace!("Updating interval from {interval:?} to {new_interval:?}");
|
||||||
|
interval = new_interval;
|
||||||
|
}
|
||||||
|
self.next_rtcp_send = RtcpTimeMembers {
|
||||||
|
time: Some(now + interval),
|
||||||
|
p_members: self.n_members(),
|
||||||
|
};
|
||||||
|
trace!("next rtcp time {:?}", self.next_rtcp_send.time);
|
||||||
|
self.last_rtcp_sent_times.push_front(now);
|
||||||
|
while self.last_rtcp_sent_times.len() > 2 {
|
||||||
|
self.last_rtcp_sent_times.pop_back();
|
||||||
|
}
|
||||||
|
self.bye_state = None;
|
||||||
}
|
}
|
||||||
self.bye_state = None;
|
self.last_rtcp_handle_time = Some(now);
|
||||||
|
self.next_early_rtcp_time = None;
|
||||||
Some(data)
|
Some(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the next time to send a RTCP packet.
|
/// Returns the next time to send a RTCP packet.
|
||||||
pub fn poll_rtcp_send_timeout(&mut self, now: Instant) -> Option<Instant> {
|
pub fn poll_rtcp_send_timeout(&mut self, now: Instant) -> Option<Instant> {
|
||||||
|
trace!(
|
||||||
|
"poll-rtcp-send-timeout early time {:?}, next {:?}",
|
||||||
|
self.next_early_rtcp_time,
|
||||||
|
self.next_rtcp_send.time
|
||||||
|
);
|
||||||
|
if let Some(early_time) = self.next_early_rtcp_time {
|
||||||
|
return Some(early_time);
|
||||||
|
}
|
||||||
if self.next_rtcp_send.time.is_none() {
|
if self.next_rtcp_send.time.is_none() {
|
||||||
|
self.update_point_to_point();
|
||||||
|
let interval = self.rtcp_interval();
|
||||||
|
self.rtcp_interval = Some(interval);
|
||||||
|
self.last_rtcp_handle_time = Some(now);
|
||||||
self.next_rtcp_send = RtcpTimeMembers {
|
self.next_rtcp_send = RtcpTimeMembers {
|
||||||
time: Some(self.next_rtcp_time(now)),
|
time: Some(now + interval),
|
||||||
p_members: self.n_members(),
|
p_members: self.n_members(),
|
||||||
};
|
};
|
||||||
|
trace!(
|
||||||
|
"poll-rtcp-send-timeout initial rtcp time {:?}",
|
||||||
|
self.next_rtcp_send.time
|
||||||
|
);
|
||||||
}
|
}
|
||||||
self.next_rtcp_send.time
|
self.next_rtcp_send.time
|
||||||
}
|
}
|
||||||
|
@ -962,8 +1126,22 @@ impl Session {
|
||||||
(n_members, rtcp_bw)
|
(n_members, rtcp_bw)
|
||||||
};
|
};
|
||||||
|
|
||||||
let min_rtcp_interval = if !self.last_rtcp_sent_times.is_empty() && self.bye_state.is_none()
|
let min_rtcp_interval = if we_sent {
|
||||||
{
|
Duration::ZERO
|
||||||
|
} else if self.profile.is_feedback() {
|
||||||
|
// RFC 4585 3.4d), 3.5.1
|
||||||
|
//
|
||||||
|
// If not the first RTCP then Tmin is initialized to 0, otherwise to 1s.
|
||||||
|
// Also for a multi-party session it is initialized to 0.
|
||||||
|
if self.is_point_to_point
|
||||||
|
&& !self.last_rtcp_sent_times.is_empty()
|
||||||
|
&& self.bye_state.is_none()
|
||||||
|
{
|
||||||
|
Duration::ZERO
|
||||||
|
} else {
|
||||||
|
Duration::from_secs(1)
|
||||||
|
}
|
||||||
|
} else if !self.last_rtcp_sent_times.is_empty() && self.bye_state.is_none() {
|
||||||
self.min_rtcp_interval
|
self.min_rtcp_interval
|
||||||
} else {
|
} else {
|
||||||
self.min_rtcp_interval / 2
|
self.min_rtcp_interval / 2
|
||||||
|
@ -971,8 +1149,12 @@ impl Session {
|
||||||
|
|
||||||
// 1_000_000_000 / (e-1.5)
|
// 1_000_000_000 / (e-1.5)
|
||||||
let compensation_ns = 820_829_366;
|
let compensation_ns = 820_829_366;
|
||||||
|
trace!(
|
||||||
|
"members {n}, RTCP bandwidth {rtcp_bw}, average RTCP size {}",
|
||||||
|
self.average_rtcp_size
|
||||||
|
);
|
||||||
let t_nanos = (compensation_ns
|
let t_nanos = (compensation_ns
|
||||||
.mul_div_round(self.average_rtcp_size as u64 * n, rtcp_bw as u64))
|
.mul_div_round(self.average_rtcp_size as u64 * n, rtcp_bw.max(1) as u64))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.max(min_rtcp_interval.as_nanos() as u64);
|
.max(min_rtcp_interval.as_nanos() as u64);
|
||||||
trace!("deterministic rtcp interval {t_nanos}ns");
|
trace!("deterministic rtcp interval {t_nanos}ns");
|
||||||
|
@ -995,12 +1177,96 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculated_rtcp_duration(&self, we_sent: bool) -> Duration {
|
fn calculated_rtcp_duration(&self, we_sent: bool) -> Duration {
|
||||||
|
// From RFC 3550: only active data senders may use the reduced minimum
|
||||||
let dur = self.deterministic_rtcp_duration(we_sent);
|
let dur = self.deterministic_rtcp_duration(we_sent);
|
||||||
|
rtcp_dither(dur)
|
||||||
|
}
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
// returns whether to keep the feedback
|
||||||
// need a factor in [0.5, 1.5]
|
fn request_early_rtcp(&mut self, now: Instant, max_delay: Duration) -> RequestEarlyRtcpResult {
|
||||||
let factor = rng.gen::<f64>();
|
// Implementation of RFC 4585 3.5.2
|
||||||
dur.mul_f64(factor + 0.5)
|
if !self.profile.is_feedback() {
|
||||||
|
warn!("requested early RTCP without relevant feedback RTP profile, ignoring");
|
||||||
|
return RequestEarlyRtcpResult::NotScheduled;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(early_time) = self.next_early_rtcp_time {
|
||||||
|
if now + max_delay >= early_time {
|
||||||
|
debug!("early RTCP already scheduled for {early_time:?}");
|
||||||
|
return RequestEarlyRtcpResult::Scheduled;
|
||||||
|
} else {
|
||||||
|
debug!("early RTCP already scheduled for {early_time:?} but too late");
|
||||||
|
return RequestEarlyRtcpResult::NotScheduled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some(next_rtcp_send_time) = self.next_rtcp_send.time else {
|
||||||
|
debug!("No regular RTCP scheduled yet, scheduling now");
|
||||||
|
return RequestEarlyRtcpResult::TimerReconsideration;
|
||||||
|
};
|
||||||
|
|
||||||
|
// no regular RTCP sent yet, we cannot send early without the first regular RTCP being sent
|
||||||
|
let Some(&last_rtcp_sent) = self.last_rtcp_sent_times.front() else {
|
||||||
|
if now + max_delay >= next_rtcp_send_time {
|
||||||
|
debug!("early RTCP can't be scheduled until first regular RTCP is sent but regular RTCP scheduled early enough");
|
||||||
|
return RequestEarlyRtcpResult::Scheduled;
|
||||||
|
} else {
|
||||||
|
debug!("early RTCP can't be scheduled until first regular RTCP is sent and regular RTCP scheduled too late");
|
||||||
|
return RequestEarlyRtcpResult::NotScheduled;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.update_point_to_point();
|
||||||
|
|
||||||
|
// safe as all previous invariants ensure that the `handle_time` must have been set
|
||||||
|
let last_rtcp_handle_time = self.last_rtcp_handle_time.unwrap();
|
||||||
|
|
||||||
|
let t_rr = self.rtcp_interval.unwrap();
|
||||||
|
let t_dither_max = if self.is_point_to_point {
|
||||||
|
Duration::from_secs(0)
|
||||||
|
} else {
|
||||||
|
t_rr / 2
|
||||||
|
};
|
||||||
|
|
||||||
|
if now + t_dither_max > next_rtcp_send_time {
|
||||||
|
if now + max_delay >= next_rtcp_send_time {
|
||||||
|
debug!("early RTCP not scheduled because regular RTCP is early enough");
|
||||||
|
return RequestEarlyRtcpResult::Scheduled;
|
||||||
|
} else {
|
||||||
|
debug!("early RTCP can't be scheduled because regular RTCP is scheduled soon but too late");
|
||||||
|
return RequestEarlyRtcpResult::NotScheduled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (allow_early, offset) = if last_rtcp_sent == last_rtcp_handle_time {
|
||||||
|
// last rtcp was a regular rtcp, we can send early after that
|
||||||
|
(true, Duration::ZERO)
|
||||||
|
} else if last_rtcp_handle_time + t_rr <= now + max_delay {
|
||||||
|
// 1. last rtcp packet was not regular rtcp so must be early rtcp
|
||||||
|
// 2. More than t_rr has passed since so a regular rtcp packet has been suppressed thus
|
||||||
|
// allowing early send again
|
||||||
|
(
|
||||||
|
true,
|
||||||
|
(last_rtcp_handle_time + t_rr).saturating_duration_since(now),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
debug!("Can't allow early RTCP again yet");
|
||||||
|
(false, Duration::ZERO)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !allow_early {
|
||||||
|
if next_rtcp_send_time - now < max_delay {
|
||||||
|
debug!("early RTCP not scheduled but regular RTCP scheduled time is soon enough");
|
||||||
|
RequestEarlyRtcpResult::Scheduled
|
||||||
|
} else {
|
||||||
|
debug!("early RTCP not scheduled and regular RTCP scheduled time is too late");
|
||||||
|
RequestEarlyRtcpResult::NotScheduled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
self.next_early_rtcp_time = Some(now + t_dither_max.mul_f64(rng.gen()) + offset);
|
||||||
|
debug!("early RTCP scheduled at {:?}", self.next_early_rtcp_time);
|
||||||
|
RequestEarlyRtcpResult::TimerReconsideration
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn schedule_bye(&mut self, reason: &str, now: Instant) {
|
pub fn schedule_bye(&mut self, reason: &str, now: Instant) {
|
||||||
|
@ -1008,10 +1274,6 @@ impl Session {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.n_members() <= 50 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for source in self.local_senders.values_mut() {
|
for source in self.local_senders.values_mut() {
|
||||||
source.mark_bye(reason);
|
source.mark_bye(reason);
|
||||||
}
|
}
|
||||||
|
@ -1019,6 +1281,10 @@ impl Session {
|
||||||
source.mark_bye(reason);
|
source.mark_bye(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.n_members() <= 50 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.bye_state = Some(ByeState {
|
self.bye_state = Some(ByeState {
|
||||||
members: 1,
|
members: 1,
|
||||||
pmembers: 1,
|
pmembers: 1,
|
||||||
|
@ -1028,15 +1294,27 @@ impl Session {
|
||||||
self.last_rtcp_sent_times.push_front(now);
|
self.last_rtcp_sent_times.push_front(now);
|
||||||
// FIXME: use actual BYE packet size
|
// FIXME: use actual BYE packet size
|
||||||
self.average_rtcp_size = 100;
|
self.average_rtcp_size = 100;
|
||||||
|
self.update_point_to_point();
|
||||||
|
let mut interval = self.rtcp_interval();
|
||||||
|
self.rtcp_interval = Some(interval);
|
||||||
|
// Handle T_rr_interval
|
||||||
|
if self.profile.is_feedback() && !self.min_rtcp_interval.is_zero() {
|
||||||
|
let new_interval = interval.max(rtcp_dither(self.min_rtcp_interval));
|
||||||
|
trace!("Updating interval from {interval:?} to {new_interval:?}");
|
||||||
|
interval = new_interval;
|
||||||
|
}
|
||||||
self.next_rtcp_send = RtcpTimeMembers {
|
self.next_rtcp_send = RtcpTimeMembers {
|
||||||
time: Some(self.next_rtcp_time(now)),
|
time: Some(now + interval),
|
||||||
p_members: self.n_members(),
|
p_members: self.n_members(),
|
||||||
};
|
};
|
||||||
|
trace!("next rtcp time {:?}", self.next_rtcp_send.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_rtcp_time(&self, now: Instant) -> Instant {
|
fn rtcp_interval(&self) -> Duration {
|
||||||
now + self
|
let interval = self
|
||||||
.calculated_rtcp_duration(!self.local_senders.is_empty() && self.bye_state.is_none())
|
.calculated_rtcp_duration(!self.local_senders.is_empty() && self.bye_state.is_none());
|
||||||
|
trace!("Calculated RTCP interval {interval:?}");
|
||||||
|
interval
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve a list of all ssrc's currently handled by this session
|
/// Retrieve a list of all ssrc's currently handled by this session
|
||||||
|
@ -1091,6 +1369,13 @@ fn generate_ssrc() -> u32 {
|
||||||
rng.gen::<u32>()
|
rng.gen::<u32>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rtcp_dither(dur: Duration) -> Duration {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
// need a factor in [0.5, 1.5]
|
||||||
|
let factor = rng.gen::<f64>();
|
||||||
|
dur.mul_f64(factor + 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use rtp_types::RtpPacketBuilder;
|
use rtp_types::RtpPacketBuilder;
|
||||||
|
@ -1160,7 +1445,7 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
fn generate_rtp_packet(ssrc: u32, seq_no: u16, rtp_ts: u32, payload_len: usize) -> Vec<u8> {
|
fn generate_rtp_packet(ssrc: u32, seq_no: u16, rtp_ts: u32, payload_len: usize) -> Vec<u8> {
|
||||||
init_logs();
|
init_logs();
|
||||||
let mut rtp_data = [0; 128];
|
let mut rtp_data = [0; 1200];
|
||||||
let payload = vec![1; payload_len];
|
let payload = vec![1; payload_len];
|
||||||
let len = RtpPacketBuilder::new()
|
let len = RtpPacketBuilder::new()
|
||||||
.payload_type(TEST_PT)
|
.payload_type(TEST_PT)
|
||||||
|
@ -1181,6 +1466,27 @@ pub(crate) mod tests {
|
||||||
(new_now, ntp_now + new_now.duration_since(old_now))
|
(new_now, ntp_now + new_now.duration_since(old_now))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_rtcp_packet(
|
||||||
|
session: &mut Session,
|
||||||
|
mut now: Instant,
|
||||||
|
mut ntp_now: SystemTime,
|
||||||
|
) -> (Vec<u8>, Instant, SystemTime) {
|
||||||
|
let mut ret = None;
|
||||||
|
while ret.is_none() {
|
||||||
|
ret = session.poll_rtcp_send(now, ntp_now);
|
||||||
|
if let Some(rtcp) = ret {
|
||||||
|
return (rtcp, now, ntp_now);
|
||||||
|
} else {
|
||||||
|
(now, ntp_now) = increment_rtcp_times(
|
||||||
|
now,
|
||||||
|
session.poll_rtcp_send_timeout(now).unwrap(),
|
||||||
|
ntp_now,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn send_new_ssrc() {
|
fn send_new_ssrc() {
|
||||||
init_logs();
|
init_logs();
|
||||||
|
@ -1262,10 +1568,8 @@ pub(crate) mod tests {
|
||||||
RecvReply::Passthrough
|
RecvReply::Passthrough
|
||||||
);
|
);
|
||||||
|
|
||||||
let (now, ntp_now) =
|
let (rtcp_data, _now, _ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
|
||||||
|
|
||||||
let rtcp_data = session.poll_rtcp_send(now, ntp_now).unwrap();
|
|
||||||
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
||||||
let mut n_rb_ssrcs = 0;
|
let mut n_rb_ssrcs = 0;
|
||||||
let mut found_sdes_cname = false;
|
let mut found_sdes_cname = false;
|
||||||
|
@ -1328,8 +1632,10 @@ pub(crate) mod tests {
|
||||||
// generate packets at the 'same time' as rtcp so some calculated timestamps will match
|
// generate packets at the 'same time' as rtcp so some calculated timestamps will match
|
||||||
let (now, ntp_now) =
|
let (now, ntp_now) =
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
||||||
|
// ensure that timer reconsideration will not suppress
|
||||||
|
let (now, ntp_now) = increment_rtcp_times(now, now + RTCP_MIN_REPORT_INTERVAL, ntp_now);
|
||||||
|
|
||||||
let rtp_data = generate_rtp_packet(ssrcs[0], 100, 4, 8);
|
let rtp_data = generate_rtp_packet(ssrcs[0], 100, 4, 1024);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
session.handle_send(&packet, now),
|
session.handle_send(&packet, now),
|
||||||
|
@ -1337,7 +1643,7 @@ pub(crate) mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
||||||
|
|
||||||
let rtp_data = generate_rtp_packet(ssrcs[1], 200, 4, 8);
|
let rtp_data = generate_rtp_packet(ssrcs[1], 200, 4, 1024);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
session.handle_send(&packet, now),
|
session.handle_send(&packet, now),
|
||||||
|
@ -1357,7 +1663,7 @@ pub(crate) mod tests {
|
||||||
}
|
}
|
||||||
// we sent 1 packet on each ssrc, rtcp should reflect that
|
// we sent 1 packet on each ssrc, rtcp should reflect that
|
||||||
assert_eq!(sr.packet_count(), 1);
|
assert_eq!(sr.packet_count(), 1);
|
||||||
assert_eq!(sr.octet_count() as usize, 8);
|
assert_eq!(sr.octet_count() as usize, 1024);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sr.ntp_timestamp(),
|
sr.ntp_timestamp(),
|
||||||
system_time_to_ntp_time_u64(ntp_now).as_u64()
|
system_time_to_ntp_time_u64(ntp_now).as_u64()
|
||||||
|
@ -1427,11 +1733,8 @@ pub(crate) mod tests {
|
||||||
vec![]
|
vec![]
|
||||||
);
|
);
|
||||||
|
|
||||||
// generate packets at the 'same time' as rtcp so some calculated timestamps will match
|
let (rtcp_data, _new_now, new_ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
let (new_now, new_ntp_now) =
|
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
|
||||||
|
|
||||||
let rtcp_data = session.poll_rtcp_send(new_now, new_ntp_now).unwrap();
|
|
||||||
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
||||||
for p in rtcp {
|
for p in rtcp {
|
||||||
match p {
|
match p {
|
||||||
|
@ -1479,9 +1782,11 @@ pub(crate) mod tests {
|
||||||
// get the next rtcp packet times and send at the same time
|
// get the next rtcp packet times and send at the same time
|
||||||
let (now, ntp_now) =
|
let (now, ntp_now) =
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
||||||
|
// ensure that timer reconsideration will not suppress
|
||||||
|
let (now, ntp_now) = increment_rtcp_times(now, now + RTCP_MIN_REPORT_INTERVAL, ntp_now);
|
||||||
|
|
||||||
// send from two ssrcs
|
// send from two ssrcs
|
||||||
let rtp_data = generate_rtp_packet(ssrcs[0], 100, 0, 4);
|
let rtp_data = generate_rtp_packet(ssrcs[0], 100, 0, 1024);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
session.handle_send(&packet, now),
|
session.handle_send(&packet, now),
|
||||||
|
@ -1489,7 +1794,7 @@ pub(crate) mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
||||||
|
|
||||||
let rtp_data = generate_rtp_packet(ssrcs[1], 200, 0, 4);
|
let rtp_data = generate_rtp_packet(ssrcs[1], 200, 0, 1024);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
session.handle_send(&packet, now),
|
session.handle_send(&packet, now),
|
||||||
|
@ -1535,11 +1840,8 @@ pub(crate) mod tests {
|
||||||
RecvReply::Passthrough
|
RecvReply::Passthrough
|
||||||
);
|
);
|
||||||
|
|
||||||
// get the next rtcp packet
|
let (rtcp_data, _now, _ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
let (now, ntp_now) =
|
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
|
||||||
|
|
||||||
let rtcp_data = session.poll_rtcp_send(now, ntp_now).unwrap();
|
|
||||||
trace!("rtcp data {rtcp_data:?}");
|
trace!("rtcp data {rtcp_data:?}");
|
||||||
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
||||||
let mut n_sr_ssrcs = 0;
|
let mut n_sr_ssrcs = 0;
|
||||||
|
@ -1580,11 +1882,8 @@ pub(crate) mod tests {
|
||||||
RecvReply::Passthrough
|
RecvReply::Passthrough
|
||||||
);
|
);
|
||||||
|
|
||||||
// get the next rtcp packet
|
let (rtcp_data, _now, _ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
let (now, ntp_now) =
|
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
|
||||||
|
|
||||||
let rtcp_data = session.poll_rtcp_send(now, ntp_now).unwrap();
|
|
||||||
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
||||||
for p in rtcp {
|
for p in rtcp {
|
||||||
trace!("{p:?}");
|
trace!("{p:?}");
|
||||||
|
@ -1620,11 +1919,8 @@ pub(crate) mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
||||||
|
|
||||||
// get the next rtcp packet
|
let (rtcp_data, now, ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
let (now, ntp_now) =
|
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
|
||||||
|
|
||||||
let rtcp_data = session.poll_rtcp_send(now, ntp_now).unwrap();
|
|
||||||
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
||||||
for p in rtcp {
|
for p in rtcp {
|
||||||
trace!("{p:?}");
|
trace!("{p:?}");
|
||||||
|
@ -1640,10 +1936,7 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
let mut seen_rr = false;
|
let mut seen_rr = false;
|
||||||
for _ in 0..=5 {
|
for _ in 0..=5 {
|
||||||
let (now, ntp_now) =
|
let (rtcp_data, _now, _ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
|
||||||
|
|
||||||
let rtcp_data = session.poll_rtcp_send(now, ntp_now).unwrap();
|
|
||||||
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
||||||
for p in rtcp {
|
for p in rtcp {
|
||||||
trace!("{p:?}");
|
trace!("{p:?}");
|
||||||
|
@ -1681,11 +1974,7 @@ pub(crate) mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
||||||
|
|
||||||
// get the next rtcp packet
|
let (rtcp_data, now, ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
let (now, ntp_now) =
|
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
|
||||||
|
|
||||||
let rtcp_data = session.poll_rtcp_send(now, ntp_now).unwrap();
|
|
||||||
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
||||||
|
|
||||||
for p in rtcp {
|
for p in rtcp {
|
||||||
|
@ -1845,9 +2134,7 @@ pub(crate) mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// send initial rtcp
|
// send initial rtcp
|
||||||
let (now, ntp_now) =
|
let (_rtcp_data, now, ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
|
||||||
let _rtcp_data = session.poll_rtcp_send(now, ntp_now).unwrap();
|
|
||||||
|
|
||||||
let rtcp = Compound::builder().add_packet(Bye::builder().add_source(ssrc));
|
let rtcp = Compound::builder().add_packet(Bye::builder().add_source(ssrc));
|
||||||
let mut data = vec![0; 128];
|
let mut data = vec![0; 128];
|
||||||
|
@ -1880,9 +2167,7 @@ pub(crate) mod tests {
|
||||||
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
||||||
|
|
||||||
// send initial rtcp
|
// send initial rtcp
|
||||||
let (now, ntp_now) =
|
let (_rtcp_data, now, ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
|
||||||
let _rtcp_data = session.poll_rtcp_send(now, ntp_now).unwrap();
|
|
||||||
|
|
||||||
let source = session.mut_local_send_source_by_ssrc(ssrc).unwrap();
|
let source = session.mut_local_send_source_by_ssrc(ssrc).unwrap();
|
||||||
source.mark_bye("Cya");
|
source.mark_bye("Cya");
|
||||||
|
@ -1891,9 +2176,7 @@ pub(crate) mod tests {
|
||||||
// data after bye should be dropped
|
// data after bye should be dropped
|
||||||
assert_eq!(session.handle_send(&packet, now), SendReply::Drop);
|
assert_eq!(session.handle_send(&packet, now), SendReply::Drop);
|
||||||
|
|
||||||
let (now, ntp_now) =
|
let (rtcp_data, _now, _ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
increment_rtcp_times(now, session.poll_rtcp_send_timeout(now).unwrap(), ntp_now);
|
|
||||||
let rtcp_data = session.poll_rtcp_send(now, ntp_now).unwrap();
|
|
||||||
|
|
||||||
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
||||||
let mut received_bye = false;
|
let mut received_bye = false;
|
||||||
|
@ -1914,4 +2197,156 @@ pub(crate) mod tests {
|
||||||
}
|
}
|
||||||
assert!(received_bye);
|
assert!(received_bye);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn early_rtcp() {
|
||||||
|
let mut session = Session::new();
|
||||||
|
session.set_pt_clock_rate(TEST_PT, TEST_CLOCK_RATE);
|
||||||
|
session.set_profile(RtpProfile::Avpf);
|
||||||
|
let now = Instant::now();
|
||||||
|
let ntp_now = SystemTime::now();
|
||||||
|
let ssrc = 0x11223344;
|
||||||
|
let send_ssrc = 0x55667788;
|
||||||
|
let send2_ssrc = 0x99aabbcc;
|
||||||
|
|
||||||
|
let rtp_data = generate_rtp_packet(ssrc, 500, 0, 4);
|
||||||
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
|
session_recv_first_packet_disable_probation(&mut session, &packet, now);
|
||||||
|
assert_eq!(
|
||||||
|
session.handle_recv(&packet, None, now),
|
||||||
|
RecvReply::Passthrough
|
||||||
|
);
|
||||||
|
|
||||||
|
let rtp_data = generate_rtp_packet(send_ssrc, 500, 0, 4);
|
||||||
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
session.handle_send(&packet, now),
|
||||||
|
SendReply::NewSsrc(send_ssrc, TEST_PT)
|
||||||
|
);
|
||||||
|
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
||||||
|
|
||||||
|
let rtp_data = generate_rtp_packet(send2_ssrc, 500, 0, 4);
|
||||||
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
session.handle_send(&packet, now),
|
||||||
|
SendReply::NewSsrc(send2_ssrc, TEST_PT)
|
||||||
|
);
|
||||||
|
assert_eq!(session.handle_send(&packet, now), SendReply::Passthrough);
|
||||||
|
|
||||||
|
// complete first regular rtcp
|
||||||
|
let (_rtcp_data, now, _ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
|
|
||||||
|
let source = session.mut_local_send_source_by_ssrc(send_ssrc).unwrap();
|
||||||
|
source.set_sdes_item(SdesItem::NAME, b"name");
|
||||||
|
|
||||||
|
// request early rtcp. The resulting RTCP packet, will only have the CNAME sdes item and a
|
||||||
|
// single SR/RR.
|
||||||
|
session.request_early_rtcp(now, RTCP_MIN_REPORT_INTERVAL);
|
||||||
|
assert!(session.next_early_rtcp_time.is_some());
|
||||||
|
|
||||||
|
let (rtcp_data, _now, _ntp_now) = next_rtcp_packet(&mut session, now, ntp_now);
|
||||||
|
let rtcp = Compound::parse(&rtcp_data).unwrap();
|
||||||
|
let mut n_sr_ssrc = 0;
|
||||||
|
let mut n_sdes = 0;
|
||||||
|
for p in rtcp {
|
||||||
|
trace!("{p:?}");
|
||||||
|
match p {
|
||||||
|
Ok(Packet::Sr(sr)) => {
|
||||||
|
assert!([send_ssrc, send2_ssrc].contains(&sr.ssrc()));
|
||||||
|
n_sr_ssrc += 1;
|
||||||
|
}
|
||||||
|
Ok(Packet::Sdes(sdes)) => {
|
||||||
|
assert_eq!(n_sdes, 0);
|
||||||
|
for chunk in sdes.chunks() {
|
||||||
|
assert!([send_ssrc, send2_ssrc].contains(&chunk.ssrc()));
|
||||||
|
for item in chunk.items() {
|
||||||
|
assert_eq!(item.type_(), SdesItem::CNAME);
|
||||||
|
n_sdes += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(n_sdes, 2);
|
||||||
|
assert_eq!(n_sr_ssrc, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn point_to_point() {
|
||||||
|
let mut session = Session::new();
|
||||||
|
assert!(session.is_point_to_point);
|
||||||
|
session.set_pt_clock_rate(TEST_PT, TEST_CLOCK_RATE);
|
||||||
|
let now = Instant::now();
|
||||||
|
let ntp_now = SystemTime::now();
|
||||||
|
let recv_ssrc = 0x11223344;
|
||||||
|
let recv2_ssrc = 0x55667788;
|
||||||
|
let from = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
|
||||||
|
// single remote cname is still point to point
|
||||||
|
let mut data = vec![0; 128];
|
||||||
|
let len = Compound::builder()
|
||||||
|
.add_packet(
|
||||||
|
Sdes::builder().add_chunk(
|
||||||
|
SdesChunk::builder(recv_ssrc)
|
||||||
|
.add_item(SdesItem::builder(SdesItem::CNAME, "cname1")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.write_into(&mut data)
|
||||||
|
.unwrap();
|
||||||
|
let rtcp = Compound::parse(&data[..len]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
session.handle_rtcp_recv(rtcp, len, Some(from), now, ntp_now),
|
||||||
|
vec![RtcpRecvReply::NewSsrc(recv_ssrc)]
|
||||||
|
);
|
||||||
|
assert!(session.is_point_to_point);
|
||||||
|
|
||||||
|
// multiple ssrc but same cname is still point to point
|
||||||
|
let mut data = vec![0; 128];
|
||||||
|
let len = Compound::builder()
|
||||||
|
.add_packet(
|
||||||
|
Sdes::builder().add_chunk(
|
||||||
|
SdesChunk::builder(recv_ssrc)
|
||||||
|
.add_item(SdesItem::builder(SdesItem::CNAME, "cname1")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.add_packet(
|
||||||
|
Sdes::builder().add_chunk(
|
||||||
|
SdesChunk::builder(recv2_ssrc)
|
||||||
|
.add_item(SdesItem::builder(SdesItem::CNAME, "cname1")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.write_into(&mut data)
|
||||||
|
.unwrap();
|
||||||
|
let rtcp = Compound::parse(&data[..len]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
session.handle_rtcp_recv(rtcp, len, Some(from), now, ntp_now),
|
||||||
|
vec![RtcpRecvReply::NewSsrc(recv2_ssrc)]
|
||||||
|
);
|
||||||
|
assert!(session.is_point_to_point);
|
||||||
|
|
||||||
|
// multiple ssrc and different cname is NOT point to point
|
||||||
|
let mut data = vec![0; 128];
|
||||||
|
let len = Compound::builder()
|
||||||
|
.add_packet(
|
||||||
|
Sdes::builder().add_chunk(
|
||||||
|
SdesChunk::builder(recv_ssrc)
|
||||||
|
.add_item(SdesItem::builder(SdesItem::CNAME, "cname1")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.add_packet(
|
||||||
|
Sdes::builder().add_chunk(
|
||||||
|
SdesChunk::builder(recv2_ssrc)
|
||||||
|
.add_item(SdesItem::builder(SdesItem::CNAME, "cname2")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.write_into(&mut data)
|
||||||
|
.unwrap();
|
||||||
|
let rtcp = Compound::parse(&data[..len]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
session.handle_rtcp_recv(rtcp, len, Some(from), now, ntp_now),
|
||||||
|
vec![]
|
||||||
|
);
|
||||||
|
assert!(!session.is_point_to_point);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -722,6 +722,11 @@ impl RemoteSendSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the SDES items currently received for this remote sender
|
||||||
|
pub fn sdes(&self) -> &HashMap<u8, String> {
|
||||||
|
&self.source.sdes
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn set_last_activity(&mut self, time: Instant) {
|
pub(crate) fn set_last_activity(&mut self, time: Instant) {
|
||||||
self.source.last_activity = time;
|
self.source.last_activity = time;
|
||||||
}
|
}
|
||||||
|
@ -1004,6 +1009,11 @@ impl RemoteReceiveSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the SDES items currently received for this remote receiver
|
||||||
|
pub fn sdes(&self) -> &HashMap<u8, String> {
|
||||||
|
&self.source.sdes
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve the last time that activity was seen on this source
|
/// Retrieve the last time that activity was seen on this source
|
||||||
pub fn last_activity(&self) -> Instant {
|
pub fn last_activity(&self) -> Instant {
|
||||||
self.source.last_activity
|
self.source.last_activity
|
||||||
|
|
Loading…
Reference in a new issue