webrtcsink: expose stats property

This commit is contained in:
Mathieu Duponchelle 2021-11-30 22:43:17 +01:00
parent 40a4b745e3
commit 921ca7fbab
2 changed files with 77 additions and 6 deletions

View file

@ -15,7 +15,7 @@ use std::ops::Mul;
use std::sync::Mutex; use std::sync::Mutex;
use super::utils::{make_element, StreamProducer}; use super::utils::{make_element, StreamProducer};
use super::WebRTCSinkCongestionControl; use super::{WebRTCSinkCongestionControl, WebRTCSinkMitigationMode};
use crate::signaller::Signaller; use crate::signaller::Signaller;
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -93,11 +93,13 @@ struct WebRTCPad {
/// stream according to the bitrate, thresholds hardcoded for now /// stream according to the bitrate, thresholds hardcoded for now
struct VideoEncoder { struct VideoEncoder {
factory_name: String, factory_name: String,
codec_name: String,
element: gst::Element, element: gst::Element,
filter: gst::Element, filter: gst::Element,
halved_framerate: gst::Fraction, halved_framerate: gst::Fraction,
full_width: i32, full_width: i32,
peer_id: String, peer_id: String,
mitigation_mode: WebRTCSinkMitigationMode,
} }
struct CongestionController { struct CongestionController {
@ -151,6 +153,7 @@ struct Consumer {
/// None if congestion control was disabled /// None if congestion control was disabled
congestion_controller: Option<CongestionController>, congestion_controller: Option<CongestionController>,
sdp: Option<gst_sdp::SDPMessage>, sdp: Option<gst_sdp::SDPMessage>,
stats: gst::Structure,
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@ -408,6 +411,7 @@ impl VideoEncoder {
filter: gst::Element, filter: gst::Element,
in_caps: &gst::Caps, in_caps: &gst::Caps,
peer_id: &str, peer_id: &str,
codec_name: &str,
) -> Self { ) -> Self {
let s = in_caps.structure(0).unwrap(); let s = in_caps.structure(0).unwrap();
@ -419,11 +423,13 @@ impl VideoEncoder {
Self { Self {
factory_name: element.factory().unwrap().name().into(), factory_name: element.factory().unwrap().name().into(),
codec_name: codec_name.to_string(),
element, element,
filter, filter,
halved_framerate, halved_framerate,
full_width, full_width,
peer_id: peer_id.to_string(), peer_id: peer_id.to_string(),
mitigation_mode: WebRTCSinkMitigationMode::NONE,
} }
} }
@ -435,7 +441,7 @@ impl VideoEncoder {
} }
} }
fn set_bitrate(&self, element: &super::WebRTCSink, bitrate: i32) { 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" => self "x264enc" | "nvh264enc" => self
@ -456,15 +462,20 @@ impl VideoEncoder {
if bitrate < 500000 { if bitrate < 500000 {
s.set("width", 360i32.min(self.full_width)); s.set("width", 360i32.min(self.full_width));
s.set("framerate", self.halved_framerate); s.set("framerate", self.halved_framerate);
self.mitigation_mode =
WebRTCSinkMitigationMode::DOWNSAMPLED | WebRTCSinkMitigationMode::DOWNSCALED;
} else if bitrate < 1000000 { } else if bitrate < 1000000 {
s.set("width", 360i32.min(self.full_width)); s.set("width", 360i32.min(self.full_width));
s.remove_field("framerate"); s.remove_field("framerate");
self.mitigation_mode = WebRTCSinkMitigationMode::DOWNSCALED;
} else if bitrate < 2000000 { } else if bitrate < 2000000 {
s.set("width", 720i32.min(self.full_width)); s.set("width", 720i32.min(self.full_width));
s.remove_field("framerate"); s.remove_field("framerate");
self.mitigation_mode = WebRTCSinkMitigationMode::DOWNSCALED;
} else { } else {
s.remove_field("width"); s.remove_field("width");
s.remove_field("framerate"); s.remove_field("framerate");
self.mitigation_mode = WebRTCSinkMitigationMode::NONE;
} }
let caps = gst::Caps::builder_full().structure(s).build(); let caps = gst::Caps::builder_full().structure(s).build();
@ -481,6 +492,14 @@ impl VideoEncoder {
self.filter.set_property("caps", caps); self.filter.set_property("caps", caps);
} }
fn gather_stats(&self) -> gst::Structure {
gst::Structure::builder("application/x-webrtcsink-video-encoder-stats")
.field("bitrate", self.bitrate())
.field("mitigation-mode", self.mitigation_mode)
.field("codec-name", self.codec_name.as_str())
.build()
}
} }
impl CongestionController { impl CongestionController {
@ -650,7 +669,7 @@ impl CongestionController {
&mut self, &mut self,
element: &super::WebRTCSink, element: &super::WebRTCSink,
stats: &gst::StructureRef, stats: &gst::StructureRef,
encoders: &Vec<VideoEncoder>, encoders: &mut Vec<VideoEncoder>,
) { ) {
let n_encoders = encoders.len() as i32; let n_encoders = encoders.len() as i32;
@ -696,7 +715,7 @@ impl CongestionController {
} }
} }
for encoder in encoders { for encoder in encoders.iter_mut() {
encoder.set_bitrate(element, self.target_bitrate / n_encoders); encoder.set_bitrate(element, self.target_bitrate / n_encoders);
} }
} }
@ -759,6 +778,25 @@ impl State {
} }
impl Consumer { impl Consumer {
fn gather_stats(&self) -> gst::Structure {
let mut ret = self.stats.to_owned();
let encoder_stats: Vec<_> = self
.encoders
.iter()
.map(VideoEncoder::gather_stats)
.map(|s| s.to_send_value())
.collect();
let our_stats = gst::Structure::builder("application/x-webrtcsink-consumer-stats")
.field("video-encoders", gst::Array::from(encoder_stats))
.build();
ret.set("consumer-stats", our_stats);
ret
}
fn generate_ssrc(&self) -> u32 { fn generate_ssrc(&self) -> u32 {
loop { loop {
let ret = fastrand::u32(..); let ret = fastrand::u32(..);
@ -893,11 +931,12 @@ impl Consumer {
pay_filter.set_property("caps", caps); pay_filter.set_property("caps", caps);
if codec.is_video { if codec.is_video {
let enc = VideoEncoder::new( let mut enc = VideoEncoder::new(
enc.clone(), enc.clone(),
raw_filter.clone(), raw_filter.clone(),
&webrtc_pad.in_caps, &webrtc_pad.in_caps,
&self.peer_id, &self.peer_id,
codec.caps.structure(0).unwrap().name(),
); );
if let Some(congestion_controller) = self.congestion_controller.as_mut() { if let Some(congestion_controller) = self.congestion_controller.as_mut() {
@ -1416,6 +1455,7 @@ impl WebRTCSink {
}, },
encoders: Vec::new(), encoders: Vec::new(),
sdp: None, sdp: None,
stats: gst::Structure::new_empty("application/x-webrtc-stats"),
}; };
state state
@ -1511,8 +1551,9 @@ impl WebRTCSink {
if let Some(consumer) = state.consumers.get_mut(peer_id) { if let Some(consumer) = state.consumers.get_mut(peer_id) {
if let Some(congestion_controller) = consumer.congestion_controller.as_mut() { if let Some(congestion_controller) = consumer.congestion_controller.as_mut() {
congestion_controller.control(element, stats, &consumer.encoders); congestion_controller.control(element, stats, &mut consumer.encoders);
} }
consumer.stats = stats.to_owned();
} }
} }
@ -1834,6 +1875,18 @@ impl WebRTCSink {
Ok(()) Ok(())
} }
fn gather_stats(&self) -> gst::Structure {
gst::Structure::from_iter(
"application/x-webrtcsink-stats",
self.state
.lock()
.unwrap()
.consumers
.iter()
.map(|(name, consumer)| (name.as_str(), consumer.gather_stats().to_send_value())),
)
}
fn sink_event(&self, pad: &gst::Pad, element: &super::WebRTCSink, event: gst::Event) -> bool { fn sink_event(&self, pad: &gst::Pad, element: &super::WebRTCSink, event: gst::Event) -> bool {
use gst::EventView; use gst::EventView;
@ -1982,6 +2035,13 @@ impl ObjectImpl for WebRTCSink {
DEFAULT_MAX_BITRATE, DEFAULT_MAX_BITRATE,
glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_READY, glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_READY,
), ),
glib::ParamSpecBoxed::new(
"stats",
"Consumer statistics",
"Statistics for the current consumers",
gst::Structure::static_type(),
glib::ParamFlags::READABLE,
),
] ]
}); });
@ -2070,6 +2130,7 @@ impl ObjectImpl for WebRTCSink {
let settings = self.settings.lock().unwrap(); let settings = self.settings.lock().unwrap();
settings.max_bitrate.to_value() settings.max_bitrate.to_value()
} }
"stats" => self.gather_stats().to_value(),
_ => unimplemented!(), _ => unimplemented!(),
} }
} }

View file

@ -120,6 +120,16 @@ pub enum WebRTCSinkCongestionControl {
Homegrown, Homegrown,
} }
#[glib::flags(name = "GstWebRTCSinkMitigationMode")]
enum WebRTCSinkMitigationMode {
#[flags_value(name = "No mitigation applied", nick = "none")]
NONE = 0b00000000,
#[flags_value(name = "Lowered resolution", nick = "downscaled")]
DOWNSCALED = 0b00000001,
#[flags_value(name = "Lowered framerate", nick = "downsampled")]
DOWNSAMPLED = 0b00000010,
}
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
gst::Element::register( gst::Element::register(
Some(plugin), Some(plugin),