webrtc: allow resolution and framerate input changes

Some changes do not require a WebRTC renegotiation so we can allow
those.

Fix #515

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1498>
This commit is contained in:
Guillaume Desmottes 2024-03-14 13:36:11 +01:00
parent eb49459937
commit 96337d5234

View file

@ -235,6 +235,8 @@ pub struct VideoEncoder {
session_id: String, session_id: String,
mitigation_mode: WebRTCSinkMitigationMode, mitigation_mode: WebRTCSinkMitigationMode,
pub transceiver: gst_webrtc::WebRTCRTPTransceiver, pub transceiver: gst_webrtc::WebRTCRTPTransceiver,
/// name of the sink pad feeding this encoder
stream_name: String,
} }
struct Session { struct Session {
@ -920,6 +922,7 @@ impl VideoEncoder {
session_id: &str, session_id: &str,
codec_name: &str, codec_name: &str,
transceiver: gst_webrtc::WebRTCRTPTransceiver, transceiver: gst_webrtc::WebRTCRTPTransceiver,
stream_name: String,
) -> Option<Self> { ) -> Option<Self> {
let halved_framerate = video_info.fps().mul(gst::Fraction::new(1, 2)); let halved_framerate = video_info.fps().mul(gst::Fraction::new(1, 2));
Some(Self { Some(Self {
@ -938,6 +941,7 @@ impl VideoEncoder {
session_id: session_id.to_string(), session_id: session_id.to_string(),
mitigation_mode: WebRTCSinkMitigationMode::NONE, mitigation_mode: WebRTCSinkMitigationMode::NONE,
transceiver, transceiver,
stream_name,
}) })
} }
@ -1322,6 +1326,7 @@ impl Session {
&self.id, &self.id,
codec.caps.structure(0).unwrap().name(), codec.caps.structure(0).unwrap().name(),
transceiver, transceiver,
stream_name.clone(),
) { ) {
match self.cc_info.heuristic { match self.cc_info.heuristic {
WebRTCSinkCongestionControl::Disabled => { WebRTCSinkCongestionControl::Disabled => {
@ -3459,6 +3464,37 @@ impl BaseWebRTCSink {
) )
} }
/// Check if the caps of a sink pad can be changed from `current` to `new` without requiring a WebRTC renegotiation
fn input_caps_change_allowed(&self, current: &gst::CapsRef, new: &gst::CapsRef) -> bool {
let Some(current) = current.structure(0) else {
return false;
};
let Some(new) = new.structure(0) else {
return false;
};
if current.name() != new.name() {
return false;
}
let mut current = current.to_owned();
let mut new = new.to_owned();
// Allow changes of fields which should not be part of the SDP, and so can be updated without requiring
// a renegotiation.
let caps_type = current.name();
if caps_type.starts_with("video/") {
const VIDEO_ALLOWED_CHANGES: &[&str] = &["width", "height", "framerate"];
current.remove_fields(VIDEO_ALLOWED_CHANGES.iter().copied());
new.remove_fields(VIDEO_ALLOWED_CHANGES.iter().copied());
} else if caps_type.starts_with("audio/") {
// TODO
}
current == new
}
fn sink_event( fn sink_event(
&self, &self,
pad: &gst::Pad, pad: &gst::Pad,
@ -3469,10 +3505,7 @@ impl BaseWebRTCSink {
if let EventView::Caps(e) = event.view() { if let EventView::Caps(e) = event.view() {
if let Some(caps) = pad.current_caps() { if let Some(caps) = pad.current_caps() {
if caps.is_strictly_equal(e.caps()) { if !self.input_caps_change_allowed(&caps, e.caps()) {
// Nothing changed
return true;
} else {
gst::error!( gst::error!(
CAT, CAT,
obj: pad, obj: pad,
@ -3482,29 +3515,40 @@ impl BaseWebRTCSink {
); );
return false; return false;
} }
} else { }
gst::info!(CAT, obj: pad, "Received caps event {:?}", e); gst::info!(CAT, obj: pad, "Received caps event {:?}", e);
self.state let mut state = self.state.lock().unwrap();
.lock()
.unwrap() state.streams.iter_mut().for_each(|(_, stream)| {
.streams if stream.sink_pad.upcast_ref::<gst::Pad>() == pad {
.iter_mut() // We do not want VideoInfo to consider max-framerate
.for_each(|(_, stream)| { // when computing fps, so we strip it away here
if stream.sink_pad.upcast_ref::<gst::Pad>() == pad { let mut caps = e.caps().to_owned();
// We do not want VideoInfo to consider max-framerate {
// when computing fps, so we strip it away here let mut_caps = caps.get_mut().unwrap();
let mut caps = e.caps().to_owned(); if let Some(s) = mut_caps.structure_mut(0) {
{ if s.has_name("video/x-raw") {
let mut_caps = caps.get_mut().unwrap(); s.remove_field("max-framerate");
if let Some(s) = mut_caps.structure_mut(0) {
if s.has_name("video/x-raw") {
s.remove_field("max-framerate");
}
}
} }
stream.in_caps = Some(caps.to_owned());
} }
}
stream.in_caps = Some(caps.to_owned());
}
});
if let Ok(video_info) = gst_video::VideoInfo::from_caps(e.caps()) {
// update video encoder info used when downscaling/downsampling the input
let stream_name = pad.name().to_string();
state
.sessions
.values_mut()
.flat_map(|session| session.unwrap_mut().encoders.iter_mut())
.filter(|encoder| encoder.stream_name == stream_name)
.for_each(|encoder| {
encoder.halved_framerate = video_info.fps().mul(gst::Fraction::new(1, 2));
encoder.video_info = video_info.clone();
}); });
} }
} }