mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-02-18 13:55:22 +00:00
plugin: Implement bandwidth estimator based on the Google Congestion Control algorithm
See https://datatracker.ietf.org/doc/html/draft-ietf-rmcat-gcc-02 This commit implements the bandwidth estimation as a GStreamer element that is then used in webrtcbin through the new `request-bandwidth-estimator` signal. This keeps our Homegrown congestion controller but removes the possibility to switch CC algorithm at runtime.
This commit is contained in:
parent
287e76847a
commit
64f664c859
7 changed files with 1554 additions and 100 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1856,6 +1856,7 @@ dependencies = [
|
||||||
"async-native-tls 0.4.0",
|
"async-native-tls 0.4.0",
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"futures",
|
"futures",
|
||||||
|
|
|
@ -17,6 +17,7 @@ gst-sdp = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package
|
||||||
gst-rtp = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-rtp", features = ["v1_20"] }
|
gst-rtp = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-rtp", features = ["v1_20"] }
|
||||||
gst-utils = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-utils" }
|
gst-utils = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-utils" }
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
|
chrono = { version = "0.4", default-features = false }
|
||||||
smallvec = "1"
|
smallvec = "1"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
|
1366
plugins/src/gcc/imp.rs
Normal file
1366
plugins/src/gcc/imp.rs
Normal file
File diff suppressed because it is too large
Load diff
16
plugins/src/gcc/mod.rs
Normal file
16
plugins/src/gcc/mod.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use gst::glib;
|
||||||
|
use gst::prelude::*;
|
||||||
|
mod imp;
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct BandwidthEstimator(ObjectSubclass<imp::BandwidthEstimator>) @extends gst::Element, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
gst::Element::register(
|
||||||
|
Some(plugin),
|
||||||
|
"rtpgccbwe",
|
||||||
|
gst::Rank::None,
|
||||||
|
BandwidthEstimator::static_type(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
|
|
||||||
|
pub mod gcc;
|
||||||
mod signaller;
|
mod signaller;
|
||||||
pub mod webrtcsink;
|
pub mod webrtcsink;
|
||||||
|
|
||||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
webrtcsink::register(plugin)?;
|
webrtcsink::register(plugin)?;
|
||||||
|
gcc::register(plugin)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,19 +47,23 @@ const DEFAULT_CONGESTION_CONTROL: WebRTCSinkCongestionControl =
|
||||||
const DEFAULT_DO_FEC: bool = true;
|
const DEFAULT_DO_FEC: bool = true;
|
||||||
const DEFAULT_DO_RETRANSMISSION: bool = true;
|
const DEFAULT_DO_RETRANSMISSION: bool = true;
|
||||||
const DEFAULT_ENABLE_DATA_CHANNEL_NAVIGATION: bool = false;
|
const DEFAULT_ENABLE_DATA_CHANNEL_NAVIGATION: bool = false;
|
||||||
|
|
||||||
const DEFAULT_START_BITRATE: u32 = 2048000;
|
const DEFAULT_START_BITRATE: u32 = 2048000;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct CCInfo {
|
||||||
|
heuristic: WebRTCSinkCongestionControl,
|
||||||
|
min_bitrate: u32,
|
||||||
|
max_bitrate: u32,
|
||||||
|
start_bitrate: u32,
|
||||||
|
}
|
||||||
|
|
||||||
/// User configuration
|
/// User configuration
|
||||||
struct Settings {
|
struct Settings {
|
||||||
video_caps: gst::Caps,
|
video_caps: gst::Caps,
|
||||||
audio_caps: gst::Caps,
|
audio_caps: gst::Caps,
|
||||||
turn_server: Option<String>,
|
turn_server: Option<String>,
|
||||||
stun_server: Option<String>,
|
stun_server: Option<String>,
|
||||||
cc_heuristic: WebRTCSinkCongestionControl,
|
cc_info: CCInfo,
|
||||||
min_bitrate: u32,
|
|
||||||
max_bitrate: u32,
|
|
||||||
start_bitrate: u32,
|
|
||||||
do_fec: bool,
|
do_fec: bool,
|
||||||
do_retransmission: bool,
|
do_retransmission: bool,
|
||||||
enable_data_channel_navigation: bool,
|
enable_data_channel_navigation: bool,
|
||||||
|
@ -114,7 +118,7 @@ struct WebRTCPad {
|
||||||
/// name in order to provide a unified set / get bitrate API, also
|
/// name in order to provide a unified set / get bitrate API, also
|
||||||
/// tracks a raw capsfilter used to resize / decimate the input video
|
/// tracks a raw capsfilter used to resize / decimate the input video
|
||||||
/// stream according to the bitrate, thresholds hardcoded for now
|
/// stream according to the bitrate, thresholds hardcoded for now
|
||||||
struct VideoEncoder {
|
pub struct VideoEncoder {
|
||||||
factory_name: String,
|
factory_name: String,
|
||||||
codec_name: String,
|
codec_name: String,
|
||||||
element: gst::Element,
|
element: gst::Element,
|
||||||
|
@ -186,19 +190,18 @@ enum ControllerType {
|
||||||
// Running the "loss based controller"
|
// Running the "loss based controller"
|
||||||
Loss,
|
Loss,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Consumer {
|
struct Consumer {
|
||||||
pipeline: gst::Pipeline,
|
pipeline: gst::Pipeline,
|
||||||
webrtcbin: gst::Element,
|
webrtcbin: gst::Element,
|
||||||
webrtc_pads: HashMap<u32, WebRTCPad>,
|
webrtc_pads: HashMap<u32, WebRTCPad>,
|
||||||
peer_id: String,
|
peer_id: String,
|
||||||
encoders: Vec<VideoEncoder>,
|
encoders: Vec<VideoEncoder>,
|
||||||
/// None if congestion control was disabled
|
// Our Homegrown controller
|
||||||
congestion_controller: Option<CongestionController>,
|
congestion_controller: Option<CongestionController>,
|
||||||
sdp: Option<gst_sdp::SDPMessage>,
|
sdp: Option<gst_sdp::SDPMessage>,
|
||||||
stats: gst::Structure,
|
stats: gst::Structure,
|
||||||
|
|
||||||
max_bitrate: u32,
|
cc_info: CCInfo,
|
||||||
|
|
||||||
links: HashMap<u32, gst_utils::ConsumptionLink>,
|
links: HashMap<u32, gst_utils::ConsumptionLink>,
|
||||||
stats_sigid: Option<glib::SignalHandlerId>,
|
stats_sigid: Option<glib::SignalHandlerId>,
|
||||||
|
@ -308,12 +311,14 @@ impl Default for Settings {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| gst::Structure::new_empty(s))
|
.map(|s| gst::Structure::new_empty(s))
|
||||||
.collect::<gst::Caps>(),
|
.collect::<gst::Caps>(),
|
||||||
cc_heuristic: WebRTCSinkCongestionControl::Homegrown,
|
|
||||||
stun_server: DEFAULT_STUN_SERVER.map(String::from),
|
stun_server: DEFAULT_STUN_SERVER.map(String::from),
|
||||||
turn_server: None,
|
turn_server: None,
|
||||||
min_bitrate: DEFAULT_MIN_BITRATE,
|
cc_info: CCInfo {
|
||||||
max_bitrate: DEFAULT_MAX_BITRATE,
|
heuristic: WebRTCSinkCongestionControl::Homegrown,
|
||||||
start_bitrate: DEFAULT_START_BITRATE,
|
min_bitrate: DEFAULT_MIN_BITRATE,
|
||||||
|
max_bitrate: (DEFAULT_MAX_BITRATE as f64 * 1.5) as u32,
|
||||||
|
start_bitrate: DEFAULT_START_BITRATE,
|
||||||
|
},
|
||||||
do_fec: DEFAULT_DO_FEC,
|
do_fec: DEFAULT_DO_FEC,
|
||||||
do_retransmission: DEFAULT_DO_RETRANSMISSION,
|
do_retransmission: DEFAULT_DO_RETRANSMISSION,
|
||||||
enable_data_channel_navigation: DEFAULT_ENABLE_DATA_CHANNEL_NAVIGATION,
|
enable_data_channel_navigation: DEFAULT_ENABLE_DATA_CHANNEL_NAVIGATION,
|
||||||
|
@ -577,13 +582,15 @@ fn setup_encoding(
|
||||||
Ok((enc, conv_filter, pay))
|
Ok((enc, conv_filter, pay))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_transport_stats(stats: &gst::StructureRef) -> Option<gst::Structure> {
|
fn lookup_twcc_stats(stats: &gst::StructureRef) -> Option<gst::Structure> {
|
||||||
for (_, field_value) in stats {
|
for (_, field_value) in stats {
|
||||||
if let Ok(s) = field_value.get::<gst::Structure>() {
|
if let Ok(s) = field_value.get::<gst::Structure>() {
|
||||||
if let Ok(type_) = s.get::<gst_webrtc::WebRTCStatsType>("type") {
|
if let Ok(type_) = s.get::<gst_webrtc::WebRTCStatsType>("type") {
|
||||||
if type_ == gst_webrtc::WebRTCStatsType::Transport && s.has_field("gst-twcc-stats")
|
if (type_ == gst_webrtc::WebRTCStatsType::Transport
|
||||||
|
|| type_ == gst_webrtc::WebRTCStatsType::CandidatePair)
|
||||||
|
&& s.has_field("gst-twcc-stats")
|
||||||
{
|
{
|
||||||
return Some(s);
|
return Some(s.get::<gst::Structure>("gst-twcc-stats").unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -616,7 +623,7 @@ impl VideoEncoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bitrate(&self) -> i32 {
|
pub fn bitrate(&self) -> i32 {
|
||||||
match self.factory_name.as_str() {
|
match self.factory_name.as_str() {
|
||||||
"vp8enc" | "vp9enc" => self.element.property::<i32>("target-bitrate"),
|
"vp8enc" | "vp9enc" => self.element.property::<i32>("target-bitrate"),
|
||||||
"x264enc" | "nvh264enc" | "vaapih264enc" | "vaapivp8enc" => {
|
"x264enc" | "nvh264enc" | "vaapih264enc" | "vaapivp8enc" => {
|
||||||
|
@ -626,7 +633,7 @@ impl VideoEncoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scale_height_round_2(&self, height: i32) -> i32 {
|
pub fn scale_height_round_2(&self, height: i32) -> i32 {
|
||||||
let ratio = gst_video::calculate_display_ratio(
|
let ratio = gst_video::calculate_display_ratio(
|
||||||
self.video_info.width(),
|
self.video_info.width(),
|
||||||
self.video_info.height(),
|
self.video_info.height(),
|
||||||
|
@ -640,7 +647,7 @@ impl VideoEncoder {
|
||||||
(width + 1) & !1
|
(width + 1) & !1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_bitrate(&mut self, element: &super::WebRTCSink, bitrate: i32) {
|
pub fn set_bitrate(&mut self, element: &super::WebRTCSink, bitrate: i32) {
|
||||||
match self.factory_name.as_str() {
|
match self.factory_name.as_str() {
|
||||||
"vp8enc" | "vp9enc" => self.element.set_property("target-bitrate", bitrate),
|
"vp8enc" | "vp9enc" => self.element.set_property("target-bitrate", bitrate),
|
||||||
"x264enc" | "nvh264enc" | "vaapih264enc" | "vaapivp8enc" => self
|
"x264enc" | "nvh264enc" | "vaapih264enc" | "vaapivp8enc" => self
|
||||||
|
@ -951,9 +958,7 @@ impl CongestionController {
|
||||||
stats: &gst::StructureRef,
|
stats: &gst::StructureRef,
|
||||||
encoders: &mut Vec<VideoEncoder>,
|
encoders: &mut Vec<VideoEncoder>,
|
||||||
) {
|
) {
|
||||||
if let Some(twcc_stats) = lookup_transport_stats(stats).and_then(|transport_stats| {
|
if let Some(twcc_stats) = lookup_twcc_stats(stats) {
|
||||||
transport_stats.get::<gst::Structure>("gst-twcc-stats").ok()
|
|
||||||
}) {
|
|
||||||
let op = self.update_delay(element, &twcc_stats, self.lookup_rtt(stats));
|
let op = self.update_delay(element, &twcc_stats, self.lookup_rtt(stats));
|
||||||
self.apply_control_op(element, encoders, op, ControllerType::Delay);
|
self.apply_control_op(element, encoders, op, ControllerType::Delay);
|
||||||
}
|
}
|
||||||
|
@ -1024,10 +1029,14 @@ impl CongestionController {
|
||||||
if target_bitrate != prev_bitrate {
|
if target_bitrate != prev_bitrate {
|
||||||
gst::info!(
|
gst::info!(
|
||||||
CAT,
|
CAT,
|
||||||
"{:?} {} => {}",
|
"{:?} {} => {} | on delay {} - on loss {} | min {} - max {}",
|
||||||
control_op,
|
control_op,
|
||||||
human_bytes::human_bytes(prev_bitrate),
|
human_bytes::human_bytes(prev_bitrate),
|
||||||
human_bytes::human_bytes(target_bitrate)
|
human_bytes::human_bytes(target_bitrate),
|
||||||
|
human_bytes::human_bytes(self.target_bitrate_on_delay),
|
||||||
|
human_bytes::human_bytes(self.target_bitrate_on_loss),
|
||||||
|
human_bytes::human_bytes(self.min_bitrate),
|
||||||
|
human_bytes::human_bytes(self.max_bitrate),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1123,16 +1132,16 @@ impl Consumer {
|
||||||
webrtcbin: gst::Element,
|
webrtcbin: gst::Element,
|
||||||
peer_id: String,
|
peer_id: String,
|
||||||
congestion_controller: Option<CongestionController>,
|
congestion_controller: Option<CongestionController>,
|
||||||
max_bitrate: u32,
|
cc_info: CCInfo,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pipeline,
|
pipeline,
|
||||||
webrtcbin,
|
webrtcbin,
|
||||||
peer_id,
|
peer_id,
|
||||||
|
cc_info,
|
||||||
congestion_controller,
|
congestion_controller,
|
||||||
max_bitrate,
|
|
||||||
sdp: None,
|
|
||||||
stats: gst::Structure::new_empty("application/x-webrtc-stats"),
|
stats: gst::Structure::new_empty("application/x-webrtc-stats"),
|
||||||
|
sdp: None,
|
||||||
webrtc_pads: HashMap::new(),
|
webrtc_pads: HashMap::new(),
|
||||||
encoders: Vec::new(),
|
encoders: Vec::new(),
|
||||||
links: HashMap::new(),
|
links: HashMap::new(),
|
||||||
|
@ -1326,16 +1335,27 @@ impl Consumer {
|
||||||
transceiver,
|
transceiver,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(congestion_controller) = self.congestion_controller.as_mut() {
|
match self.cc_info.heuristic {
|
||||||
congestion_controller.target_bitrate_on_delay += enc.bitrate();
|
WebRTCSinkCongestionControl::Disabled => {
|
||||||
congestion_controller.target_bitrate_on_loss =
|
// If congestion control is disabled, we simply use the highest
|
||||||
congestion_controller.target_bitrate_on_delay;
|
// known "safe" value for the bitrate.
|
||||||
enc.transceiver.set_property("fec-percentage", 0u32);
|
enc.set_bitrate(element, (self.cc_info.max_bitrate as f64 / 1.5) as i32);
|
||||||
} else {
|
enc.transceiver.set_property("fec-percentage", 50u32);
|
||||||
/* If congestion control is disabled, we simply use the highest
|
}
|
||||||
* known "safe" value for the bitrate. */
|
WebRTCSinkCongestionControl::Homegrown => {
|
||||||
enc.set_bitrate(element, self.max_bitrate as i32);
|
if let Some(congestion_controller) = self.congestion_controller.as_mut() {
|
||||||
enc.transceiver.set_property("fec-percentage", 50u32);
|
congestion_controller.target_bitrate_on_delay += enc.bitrate();
|
||||||
|
congestion_controller.target_bitrate_on_loss =
|
||||||
|
congestion_controller.target_bitrate_on_delay;
|
||||||
|
enc.transceiver.set_property("fec-percentage", 0u32);
|
||||||
|
} else {
|
||||||
|
/* If congestion control is disabled, we simply use the highest
|
||||||
|
* known "safe" value for the bitrate. */
|
||||||
|
enc.set_bitrate(element, self.cc_info.max_bitrate as i32);
|
||||||
|
enc.transceiver.set_property("fec-percentage", 50u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => enc.transceiver.set_property("fec-percentage", 0u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.encoders.push(enc);
|
self.encoders.push(enc);
|
||||||
|
@ -1702,21 +1722,21 @@ impl WebRTCSink {
|
||||||
) -> Result<(), WebRTCSinkError> {
|
) -> Result<(), WebRTCSinkError> {
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
|
let peer_id = peer_id.to_string();
|
||||||
|
|
||||||
if state.consumers.contains_key(peer_id) {
|
if state.consumers.contains_key(&peer_id) {
|
||||||
return Err(WebRTCSinkError::DuplicateConsumerId(peer_id.to_string()));
|
return Err(WebRTCSinkError::DuplicateConsumerId(peer_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
gst::info!(CAT, obj: element, "Adding consumer {}", peer_id);
|
gst::info!(CAT, obj: element, "Adding consumer {}", peer_id);
|
||||||
|
|
||||||
let pipeline = gst::Pipeline::new(Some(&format!("consumer-pipeline-{}", peer_id)));
|
let pipeline = gst::Pipeline::new(Some(&format!("consumer-pipeline-{}", peer_id)));
|
||||||
|
|
||||||
let webrtcbin = make_element("webrtcbin", None).map_err(|err| {
|
let webrtcbin = make_element("webrtcbin", Some(&format!("webrtcbin-{}", peer_id)))
|
||||||
WebRTCSinkError::ConsumerPipelineError {
|
.map_err(|err| WebRTCSinkError::ConsumerPipelineError {
|
||||||
peer_id: peer_id.to_string(),
|
peer_id: peer_id.clone(),
|
||||||
details: err.to_string(),
|
details: err.to_string(),
|
||||||
}
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
webrtcbin.set_property_from_str("bundle-policy", "max-bundle");
|
webrtcbin.set_property_from_str("bundle-policy", "max-bundle");
|
||||||
|
|
||||||
|
@ -1728,10 +1748,54 @@ impl WebRTCSink {
|
||||||
webrtcbin.set_property("turn-server", turn_server);
|
webrtcbin.set_property("turn-server", turn_server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match settings.cc_info.heuristic {
|
||||||
|
WebRTCSinkCongestionControl::GoogleCongestionControl => {
|
||||||
|
let cc_info = settings.cc_info;
|
||||||
|
webrtcbin.connect_closure(
|
||||||
|
"request-aux-sender",
|
||||||
|
false,
|
||||||
|
glib::closure!(@watch element, @strong peer_id
|
||||||
|
=> move |_webrtcbin: gst::Element, _transport: gst::Object| {
|
||||||
|
|
||||||
|
let cc = match gst::ElementFactory::make("rtpgccbwe", None) {
|
||||||
|
Err(err) => {
|
||||||
|
glib::g_warning!("webrtcsink",
|
||||||
|
"The `rtpgccbwe` element is not available \
|
||||||
|
not doing any congestion control: {err:?}"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
},
|
||||||
|
Ok(e) => {
|
||||||
|
e.set_properties(&[
|
||||||
|
("min-bitrate", &cc_info.min_bitrate),
|
||||||
|
("estimated-bitrate", &cc_info.start_bitrate),
|
||||||
|
("max-bitrate", &cc_info.max_bitrate),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// TODO: Bind properties with @element's
|
||||||
|
|
||||||
|
e
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cc.connect_notify(Some("estimated-bitrate"),
|
||||||
|
glib::clone!(@weak element, @strong peer_id
|
||||||
|
=> move |bwe, pspec| {
|
||||||
|
element.imp().set_bitrate(&element, &peer_id,
|
||||||
|
bwe.property::<u32>(pspec.name()));
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
Some(cc)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pipeline.add(&webrtcbin).unwrap();
|
pipeline.add(&webrtcbin).unwrap();
|
||||||
|
|
||||||
let element_clone = element.downgrade();
|
let element_clone = element.downgrade();
|
||||||
let peer_id_clone = peer_id.to_owned();
|
let peer_id_clone = peer_id.clone();
|
||||||
webrtcbin.connect("on-ice-candidate", false, move |values| {
|
webrtcbin.connect("on-ice-candidate", false, move |values| {
|
||||||
if let Some(element) = element_clone.upgrade() {
|
if let Some(element) = element_clone.upgrade() {
|
||||||
let this = Self::from_instance(&element);
|
let this = Self::from_instance(&element);
|
||||||
|
@ -1748,7 +1812,7 @@ impl WebRTCSink {
|
||||||
});
|
});
|
||||||
|
|
||||||
let element_clone = element.downgrade();
|
let element_clone = element.downgrade();
|
||||||
let peer_id_clone = peer_id.to_owned();
|
let peer_id_clone = peer_id.clone();
|
||||||
webrtcbin.connect_notify(Some("connection-state"), move |webrtcbin, _pspec| {
|
webrtcbin.connect_notify(Some("connection-state"), move |webrtcbin, _pspec| {
|
||||||
if let Some(element) = element_clone.upgrade() {
|
if let Some(element) = element_clone.upgrade() {
|
||||||
let state =
|
let state =
|
||||||
|
@ -1779,7 +1843,7 @@ impl WebRTCSink {
|
||||||
});
|
});
|
||||||
|
|
||||||
let element_clone = element.downgrade();
|
let element_clone = element.downgrade();
|
||||||
let peer_id_clone = peer_id.to_owned();
|
let peer_id_clone = peer_id.clone();
|
||||||
webrtcbin.connect_notify(Some("ice-connection-state"), move |webrtcbin, _pspec| {
|
webrtcbin.connect_notify(Some("ice-connection-state"), move |webrtcbin, _pspec| {
|
||||||
if let Some(element) = element_clone.upgrade() {
|
if let Some(element) = element_clone.upgrade() {
|
||||||
let state = webrtcbin
|
let state = webrtcbin
|
||||||
|
@ -1826,7 +1890,7 @@ impl WebRTCSink {
|
||||||
});
|
});
|
||||||
|
|
||||||
let element_clone = element.downgrade();
|
let element_clone = element.downgrade();
|
||||||
let peer_id_clone = peer_id.to_owned();
|
let peer_id_clone = peer_id.clone();
|
||||||
webrtcbin.connect_notify(Some("ice-gathering-state"), move |webrtcbin, _pspec| {
|
webrtcbin.connect_notify(Some("ice-gathering-state"), move |webrtcbin, _pspec| {
|
||||||
let state =
|
let state =
|
||||||
webrtcbin.property::<gst_webrtc::WebRTCICEGatheringState>("ice-gathering-state");
|
webrtcbin.property::<gst_webrtc::WebRTCICEGatheringState>("ice-gathering-state");
|
||||||
|
@ -1845,16 +1909,16 @@ impl WebRTCSink {
|
||||||
let mut consumer = Consumer::new(
|
let mut consumer = Consumer::new(
|
||||||
pipeline.clone(),
|
pipeline.clone(),
|
||||||
webrtcbin.clone(),
|
webrtcbin.clone(),
|
||||||
peer_id.to_string(),
|
peer_id.clone(),
|
||||||
match settings.cc_heuristic {
|
match settings.cc_info.heuristic {
|
||||||
WebRTCSinkCongestionControl::Disabled => None,
|
|
||||||
WebRTCSinkCongestionControl::Homegrown => Some(CongestionController::new(
|
WebRTCSinkCongestionControl::Homegrown => Some(CongestionController::new(
|
||||||
peer_id,
|
&peer_id,
|
||||||
settings.min_bitrate,
|
settings.cc_info.min_bitrate,
|
||||||
settings.max_bitrate,
|
settings.cc_info.max_bitrate,
|
||||||
)),
|
)),
|
||||||
|
_ => None,
|
||||||
},
|
},
|
||||||
settings.max_bitrate,
|
settings.cc_info,
|
||||||
);
|
);
|
||||||
|
|
||||||
let rtpbin = webrtcbin
|
let rtpbin = webrtcbin
|
||||||
|
@ -1983,7 +2047,7 @@ impl WebRTCSink {
|
||||||
//
|
//
|
||||||
// This is completely safe, as we know that by now all conditions are gathered:
|
// This is completely safe, as we know that by now all conditions are gathered:
|
||||||
// webrtcbin is in the Ready state, and all its transceivers have codec_preferences.
|
// webrtcbin is in the Ready state, and all its transceivers have codec_preferences.
|
||||||
self.negotiate(element, peer_id);
|
self.negotiate(element, &peer_id);
|
||||||
|
|
||||||
pipeline.set_state(gst::State::Playing).map_err(|err| {
|
pipeline.set_state(gst::State::Playing).map_err(|err| {
|
||||||
WebRTCSinkError::ConsumerPipelineError {
|
WebRTCSinkError::ConsumerPipelineError {
|
||||||
|
@ -2051,6 +2115,32 @@ impl WebRTCSink {
|
||||||
webrtcbin.emit_by_name::<()>("get-stats", &[&None::<gst::Pad>, &promise]);
|
webrtcbin.emit_by_name::<()>("get-stats", &[&None::<gst::Pad>, &promise]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_bitrate(&self, element: &super::WebRTCSink, peer_id: &str, bitrate: u32) {
|
||||||
|
let mut state = element.imp().state.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(consumer) = state.consumers.get_mut(peer_id) {
|
||||||
|
let fec_ratio = {
|
||||||
|
// Start adding some FEC when the bitrate > 2Mbps as we found experimentally
|
||||||
|
// that it is not worth it below that threshold
|
||||||
|
if bitrate <= 2_000_000 || consumer.cc_info.max_bitrate <= 2_000_000 {
|
||||||
|
0f64
|
||||||
|
} else {
|
||||||
|
(bitrate as f64 - 2_000_000.)
|
||||||
|
/ (consumer.cc_info.max_bitrate as f64 - 2_000_000.)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let fec_percentage = fec_ratio * 50f64;
|
||||||
|
let encoders_bitrate = ((bitrate as f64) / (1. + (fec_percentage / 100.))) as i32;
|
||||||
|
for encoder in consumer.encoders.iter_mut() {
|
||||||
|
encoder.set_bitrate(element, encoders_bitrate);
|
||||||
|
encoder
|
||||||
|
.transceiver
|
||||||
|
.set_property("fec-percentage", fec_percentage as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_remote_description_set(&self, element: &super::WebRTCSink, peer_id: String) {
|
fn on_remote_description_set(&self, element: &super::WebRTCSink, peer_id: String) {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
let mut remove = false;
|
let mut remove = false;
|
||||||
|
@ -2105,21 +2195,16 @@ impl WebRTCSink {
|
||||||
|
|
||||||
let element_clone = element.downgrade();
|
let element_clone = element.downgrade();
|
||||||
let webrtcbin = consumer.webrtcbin.downgrade();
|
let webrtcbin = consumer.webrtcbin.downgrade();
|
||||||
let peer_id_clone = peer_id.clone();
|
|
||||||
|
|
||||||
task::spawn(async move {
|
task::spawn(async move {
|
||||||
let mut interval =
|
let mut interval =
|
||||||
async_std::stream::interval(std::time::Duration::from_millis(100));
|
async_std::stream::interval(std::time::Duration::from_millis(100));
|
||||||
|
|
||||||
while interval.next().await.is_some() {
|
while interval.next().await.is_some() {
|
||||||
let element_clone = element_clone.clone();
|
let element_clone = element_clone.clone();
|
||||||
let peer_id_clone = peer_id_clone.clone();
|
|
||||||
if let (Some(webrtcbin), Some(element)) =
|
if let (Some(webrtcbin), Some(element)) =
|
||||||
(webrtcbin.upgrade(), element_clone.upgrade())
|
(webrtcbin.upgrade(), element_clone.upgrade())
|
||||||
{
|
{
|
||||||
element
|
element.imp().process_stats(&element, webrtcbin, &peer_id);
|
||||||
.imp()
|
|
||||||
.process_stats(&element, webrtcbin, &peer_id_clone);
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2547,7 +2632,7 @@ impl ObjectImpl for WebRTCSink {
|
||||||
"Defines how congestion is controlled, if at all",
|
"Defines how congestion is controlled, if at all",
|
||||||
WebRTCSinkCongestionControl::static_type(),
|
WebRTCSinkCongestionControl::static_type(),
|
||||||
DEFAULT_CONGESTION_CONTROL as i32,
|
DEFAULT_CONGESTION_CONTROL as i32,
|
||||||
glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_PLAYING,
|
glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_READY,
|
||||||
),
|
),
|
||||||
glib::ParamSpecUInt::new(
|
glib::ParamSpecUInt::new(
|
||||||
"min-bitrate",
|
"min-bitrate",
|
||||||
|
@ -2653,48 +2738,27 @@ impl ObjectImpl for WebRTCSink {
|
||||||
}
|
}
|
||||||
"congestion-control" => {
|
"congestion-control" => {
|
||||||
let mut settings = self.settings.lock().unwrap();
|
let mut settings = self.settings.lock().unwrap();
|
||||||
let new_heuristic = value
|
settings.cc_info.heuristic = value
|
||||||
.get::<WebRTCSinkCongestionControl>()
|
.get::<WebRTCSinkCongestionControl>()
|
||||||
.expect("type checked upstream");
|
.expect("type checked upstream");
|
||||||
if new_heuristic != settings.cc_heuristic {
|
|
||||||
settings.cc_heuristic = new_heuristic;
|
|
||||||
|
|
||||||
let mut state = self.state.lock().unwrap();
|
|
||||||
for (peer_id, consumer) in state.consumers.iter_mut() {
|
|
||||||
match new_heuristic {
|
|
||||||
WebRTCSinkCongestionControl::Disabled => {
|
|
||||||
consumer.congestion_controller.take();
|
|
||||||
for encoder in &mut consumer.encoders {
|
|
||||||
encoder
|
|
||||||
.set_bitrate(&self.instance(), consumer.max_bitrate as i32);
|
|
||||||
encoder.transceiver.set_property("fec-percentage", 50u32);
|
|
||||||
}
|
|
||||||
consumer.stats_sigid.take();
|
|
||||||
}
|
|
||||||
WebRTCSinkCongestionControl::Homegrown => {
|
|
||||||
let _ = consumer.congestion_controller.insert(
|
|
||||||
CongestionController::new(
|
|
||||||
peer_id,
|
|
||||||
settings.min_bitrate,
|
|
||||||
settings.max_bitrate,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"min-bitrate" => {
|
"min-bitrate" => {
|
||||||
let mut settings = self.settings.lock().unwrap();
|
let mut settings = self.settings.lock().unwrap();
|
||||||
settings.min_bitrate = value.get::<u32>().expect("type checked upstream");
|
settings.cc_info.min_bitrate = value.get::<u32>().expect("type checked upstream");
|
||||||
}
|
}
|
||||||
"max-bitrate" => {
|
"max-bitrate" => {
|
||||||
let mut settings = self.settings.lock().unwrap();
|
let mut settings = self.settings.lock().unwrap();
|
||||||
settings.max_bitrate = value.get::<u32>().expect("type checked upstream");
|
settings.cc_info.max_bitrate = (value.get::<u32>().expect("type checked upstream")
|
||||||
|
as f32
|
||||||
|
* if settings.do_fec {
|
||||||
|
settings.cc_info.max_bitrate as f32 * 1.5
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
}) as u32;
|
||||||
}
|
}
|
||||||
"start-bitrate" => {
|
"start-bitrate" => {
|
||||||
let mut settings = self.settings.lock().unwrap();
|
let mut settings = self.settings.lock().unwrap();
|
||||||
settings.start_bitrate = value.get::<u32>().expect("type checked upstream");
|
settings.cc_info.start_bitrate = value.get::<u32>().expect("type checked upstream");
|
||||||
}
|
}
|
||||||
"do-fec" => {
|
"do-fec" => {
|
||||||
let mut settings = self.settings.lock().unwrap();
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
@ -2731,7 +2795,7 @@ impl ObjectImpl for WebRTCSink {
|
||||||
}
|
}
|
||||||
"congestion-control" => {
|
"congestion-control" => {
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
settings.cc_heuristic.to_value()
|
settings.cc_info.heuristic.to_value()
|
||||||
}
|
}
|
||||||
"stun-server" => {
|
"stun-server" => {
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
|
@ -2743,15 +2807,17 @@ impl ObjectImpl for WebRTCSink {
|
||||||
}
|
}
|
||||||
"min-bitrate" => {
|
"min-bitrate" => {
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
settings.min_bitrate.to_value()
|
settings.cc_info.min_bitrate.to_value()
|
||||||
}
|
}
|
||||||
"max-bitrate" => {
|
"max-bitrate" => {
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
settings.max_bitrate.to_value()
|
((settings.cc_info.max_bitrate as f32 / if settings.do_fec { 1.5 } else { 1. })
|
||||||
|
as u32)
|
||||||
|
.to_value()
|
||||||
}
|
}
|
||||||
"start-bitrate" => {
|
"start-bitrate" => {
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
settings.start_bitrate.to_value()
|
settings.cc_info.start_bitrate.to_value()
|
||||||
}
|
}
|
||||||
"do-fec" => {
|
"do-fec" => {
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
|
@ -2873,7 +2939,7 @@ impl ObjectImpl for WebRTCSink {
|
||||||
|
|
||||||
let this = element.imp();
|
let this = element.imp();
|
||||||
let settings = this.settings.lock().unwrap();
|
let settings = this.settings.lock().unwrap();
|
||||||
configure_encoder(&enc, settings.start_bitrate);
|
configure_encoder(&enc, settings.cc_info.start_bitrate);
|
||||||
|
|
||||||
// Return false here so that latter handlers get called
|
// Return false here so that latter handlers get called
|
||||||
Some(false.to_value())
|
Some(false.to_value())
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::subclass::prelude::ObjectSubclassExt;
|
use gst::subclass::prelude::*;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
mod imp;
|
mod imp;
|
||||||
|
@ -135,6 +135,8 @@ pub enum WebRTCSinkCongestionControl {
|
||||||
Disabled,
|
Disabled,
|
||||||
#[enum_value(name = "Homegrown: simple sender-side heuristic", nick = "homegrown")]
|
#[enum_value(name = "Homegrown: simple sender-side heuristic", nick = "homegrown")]
|
||||||
Homegrown,
|
Homegrown,
|
||||||
|
#[enum_value(name = "Google Congestion Control algorithm", nick = "gcc")]
|
||||||
|
GoogleCongestionControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::flags(name = "GstWebRTCSinkMitigationMode")]
|
#[glib::flags(name = "GstWebRTCSinkMitigationMode")]
|
||||||
|
|
Loading…
Reference in a new issue