mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-12-23 10:30:40 +00:00
fallbackswitch: output buffers ASAP at startup
When only the backup pad is receiving buffers, and the primary pad is a bit slow to start up (eg network source with buffering), it makes for a better UX to output buffers from the backup pad while waiting for the network source to make its move. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/515>
This commit is contained in:
parent
3cdc5870a1
commit
a5a80281f3
2 changed files with 94 additions and 26 deletions
|
@ -79,6 +79,7 @@ struct Settings {
|
||||||
restart_on_eos: bool,
|
restart_on_eos: bool,
|
||||||
min_latency: gst::ClockTime,
|
min_latency: gst::ClockTime,
|
||||||
buffer_duration: i64,
|
buffer_duration: i64,
|
||||||
|
immediate_fallback: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
|
@ -95,6 +96,7 @@ impl Default for Settings {
|
||||||
restart_on_eos: false,
|
restart_on_eos: false,
|
||||||
min_latency: gst::ClockTime::ZERO,
|
min_latency: gst::ClockTime::ZERO,
|
||||||
buffer_duration: -1,
|
buffer_duration: -1,
|
||||||
|
immediate_fallback: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,6 +288,13 @@ impl ObjectImpl for FallbackSrc {
|
||||||
gst::Structure::static_type(),
|
gst::Structure::static_type(),
|
||||||
glib::ParamFlags::READABLE,
|
glib::ParamFlags::READABLE,
|
||||||
),
|
),
|
||||||
|
glib::ParamSpec::new_boolean(
|
||||||
|
"immediate-fallback",
|
||||||
|
"Immediate fallback",
|
||||||
|
"Forward the fallback streams immediately at startup, when the primary streams are slow to start up and immediate output is required",
|
||||||
|
false,
|
||||||
|
glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_READY,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -432,6 +441,18 @@ impl ObjectImpl for FallbackSrc {
|
||||||
);
|
);
|
||||||
settings.buffer_duration = new_value;
|
settings.buffer_duration = new_value;
|
||||||
}
|
}
|
||||||
|
"immediate-fallback" => {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
let new_value = value.get().expect("type checked upstream");
|
||||||
|
gst_info!(
|
||||||
|
CAT,
|
||||||
|
obj: obj,
|
||||||
|
"Changing immediate-fallback from {:?} to {:?}",
|
||||||
|
settings.immediate_fallback,
|
||||||
|
new_value,
|
||||||
|
);
|
||||||
|
settings.immediate_fallback = new_value;
|
||||||
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -538,6 +559,10 @@ impl ObjectImpl for FallbackSrc {
|
||||||
settings.buffer_duration.to_value()
|
settings.buffer_duration.to_value()
|
||||||
}
|
}
|
||||||
"statistics" => self.stats().to_value(),
|
"statistics" => self.stats().to_value(),
|
||||||
|
"immediate-fallback" => {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
settings.immediate_fallback.to_value()
|
||||||
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -775,6 +800,7 @@ impl FallbackSrc {
|
||||||
min_latency: gst::ClockTime,
|
min_latency: gst::ClockTime,
|
||||||
is_audio: bool,
|
is_audio: bool,
|
||||||
fallback_uri: Option<&str>,
|
fallback_uri: Option<&str>,
|
||||||
|
immediate_fallback: bool,
|
||||||
) -> Stream {
|
) -> Stream {
|
||||||
let fallback_input = if is_audio {
|
let fallback_input = if is_audio {
|
||||||
self.create_fallback_audio_input(element)
|
self.create_fallback_audio_input(element)
|
||||||
|
@ -820,6 +846,9 @@ impl FallbackSrc {
|
||||||
switch
|
switch
|
||||||
.set_property("min-upstream-latency", &min_latency.nseconds())
|
.set_property("min-upstream-latency", &min_latency.nseconds())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
switch
|
||||||
|
.set_property("immediate-fallback", &immediate_fallback)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
gst::Element::link_pads(&fallback_input, Some("src"), &switch, Some("fallback_sink"))
|
gst::Element::link_pads(&fallback_input, Some("src"), &switch, Some("fallback_sink"))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -903,6 +932,7 @@ impl FallbackSrc {
|
||||||
settings.min_latency,
|
settings.min_latency,
|
||||||
false,
|
false,
|
||||||
fallback_uri.as_deref(),
|
fallback_uri.as_deref(),
|
||||||
|
settings.immediate_fallback,
|
||||||
);
|
);
|
||||||
flow_combiner.add_pad(&stream.srcpad);
|
flow_combiner.add_pad(&stream.srcpad);
|
||||||
Some(stream)
|
Some(stream)
|
||||||
|
@ -912,8 +942,14 @@ impl FallbackSrc {
|
||||||
|
|
||||||
// Create audio stream
|
// Create audio stream
|
||||||
let audio_stream = if settings.enable_audio {
|
let audio_stream = if settings.enable_audio {
|
||||||
let stream =
|
let stream = self.create_stream(
|
||||||
self.create_stream(element, settings.timeout, settings.min_latency, true, None);
|
element,
|
||||||
|
settings.timeout,
|
||||||
|
settings.min_latency,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
settings.immediate_fallback,
|
||||||
|
);
|
||||||
flow_combiner.add_pad(&stream.srcpad);
|
flow_combiner.add_pad(&stream.srcpad);
|
||||||
Some(stream)
|
Some(stream)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -92,11 +92,13 @@ struct PadInputState {
|
||||||
const DEFAULT_TIMEOUT: gst::ClockTime = gst::ClockTime::from_seconds(5);
|
const DEFAULT_TIMEOUT: gst::ClockTime = gst::ClockTime::from_seconds(5);
|
||||||
const DEFAULT_AUTO_SWITCH: bool = true;
|
const DEFAULT_AUTO_SWITCH: bool = true;
|
||||||
const DEFAULT_STREAM_HEALTH: StreamHealth = StreamHealth::Inactive;
|
const DEFAULT_STREAM_HEALTH: StreamHealth = StreamHealth::Inactive;
|
||||||
|
const DEFAULT_IMMEDIATE_FALLBACK: bool = false;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Settings {
|
struct Settings {
|
||||||
timeout: gst::ClockTime,
|
timeout: gst::ClockTime,
|
||||||
auto_switch: bool,
|
auto_switch: bool,
|
||||||
|
immediate_fallback: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StreamHealth {
|
impl Default for StreamHealth {
|
||||||
|
@ -120,6 +122,7 @@ impl Default for Settings {
|
||||||
Settings {
|
Settings {
|
||||||
timeout: DEFAULT_TIMEOUT,
|
timeout: DEFAULT_TIMEOUT,
|
||||||
auto_switch: DEFAULT_AUTO_SWITCH,
|
auto_switch: DEFAULT_AUTO_SWITCH,
|
||||||
|
immediate_fallback: DEFAULT_IMMEDIATE_FALLBACK,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -411,40 +414,54 @@ impl FallbackSwitch {
|
||||||
if state.last_output_time.is_none() {
|
if state.last_output_time.is_none() {
|
||||||
state.last_output_time = running_time;
|
state.last_output_time = running_time;
|
||||||
}
|
}
|
||||||
if backup_pad == &self.primary_sinkpad {
|
|
||||||
state.primary.last_sinkpad_time = running_time;
|
|
||||||
} else {
|
|
||||||
state.fallback.last_sinkpad_time = running_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the next one if this one is before the timeout
|
// If the other pad never received a buffer, we want to start consuming
|
||||||
if state.last_output_time.zip(running_time).map_or(
|
// buffers on this pad in order to provide an output at start up
|
||||||
false,
|
// (for example with a slow primary)
|
||||||
|(last_output_time, running_time)| {
|
let ignore_timeout = settings.immediate_fallback && {
|
||||||
last_output_time + settings.timeout > running_time
|
if backup_pad == &self.primary_sinkpad {
|
||||||
},
|
state.primary.last_sinkpad_time = running_time;
|
||||||
) {
|
state.fallback.last_sinkpad_time.is_none()
|
||||||
|
} else {
|
||||||
|
state.fallback.last_sinkpad_time = running_time;
|
||||||
|
state.primary.last_sinkpad_time.is_none()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !ignore_timeout {
|
||||||
|
// Get the next one if this one is before the timeout
|
||||||
|
if state.last_output_time.zip(running_time).map_or(
|
||||||
|
false,
|
||||||
|
|(last_output_time, running_time)| {
|
||||||
|
last_output_time + settings.timeout > running_time
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
gst_debug!(
|
||||||
|
CAT,
|
||||||
|
obj: backup_pad,
|
||||||
|
"Timeout not reached yet: {} + {} > {}",
|
||||||
|
state.last_output_time.display(),
|
||||||
|
settings.timeout,
|
||||||
|
running_time.display(),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
gst_debug!(
|
gst_debug!(
|
||||||
CAT,
|
CAT,
|
||||||
obj: backup_pad,
|
obj: backup_pad,
|
||||||
"Timeout not reached yet: {} + {} > {}",
|
"Timeout reached: {} + {} <= {}",
|
||||||
state.last_output_time.display(),
|
state.last_output_time.display(),
|
||||||
settings.timeout,
|
settings.timeout,
|
||||||
running_time.display(),
|
running_time.display(),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
continue;
|
gst_debug!(
|
||||||
|
CAT,
|
||||||
|
obj: backup_pad,
|
||||||
|
"Consuming buffer as we haven't yet received a buffer on the other pad",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
gst_debug!(
|
|
||||||
CAT,
|
|
||||||
obj: backup_pad,
|
|
||||||
"Timeout reached: {} + {} <= {}",
|
|
||||||
state.last_output_time.display(),
|
|
||||||
settings.timeout,
|
|
||||||
running_time.display(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut active_sinkpad = self.active_sinkpad.lock().unwrap();
|
let mut active_sinkpad = self.active_sinkpad.lock().unwrap();
|
||||||
let pad_change = settings.auto_switch
|
let pad_change = settings.auto_switch
|
||||||
&& active_sinkpad.as_ref() != Some(backup_pad.upcast_ref::<gst::Pad>());
|
&& active_sinkpad.as_ref() != Some(backup_pad.upcast_ref::<gst::Pad>());
|
||||||
|
@ -721,6 +738,13 @@ impl ObjectImpl for FallbackSwitch {
|
||||||
DEFAULT_STREAM_HEALTH as i32,
|
DEFAULT_STREAM_HEALTH as i32,
|
||||||
glib::ParamFlags::READABLE,
|
glib::ParamFlags::READABLE,
|
||||||
),
|
),
|
||||||
|
glib::ParamSpec::new_boolean(
|
||||||
|
"immediate-fallback",
|
||||||
|
"Immediate fallback",
|
||||||
|
"Forward the fallback stream immediately at startup, when the primary stream is slow to start up and immediate output is required",
|
||||||
|
DEFAULT_AUTO_SWITCH,
|
||||||
|
glib::ParamFlags::READWRITE| gst::PARAM_FLAG_MUTABLE_READY,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -779,6 +803,10 @@ impl ObjectImpl for FallbackSwitch {
|
||||||
let mut settings = self.settings.lock().unwrap();
|
let mut settings = self.settings.lock().unwrap();
|
||||||
settings.auto_switch = value.get().expect("type checked upstream");
|
settings.auto_switch = value.get().expect("type checked upstream");
|
||||||
}
|
}
|
||||||
|
"immediate-fallback" => {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
settings.immediate_fallback = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -805,6 +833,10 @@ impl ObjectImpl for FallbackSwitch {
|
||||||
let state = self.output_state.lock().unwrap();
|
let state = self.output_state.lock().unwrap();
|
||||||
state.fallback.stream_health.to_value()
|
state.fallback.stream_health.to_value()
|
||||||
}
|
}
|
||||||
|
"immediate-fallback" => {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
settings.immediate_fallback.to_value()
|
||||||
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue