diff --git a/README.md b/README.md index 2973f810a..ea3a6c29b 100644 --- a/README.md +++ b/README.md @@ -35,26 +35,30 @@ useful alternative. While this is not on the roadmap at the moment, nothing in the design prevents implementing this optimization. -* Congestion control: the element levarages transport-wide congestion control +* Congestion control: the element leverages transport-wide congestion control feedback messages in order to adapt the bitrate of individual consumers' video encoders to the available bandwidth. -* Configuration: the level of user control over the element is at the moment quite - narrow, as the only interface exposed is control over proposed codecs, as well - as their order of priority, and disabling congestion control. Consult `gst-inspect=1.0` - for more information. +* Configuration: the level of user control over the element is slowly expanding, + consult `gst-inspect-1.0` for more information on the available properties and + signals. -More features are on the roadmap, focusing on mechanisms for mitigating packet -loss. +* Packet loss mitigation: webrtcsink now supports sending protection packets for + Forward Error Correction, modulating the amount as a function of the available + bandwidth, and can honor retransmission requests. Both features can be disabled + via properties. It is important to note that full control over the individual elements used by `webrtcsink` is *not* on the roadmap, as it will act as a black box in that respect, for example `webrtcsink` wants to reserve control over the bitrate for congestion control. +A signal is now available however for the application to provide the initial +configuration for the encoders `webrtcsink` instantiates. + If more granular control is required, applications should use `webrtcbin` directly, `webrtcsink` will focus on trying to just do the right thing, although it might -expose interfaces to guide and tune the heuristics it employs. +expose more interfaces to guide and tune the heuristics it employs. [example project]: https://github.com/centricular/webrtcsink-custom-signaller diff --git a/plugins/examples/webrtcsink-stats-server.rs b/plugins/examples/webrtcsink-stats-server.rs index 846b86e74..4045e1d3e 100644 --- a/plugins/examples/webrtcsink-stats-server.rs +++ b/plugins/examples/webrtcsink-stats-server.rs @@ -133,6 +133,24 @@ async fn run(args: Args) -> Result<(), Error> { .by_name("ws") .unwrap(); + ws.connect("encoder-setup", false, |values| { + let encoder = values[3].get::().unwrap(); + + info!("Encoder: {}", encoder.factory().unwrap().name()); + + let configured = match encoder.factory().unwrap().name().as_str() { + "does-not-exist" => { + // One could configure a hardware encoder to their liking here, + // and return true to make sure webrtcsink does not do any configuration + // of its own + true + } + _ => false, + }; + + Some(configured.to_value()) + }); + let ws_clone = ws.downgrade(); let state_clone = state.clone(); task::spawn(async move { diff --git a/plugins/src/webrtcsink/imp.rs b/plugins/src/webrtcsink/imp.rs index 18df5a649..d4b869325 100644 --- a/plugins/src/webrtcsink/imp.rs +++ b/plugins/src/webrtcsink/imp.rs @@ -296,6 +296,45 @@ fn make_converter_for_video_caps(caps: &gst::Caps) -> Result { + enc.set_property("deadline", 1i64); + enc.set_property("threads", 12i32); + enc.set_property("target-bitrate", 2560000i32); + enc.set_property("cpu-used", -16i32); + enc.set_property("keyframe-max-dist", 2000i32); + enc.set_property_from_str("keyframe-mode", "disabled"); + enc.set_property_from_str("end-usage", "cbr"); + enc.set_property("buffer-initial-size", 100i32); + enc.set_property("buffer-optimal-size", 120i32); + enc.set_property("buffer-size", 150i32); + enc.set_property("resize-allowed", true); + enc.set_property("max-intra-bitrate", 250i32); + enc.set_property_from_str("error-resilient", "default"); + enc.set_property("lag-in-frames", 0i32); + } + "x264enc" => { + enc.set_property("bitrate", 2048u32); + enc.set_property_from_str("tune", "zerolatency"); + enc.set_property_from_str("speed-preset", "ultrafast"); + enc.set_property("threads", 12u32); + enc.set_property("key-int-max", 2560u32); + enc.set_property("b-adapt", false); + enc.set_property("vbv-buf-capacity", 120u32); + } + "nvh264enc" => { + enc.set_property("bitrate", 2048u32); + enc.set_property("gop-size", 2560i32); + enc.set_property_from_str("rc-mode", "cbr-ld-hq"); + enc.set_property("zerolatency", true); + } + _ => (), + } +} + /// Bit of an awkward function, but the goal here is to keep /// most of the encoding code for consumers in line with /// the codec discovery code, and this gets the job done. @@ -376,37 +415,8 @@ fn setup_encoding( match codec.encoder.name().as_str() { "vp8enc" | "vp9enc" => { - enc.set_property("deadline", 1i64); - enc.set_property("threads", 12i32); - enc.set_property("target-bitrate", 2560000i32); - enc.set_property("cpu-used", -16i32); - enc.set_property("keyframe-max-dist", 2000i32); - enc.set_property_from_str("keyframe-mode", "disabled"); - enc.set_property_from_str("end-usage", "cbr"); - enc.set_property("buffer-initial-size", 100i32); - enc.set_property("buffer-optimal-size", 120i32); - enc.set_property("buffer-size", 150i32); - enc.set_property("resize-allowed", true); - enc.set_property("max-intra-bitrate", 250i32); - enc.set_property_from_str("error-resilient", "default"); - enc.set_property("lag-in-frames", 0i32); pay.set_property_from_str("picture-id-mode", "15-bit"); } - "x264enc" => { - enc.set_property("bitrate", 2048u32); - enc.set_property_from_str("tune", "zerolatency"); - enc.set_property_from_str("speed-preset", "ultrafast"); - enc.set_property("threads", 12u32); - enc.set_property("key-int-max", 2560u32); - enc.set_property("b-adapt", false); - enc.set_property("vbv-buf-capacity", 120u32); - } - "nvh264enc" => { - enc.set_property("bitrate", 2048u32); - enc.set_property("gop-size", 2560i32); - enc.set_property_from_str("rc-mode", "cbr-ld-hq"); - enc.set_property("zerolatency", true); - } _ => (), } @@ -1059,6 +1069,23 @@ impl Consumer { false, )?; + let encoder_was_configured: bool = element.emit_by_name( + "encoder-setup", + &[&self.peer_id, &webrtc_pad.stream_name, &enc], + ); + + if !encoder_was_configured { + gst_debug!(CAT, obj: element, "configuring encoder {:?}", enc); + configure_encoder(codec, &enc); + } else { + gst_debug!( + CAT, + obj: element, + "the encoder {:?} was configured through the signal handler", + enc + ); + } + // At this point, the peer has provided its answer, and we want to // let the payloader / encoder perform negotiation according to that. // @@ -2551,6 +2578,27 @@ impl ObjectImpl for WebRTCSink { res }) .build(), + /* + * RsWebRTCSink::encoder-setup: + * @consumer_id: Identifier of the consumer + * @pad_name: The name of the corresponding input pad + * @encoder: The constructed encoder + * + * This signal can be used to tweak @encoder properties. + * + * Returns: True if the encoder is entirely configured, + * False if webrtcsink should apply a default configuration + */ + glib::subclass::Signal::builder( + "encoder-setup", + &[ + String::static_type().into(), + String::static_type().into(), + gst::Element::static_type().into(), + ], + bool::static_type().into(), + ) + .build(), ] });