webrtcsink: expose signal for initial encoder configuration

+ Update README
This commit is contained in:
Mathieu Duponchelle 2022-03-25 02:32:55 +01:00
parent b5443c5966
commit 983fcf2fbd
3 changed files with 107 additions and 37 deletions

View file

@ -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

View file

@ -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::<gst::Element>().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 {

View file

@ -296,6 +296,45 @@ fn make_converter_for_video_caps(caps: &gst::Caps) -> Result<gst::Element, Error
.upcast())
}
/// Default configuration for known encoders, can be disabled
/// by returning True from an encoder-setup handler
fn configure_encoder(codec: &Codec, enc: &gst::Element) {
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);
}
"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(),
]
});