2019-08-17 08:47:05 +00:00
|
|
|
// Copyright (C) 2019 Sebastian Dröge <sebastian@centricular.com>
|
2021-09-13 15:08:45 +00:00
|
|
|
// Copyright (C) 2021 Jan Schmidt <jan@centricular.com>
|
2019-08-17 08:47:05 +00:00
|
|
|
//
|
2022-01-15 18:40:12 +00:00
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
|
|
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
|
|
|
// <https://mozilla.org/MPL/2.0/>.
|
2019-08-17 08:47:05 +00:00
|
|
|
//
|
2022-01-15 18:40:12 +00:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
use gst::debug;
|
2019-08-17 08:47:05 +00:00
|
|
|
use gst::prelude::*;
|
|
|
|
|
2024-10-21 17:44:33 +00:00
|
|
|
use std::sync::LazyLock;
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
const LATENCY: gst::ClockTime = gst::ClockTime::from_mseconds(10);
|
|
|
|
|
2024-10-21 17:44:33 +00:00
|
|
|
static TEST_CAT: LazyLock<gst::DebugCategory> = LazyLock::new(|| {
|
2020-04-07 20:47:15 +00:00
|
|
|
gst::DebugCategory::new(
|
2019-08-17 08:47:05 +00:00
|
|
|
"fallbackswitch-test",
|
|
|
|
gst::DebugColorFlags::empty(),
|
|
|
|
Some("fallbackswitch test"),
|
2020-04-07 20:47:15 +00:00
|
|
|
)
|
|
|
|
});
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
fn init() {
|
|
|
|
use std::sync::Once;
|
|
|
|
static INIT: Once = Once::new();
|
|
|
|
|
|
|
|
INIT.call_once(|| {
|
|
|
|
gst::init().unwrap();
|
|
|
|
gstfallbackswitch::plugin_register_static().expect("gstfallbackswitch test");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! assert_fallback_buffer {
|
|
|
|
($buffer:expr, $ts:expr) => {
|
2021-04-12 12:49:54 +00:00
|
|
|
assert_eq!($buffer.pts(), $ts);
|
|
|
|
assert_eq!($buffer.size(), 160 * 120 * 4);
|
2019-08-17 08:47:05 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! assert_buffer {
|
|
|
|
($buffer:expr, $ts:expr) => {
|
2021-04-12 12:49:54 +00:00
|
|
|
assert_eq!($buffer.pts(), $ts);
|
|
|
|
assert_eq!($buffer.size(), 320 * 240 * 4);
|
2019-08-17 08:47:05 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_no_fallback_no_drops() {
|
2021-09-13 15:08:45 +00:00
|
|
|
let pipeline = setup_pipeline(None, None, None);
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2021-05-28 16:35:28 +00:00
|
|
|
push_buffer(&pipeline, gst::ClockTime::ZERO);
|
2021-09-13 15:08:45 +00:00
|
|
|
set_time(&pipeline, gst::ClockTime::ZERO + LATENCY);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2021-05-28 16:35:28 +00:00
|
|
|
assert_buffer!(buffer, Some(gst::ClockTime::ZERO));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2022-10-17 17:48:43 +00:00
|
|
|
push_buffer(&pipeline, 1.seconds());
|
|
|
|
set_time(&pipeline, 1.seconds() + LATENCY);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2021-05-28 16:35:28 +00:00
|
|
|
assert_buffer!(buffer, Some(gst::ClockTime::SECOND));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2022-10-17 17:48:43 +00:00
|
|
|
push_buffer(&pipeline, 2.seconds());
|
|
|
|
set_time(&pipeline, 2.seconds() + LATENCY);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_buffer!(buffer, Some(2.seconds()));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
push_eos(&pipeline);
|
|
|
|
wait_eos(&pipeline);
|
|
|
|
|
|
|
|
stop_pipeline(pipeline);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_no_drops_live() {
|
|
|
|
test_no_drops(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_no_drops_not_live() {
|
|
|
|
test_no_drops(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_no_drops(live: bool) {
|
2021-09-13 15:08:45 +00:00
|
|
|
let pipeline = setup_pipeline(Some(live), None, None);
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2021-05-28 16:35:28 +00:00
|
|
|
push_buffer(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
push_fallback_buffer(&pipeline, gst::ClockTime::ZERO);
|
2021-09-13 15:08:45 +00:00
|
|
|
set_time(&pipeline, gst::ClockTime::ZERO + LATENCY);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2021-05-28 16:35:28 +00:00
|
|
|
assert_buffer!(buffer, Some(gst::ClockTime::ZERO));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 1.seconds());
|
|
|
|
push_buffer(&pipeline, 1.seconds());
|
|
|
|
set_time(&pipeline, 1.seconds() + LATENCY);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2021-05-28 16:35:28 +00:00
|
|
|
assert_buffer!(buffer, Some(gst::ClockTime::SECOND));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2022-10-17 17:48:43 +00:00
|
|
|
push_buffer(&pipeline, 2.seconds());
|
|
|
|
push_fallback_buffer(&pipeline, 2.seconds());
|
|
|
|
set_time(&pipeline, 2.seconds() + LATENCY);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_buffer!(buffer, Some(2.seconds()));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// EOS on the fallback should not be required
|
|
|
|
push_eos(&pipeline);
|
|
|
|
wait_eos(&pipeline);
|
|
|
|
|
|
|
|
stop_pipeline(pipeline);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_no_drops_but_no_fallback_frames_live() {
|
|
|
|
test_no_drops_but_no_fallback_frames(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_no_drops_but_no_fallback_frames_not_live() {
|
|
|
|
test_no_drops_but_no_fallback_frames(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_no_drops_but_no_fallback_frames(live: bool) {
|
2021-09-13 15:08:45 +00:00
|
|
|
let pipeline = setup_pipeline(Some(live), None, None);
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2021-05-28 16:35:28 +00:00
|
|
|
push_buffer(&pipeline, gst::ClockTime::ZERO);
|
2019-08-17 08:47:05 +00:00
|
|
|
// +10ms needed here because the immediate timeout will be always at running time 0, but
|
|
|
|
// aggregator also adds the latency to it so we end up at 10ms instead.
|
2021-09-13 15:08:45 +00:00
|
|
|
set_time(&pipeline, LATENCY);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2021-05-28 16:35:28 +00:00
|
|
|
assert_buffer!(buffer, Some(gst::ClockTime::ZERO));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2022-10-17 17:48:43 +00:00
|
|
|
push_buffer(&pipeline, 1.seconds());
|
|
|
|
set_time(&pipeline, 1.seconds() + LATENCY);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2021-05-28 16:35:28 +00:00
|
|
|
assert_buffer!(buffer, Some(gst::ClockTime::SECOND));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2022-10-17 17:48:43 +00:00
|
|
|
push_buffer(&pipeline, 2.seconds());
|
|
|
|
set_time(&pipeline, 2.seconds() + LATENCY);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_buffer!(buffer, Some(2.seconds()));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// EOS on the fallback should not be required
|
|
|
|
push_eos(&pipeline);
|
|
|
|
wait_eos(&pipeline);
|
|
|
|
|
|
|
|
stop_pipeline(pipeline);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_short_drop_live() {
|
|
|
|
test_short_drop(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_short_drop_not_live() {
|
|
|
|
test_short_drop(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_short_drop(live: bool) {
|
2021-09-13 15:08:45 +00:00
|
|
|
let pipeline = setup_pipeline(Some(live), None, None);
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2021-05-28 16:35:28 +00:00
|
|
|
push_buffer(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
push_fallback_buffer(&pipeline, gst::ClockTime::ZERO);
|
2021-09-13 15:08:45 +00:00
|
|
|
set_time(&pipeline, gst::ClockTime::ZERO + LATENCY);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2021-05-28 16:35:28 +00:00
|
|
|
assert_buffer!(buffer, Some(gst::ClockTime::ZERO));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// A timeout at 1s will get rid of the fallback buffer
|
|
|
|
// but not output anything
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 1.seconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
// Time out the fallback buffer at +10ms
|
2022-10-17 17:48:43 +00:00
|
|
|
set_time(&pipeline, 1.seconds() + 10.mseconds());
|
2021-05-28 16:35:28 +00:00
|
|
|
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 2.seconds());
|
|
|
|
push_buffer(&pipeline, 2.seconds());
|
|
|
|
set_time(&pipeline, 2.seconds() + LATENCY);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_buffer!(buffer, Some(2.seconds()));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
push_eos(&pipeline);
|
|
|
|
push_fallback_eos(&pipeline);
|
|
|
|
wait_eos(&pipeline);
|
|
|
|
|
|
|
|
stop_pipeline(pipeline);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_long_drop_and_eos_live() {
|
|
|
|
test_long_drop_and_eos(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_long_drop_and_eos_not_live() {
|
|
|
|
test_long_drop_and_eos(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_long_drop_and_eos(live: bool) {
|
2021-09-13 15:08:45 +00:00
|
|
|
let pipeline = setup_pipeline(Some(live), None, None);
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce the first frame
|
2021-05-28 16:35:28 +00:00
|
|
|
push_buffer(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
push_fallback_buffer(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
set_time(&pipeline, gst::ClockTime::ZERO);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2021-05-28 16:35:28 +00:00
|
|
|
assert_buffer!(buffer, Some(gst::ClockTime::ZERO));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce a second frame but only from the fallback source
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 1.seconds());
|
|
|
|
set_time(&pipeline, 1.seconds() + 10.mseconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce a third frame but only from the fallback source
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 2.seconds());
|
|
|
|
set_time(&pipeline, 2.seconds() + 10.mseconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce a fourth frame but only from the fallback source
|
|
|
|
// This should be output now
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 3.seconds());
|
|
|
|
set_time(&pipeline, 3.seconds() + 10.mseconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_fallback_buffer!(buffer, Some(3.seconds()));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce a fifth frame but only from the fallback source
|
|
|
|
// This should be output now
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 4.seconds());
|
|
|
|
set_time(&pipeline, 4.seconds() + 10.mseconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_fallback_buffer!(buffer, Some(4.seconds()));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Wait for EOS to arrive at appsink
|
|
|
|
push_eos(&pipeline);
|
|
|
|
push_fallback_eos(&pipeline);
|
|
|
|
wait_eos(&pipeline);
|
|
|
|
|
|
|
|
stop_pipeline(pipeline);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_long_drop_and_recover_live() {
|
|
|
|
test_long_drop_and_recover(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_long_drop_and_recover_not_live() {
|
|
|
|
test_long_drop_and_recover(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_long_drop_and_recover(live: bool) {
|
2021-09-13 15:08:45 +00:00
|
|
|
let pipeline = setup_pipeline(Some(live), None, None);
|
|
|
|
let switch = pipeline.by_name("switch").unwrap();
|
|
|
|
let mainsink = switch.static_pad("sink_0").unwrap();
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce the first frame
|
2021-05-28 16:35:28 +00:00
|
|
|
push_buffer(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
push_fallback_buffer(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
set_time(&pipeline, gst::ClockTime::ZERO);
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2021-05-28 16:35:28 +00:00
|
|
|
assert_buffer!(buffer, Some(gst::ClockTime::ZERO));
|
2021-09-13 15:08:45 +00:00
|
|
|
assert!(mainsink.property::<bool>("is-healthy"));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce a second frame but only from the fallback source
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 1.seconds());
|
|
|
|
set_time(&pipeline, 1.seconds() + 10.mseconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce a third frame but only from the fallback source
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 2.seconds());
|
|
|
|
set_time(&pipeline, 2.seconds() + 10.mseconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce a fourth frame but only from the fallback source
|
|
|
|
// This should be output now
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 3.seconds());
|
|
|
|
set_time(&pipeline, 3.seconds() + 10.mseconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_fallback_buffer!(buffer, Some(3.seconds()));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce a fifth frame but only from the fallback source
|
|
|
|
// This should be output now
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 4.seconds());
|
|
|
|
set_time(&pipeline, 4.seconds() + 10.mseconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_fallback_buffer!(buffer, Some(4.seconds()));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2023-04-12 16:30:54 +00:00
|
|
|
// Produce a sixth frame from the normal source, which
|
|
|
|
// will make it healthy again
|
2022-10-17 17:48:43 +00:00
|
|
|
push_buffer(&pipeline, 5.seconds());
|
|
|
|
set_time(&pipeline, 5.seconds() + 10.mseconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_buffer!(buffer, Some(5.seconds()));
|
2023-04-12 16:30:54 +00:00
|
|
|
assert!(mainsink.property::<bool>("is-healthy"));
|
2021-09-13 15:08:45 +00:00
|
|
|
drop(mainsink);
|
|
|
|
drop(switch);
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce a seventh frame from the normal source but no fallback.
|
|
|
|
// This should still be output immediately
|
2022-10-17 17:48:43 +00:00
|
|
|
push_buffer(&pipeline, 6.seconds());
|
|
|
|
set_time(&pipeline, 6.seconds() + 10.mseconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_buffer!(buffer, Some(6.seconds()));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Produce a eight frame from the normal source
|
2022-10-17 17:48:43 +00:00
|
|
|
push_buffer(&pipeline, 7.seconds());
|
|
|
|
push_fallback_buffer(&pipeline, 7.seconds());
|
|
|
|
set_time(&pipeline, 7.seconds() + 10.mseconds());
|
2019-08-17 08:47:05 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_buffer!(buffer, Some(7.seconds()));
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Wait for EOS to arrive at appsink
|
|
|
|
push_eos(&pipeline);
|
|
|
|
push_fallback_eos(&pipeline);
|
|
|
|
wait_eos(&pipeline);
|
|
|
|
|
|
|
|
stop_pipeline(pipeline);
|
|
|
|
}
|
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
#[test]
|
|
|
|
fn test_initial_timeout_live() {
|
|
|
|
test_initial_timeout(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_initial_timeout_not_live() {
|
|
|
|
test_initial_timeout(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_initial_timeout(live: bool) {
|
|
|
|
let pipeline = setup_pipeline(Some(live), None, None);
|
|
|
|
|
|
|
|
// Produce the first frame but only from the fallback source
|
|
|
|
push_fallback_buffer(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
set_time(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
|
|
|
|
// Produce a second frame but only from the fallback source
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 1.seconds());
|
|
|
|
set_time(&pipeline, 1.seconds() + 10.mseconds());
|
2021-09-13 15:08:45 +00:00
|
|
|
|
|
|
|
// Produce a third frame but only from the fallback source
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 2.seconds());
|
|
|
|
set_time(&pipeline, 2.seconds() + 10.mseconds());
|
2021-09-13 15:08:45 +00:00
|
|
|
|
|
|
|
// Produce a fourth frame but only from the fallback source
|
|
|
|
// This should be output now
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 3.seconds());
|
|
|
|
set_time(&pipeline, 3.seconds() + 10.mseconds());
|
2021-09-13 15:08:45 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_fallback_buffer!(buffer, Some(3.seconds()));
|
2021-09-13 15:08:45 +00:00
|
|
|
|
|
|
|
// Produce a fifth frame but only from the fallback source
|
|
|
|
// This should be output now
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 4.seconds());
|
|
|
|
set_time(&pipeline, 4.seconds() + 10.mseconds());
|
2021-09-13 15:08:45 +00:00
|
|
|
let buffer = pull_buffer(&pipeline);
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_fallback_buffer!(buffer, Some(4.seconds()));
|
2021-09-13 15:08:45 +00:00
|
|
|
|
|
|
|
// Wait for EOS to arrive at appsink
|
|
|
|
push_eos(&pipeline);
|
|
|
|
push_fallback_eos(&pipeline);
|
|
|
|
wait_eos(&pipeline);
|
|
|
|
|
|
|
|
stop_pipeline(pipeline);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_immediate_fallback_live() {
|
|
|
|
test_immediate_fallback(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_immediate_fallback_not_live() {
|
|
|
|
test_immediate_fallback(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_immediate_fallback(live: bool) {
|
|
|
|
let pipeline = setup_pipeline(Some(live), Some(true), None);
|
|
|
|
|
|
|
|
// Produce the first frame but only from the fallback source
|
|
|
|
push_fallback_buffer(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
set_time(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
|
|
|
|
let buffer = pull_buffer(&pipeline);
|
|
|
|
assert_fallback_buffer!(buffer, Some(gst::ClockTime::ZERO));
|
|
|
|
|
|
|
|
// Wait for EOS to arrive at appsink
|
|
|
|
push_eos(&pipeline);
|
|
|
|
push_fallback_eos(&pipeline);
|
|
|
|
wait_eos(&pipeline);
|
|
|
|
|
|
|
|
stop_pipeline(pipeline);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_manual_switch_live() {
|
|
|
|
test_manual_switch(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_manual_switch_not_live() {
|
|
|
|
test_manual_switch(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_manual_switch(live: bool) {
|
|
|
|
let pipeline = setup_pipeline(Some(live), None, Some(false));
|
|
|
|
let switch = pipeline.by_name("switch").unwrap();
|
|
|
|
let mainsink = switch.static_pad("sink_0").unwrap();
|
|
|
|
let fallbacksink = switch.static_pad("sink_1").unwrap();
|
|
|
|
|
|
|
|
switch.set_property("active-pad", &mainsink);
|
|
|
|
push_buffer(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
push_fallback_buffer(&pipeline, gst::ClockTime::ZERO);
|
|
|
|
set_time(&pipeline, gst::ClockTime::ZERO + LATENCY);
|
|
|
|
let buffer = pull_buffer(&pipeline);
|
|
|
|
assert_buffer!(buffer, Some(gst::ClockTime::ZERO));
|
|
|
|
|
|
|
|
switch.set_property("active-pad", &fallbacksink);
|
2022-10-17 17:48:43 +00:00
|
|
|
push_fallback_buffer(&pipeline, 1.seconds());
|
|
|
|
push_buffer(&pipeline, 1.seconds());
|
|
|
|
set_time(&pipeline, 1.seconds() + LATENCY);
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut buffer = pull_buffer(&pipeline);
|
2022-04-18 13:11:25 +00:00
|
|
|
// FIXME: Sometimes we first get the ZERO buffer from the fallback sink
|
2021-09-13 15:08:45 +00:00
|
|
|
if buffer.pts() == Some(gst::ClockTime::ZERO) {
|
|
|
|
buffer = pull_buffer(&pipeline);
|
|
|
|
}
|
|
|
|
assert_fallback_buffer!(buffer, Some(gst::ClockTime::SECOND));
|
|
|
|
|
|
|
|
switch.set_property("active-pad", &mainsink);
|
2022-10-17 17:48:43 +00:00
|
|
|
push_buffer(&pipeline, 2.seconds());
|
|
|
|
push_fallback_buffer(&pipeline, 2.seconds());
|
|
|
|
set_time(&pipeline, 2.seconds() + LATENCY);
|
2022-04-18 13:11:25 +00:00
|
|
|
buffer = pull_buffer(&pipeline);
|
|
|
|
// FIXME: Sometimes we first get the 1sec buffer from the main sink
|
|
|
|
if buffer.pts() == Some(gst::ClockTime::SECOND) {
|
|
|
|
buffer = pull_buffer(&pipeline);
|
|
|
|
}
|
2022-10-17 17:48:43 +00:00
|
|
|
assert_buffer!(buffer, Some(2.seconds()));
|
2021-09-13 15:08:45 +00:00
|
|
|
|
|
|
|
drop(mainsink);
|
|
|
|
drop(fallbacksink);
|
|
|
|
drop(switch);
|
|
|
|
// EOS on the fallback should not be required
|
|
|
|
push_eos(&pipeline);
|
|
|
|
wait_eos(&pipeline);
|
|
|
|
|
|
|
|
stop_pipeline(pipeline);
|
|
|
|
}
|
|
|
|
|
2019-08-17 08:47:05 +00:00
|
|
|
struct Pipeline {
|
|
|
|
pipeline: gst::Pipeline,
|
|
|
|
clock_join_handle: Option<std::thread::JoinHandle<()>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::Deref for Pipeline {
|
|
|
|
type Target = gst::Pipeline;
|
|
|
|
|
|
|
|
fn deref(&self) -> &gst::Pipeline {
|
|
|
|
&self.pipeline
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
fn setup_pipeline(
|
|
|
|
with_live_fallback: Option<bool>,
|
|
|
|
immediate_fallback: Option<bool>,
|
|
|
|
auto_switch: Option<bool>,
|
|
|
|
) -> Pipeline {
|
2019-08-17 08:47:05 +00:00
|
|
|
init();
|
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
debug!(TEST_CAT, "Setting up pipeline");
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
let clock = gst_check::TestClock::new();
|
2021-05-28 16:35:28 +00:00
|
|
|
clock.set_time(gst::ClockTime::ZERO);
|
2022-10-22 16:06:29 +00:00
|
|
|
let pipeline = gst::Pipeline::default();
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
// Running time 0 in our pipeline is going to be clock time 1s. All
|
|
|
|
// clock ids before 1s are used for signalling to our clock advancing
|
|
|
|
// thread.
|
|
|
|
pipeline.use_clock(Some(&clock));
|
2021-05-28 16:35:28 +00:00
|
|
|
pipeline.set_base_time(gst::ClockTime::SECOND);
|
|
|
|
pipeline.set_start_time(gst::ClockTime::NONE);
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2022-10-22 16:06:29 +00:00
|
|
|
let src = gst_app::AppSrc::builder()
|
2022-10-19 16:18:43 +00:00
|
|
|
.name("src")
|
2022-10-22 16:06:29 +00:00
|
|
|
.is_live(true)
|
|
|
|
.format(gst::Format::Time)
|
|
|
|
.min_latency(LATENCY.nseconds() as i64)
|
|
|
|
.caps(
|
|
|
|
&gst_video::VideoCapsBuilder::new()
|
|
|
|
.format(gst_video::VideoFormat::Argb)
|
|
|
|
.width(320)
|
|
|
|
.height(240)
|
|
|
|
.framerate((0, 1).into())
|
|
|
|
.build(),
|
|
|
|
)
|
|
|
|
.build();
|
2022-10-19 16:18:43 +00:00
|
|
|
|
|
|
|
let switch = gst::ElementFactory::make("fallbackswitch")
|
|
|
|
.name("switch")
|
|
|
|
.property("timeout", 3.seconds())
|
2024-11-20 19:11:02 +00:00
|
|
|
.property_if_some("immediate-fallback", immediate_fallback)
|
|
|
|
.property_if_some("auto-switch", auto_switch)
|
2022-10-19 16:18:43 +00:00
|
|
|
.build()
|
|
|
|
.unwrap();
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2022-10-22 16:06:29 +00:00
|
|
|
let sink = gst_app::AppSink::builder().name("sink").sync(false).build();
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let queue = gst::ElementFactory::make("queue").build().unwrap();
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
pipeline
|
2023-03-09 14:46:52 +00:00
|
|
|
.add_many([src.upcast_ref(), &switch, &queue, sink.upcast_ref()])
|
2019-08-17 08:47:05 +00:00
|
|
|
.unwrap();
|
2021-09-13 15:08:45 +00:00
|
|
|
src.link_pads(Some("src"), &switch, Some("sink_0")).unwrap();
|
2019-08-17 08:47:05 +00:00
|
|
|
switch.link_pads(Some("src"), &queue, Some("sink")).unwrap();
|
|
|
|
queue.link_pads(Some("src"), &sink, Some("sink")).unwrap();
|
|
|
|
|
2022-04-13 11:07:49 +00:00
|
|
|
let sink_pad = switch.static_pad("sink_0").unwrap();
|
|
|
|
sink_pad.set_property("priority", 0u32);
|
|
|
|
|
2019-08-17 08:47:05 +00:00
|
|
|
if let Some(live) = with_live_fallback {
|
2022-10-22 16:06:29 +00:00
|
|
|
let fallback_src = gst_app::AppSrc::builder()
|
2022-10-19 16:18:43 +00:00
|
|
|
.name("fallback-src")
|
2022-10-22 16:06:29 +00:00
|
|
|
.is_live(live)
|
|
|
|
.format(gst::Format::Time)
|
|
|
|
.min_latency(LATENCY.nseconds() as i64)
|
|
|
|
.caps(
|
|
|
|
&gst_video::VideoCapsBuilder::new()
|
|
|
|
.format(gst_video::VideoFormat::Argb)
|
|
|
|
.width(160)
|
|
|
|
.height(120)
|
|
|
|
.framerate((0, 1).into())
|
|
|
|
.build(),
|
|
|
|
)
|
|
|
|
.build();
|
2019-08-17 08:47:05 +00:00
|
|
|
|
|
|
|
pipeline.add(&fallback_src).unwrap();
|
|
|
|
|
|
|
|
fallback_src
|
2021-09-13 15:08:45 +00:00
|
|
|
.link_pads(Some("src"), &switch, Some("sink_1"))
|
2019-08-17 08:47:05 +00:00
|
|
|
.unwrap();
|
2022-04-13 11:07:49 +00:00
|
|
|
let sink_pad = switch.static_pad("sink_1").unwrap();
|
|
|
|
sink_pad.set_property("priority", 1u32);
|
2019-08-17 08:47:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pipeline.set_state(gst::State::Playing).unwrap();
|
|
|
|
|
|
|
|
let clock_join_handle = std::thread::spawn(move || {
|
|
|
|
loop {
|
|
|
|
while let Some(clock_id) = clock.peek_next_pending_id().and_then(|clock_id| {
|
|
|
|
// Process if the clock ID is in the past or now
|
2024-06-19 08:11:18 +00:00
|
|
|
if clock.time().is_some_and(|time| time >= clock_id.time()) {
|
2019-08-17 08:47:05 +00:00
|
|
|
Some(clock_id)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}) {
|
2021-09-13 15:08:45 +00:00
|
|
|
debug!(
|
|
|
|
TEST_CAT,
|
|
|
|
"Processing clock ID {} at {:?}",
|
|
|
|
clock_id.time(),
|
|
|
|
clock.time()
|
|
|
|
);
|
2019-08-17 08:47:05 +00:00
|
|
|
if let Some(clock_id) = clock.process_next_clock_id() {
|
2021-09-13 15:08:45 +00:00
|
|
|
debug!(TEST_CAT, "Processed clock ID {}", clock_id.time());
|
2021-05-28 16:35:28 +00:00
|
|
|
if clock_id.time().is_zero() {
|
2021-09-13 15:08:45 +00:00
|
|
|
debug!(TEST_CAT, "Stopping clock thread");
|
2019-08-17 08:47:05 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sleep for 5ms as long as we have pending clock IDs that are in the future
|
|
|
|
// at the top of the queue. We don't want to do a busy loop here.
|
|
|
|
while clock.peek_next_pending_id().iter().any(|clock_id| {
|
|
|
|
// Sleep if the clock ID is in the future
|
2021-05-28 16:35:28 +00:00
|
|
|
// FIXME probably can expect clock.time()
|
|
|
|
clock
|
|
|
|
.time()
|
|
|
|
.map_or(true, |clock_time| clock_time < clock_id.time())
|
2019-08-17 08:47:05 +00:00
|
|
|
}) {
|
|
|
|
use std::{thread, time};
|
|
|
|
|
|
|
|
thread::sleep(time::Duration::from_millis(10));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise if there are none (or they are ready now) wait until there are
|
|
|
|
// clock ids again
|
|
|
|
let _ = clock.wait_for_next_pending_id();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Pipeline {
|
|
|
|
pipeline,
|
|
|
|
clock_join_handle: Some(clock_join_handle),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn push_buffer(pipeline: &Pipeline, time: gst::ClockTime) {
|
|
|
|
let src = pipeline
|
2021-04-20 12:58:11 +00:00
|
|
|
.by_name("src")
|
2019-08-17 08:47:05 +00:00
|
|
|
.unwrap()
|
|
|
|
.downcast::<gst_app::AppSrc>()
|
|
|
|
.unwrap();
|
|
|
|
let mut buffer = gst::Buffer::with_size(320 * 240 * 4).unwrap();
|
|
|
|
{
|
|
|
|
let buffer = buffer.get_mut().unwrap();
|
|
|
|
buffer.set_pts(time);
|
|
|
|
}
|
|
|
|
src.push_buffer(buffer).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn push_fallback_buffer(pipeline: &Pipeline, time: gst::ClockTime) {
|
|
|
|
let src = pipeline
|
2021-04-20 12:58:11 +00:00
|
|
|
.by_name("fallback-src")
|
2019-08-17 08:47:05 +00:00
|
|
|
.unwrap()
|
|
|
|
.downcast::<gst_app::AppSrc>()
|
|
|
|
.unwrap();
|
|
|
|
let mut buffer = gst::Buffer::with_size(160 * 120 * 4).unwrap();
|
|
|
|
{
|
|
|
|
let buffer = buffer.get_mut().unwrap();
|
|
|
|
buffer.set_pts(time);
|
|
|
|
}
|
|
|
|
src.push_buffer(buffer).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn push_eos(pipeline: &Pipeline) {
|
|
|
|
let src = pipeline
|
2021-04-20 12:58:11 +00:00
|
|
|
.by_name("src")
|
2019-08-17 08:47:05 +00:00
|
|
|
.unwrap()
|
|
|
|
.downcast::<gst_app::AppSrc>()
|
|
|
|
.unwrap();
|
|
|
|
src.end_of_stream().unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn push_fallback_eos(pipeline: &Pipeline) {
|
|
|
|
let src = pipeline
|
2021-04-20 12:58:11 +00:00
|
|
|
.by_name("fallback-src")
|
2019-08-17 08:47:05 +00:00
|
|
|
.unwrap()
|
|
|
|
.downcast::<gst_app::AppSrc>()
|
|
|
|
.unwrap();
|
|
|
|
src.end_of_stream().unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn pull_buffer(pipeline: &Pipeline) -> gst::Buffer {
|
|
|
|
let sink = pipeline
|
2021-04-20 12:58:11 +00:00
|
|
|
.by_name("sink")
|
2019-08-17 08:47:05 +00:00
|
|
|
.unwrap()
|
|
|
|
.downcast::<gst_app::AppSink>()
|
|
|
|
.unwrap();
|
|
|
|
let sample = sink.pull_sample().unwrap();
|
2021-04-12 12:49:54 +00:00
|
|
|
sample.buffer_owned().unwrap()
|
2019-08-17 08:47:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn set_time(pipeline: &Pipeline, time: gst::ClockTime) {
|
|
|
|
let clock = pipeline
|
2021-04-12 12:49:54 +00:00
|
|
|
.clock()
|
2019-08-17 08:47:05 +00:00
|
|
|
.unwrap()
|
|
|
|
.downcast::<gst_check::TestClock>()
|
|
|
|
.unwrap();
|
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
debug!(TEST_CAT, "Setting time to {}", time);
|
2021-05-28 16:35:28 +00:00
|
|
|
clock.set_time(gst::ClockTime::SECOND + time);
|
2019-08-17 08:47:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn wait_eos(pipeline: &Pipeline) {
|
|
|
|
let sink = pipeline
|
2021-04-20 12:58:11 +00:00
|
|
|
.by_name("sink")
|
2019-08-17 08:47:05 +00:00
|
|
|
.unwrap()
|
|
|
|
.downcast::<gst_app::AppSink>()
|
|
|
|
.unwrap();
|
|
|
|
// FIXME: Ideally without a sleep
|
|
|
|
loop {
|
|
|
|
use std::{thread, time};
|
|
|
|
|
|
|
|
if sink.is_eos() {
|
2021-09-13 15:08:45 +00:00
|
|
|
debug!(TEST_CAT, "Waited for EOS");
|
2019-08-17 08:47:05 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
thread::sleep(time::Duration::from_millis(10));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn stop_pipeline(mut pipeline: Pipeline) {
|
|
|
|
pipeline.set_state(gst::State::Null).unwrap();
|
|
|
|
|
|
|
|
let clock = pipeline
|
2021-04-12 12:49:54 +00:00
|
|
|
.clock()
|
2019-08-17 08:47:05 +00:00
|
|
|
.unwrap()
|
|
|
|
.downcast::<gst_check::TestClock>()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// Signal shutdown to the clock thread
|
2021-05-28 16:35:28 +00:00
|
|
|
let clock_id = clock.new_single_shot_id(gst::ClockTime::ZERO);
|
2019-09-18 11:28:06 +00:00
|
|
|
let _ = clock_id.wait();
|
2019-08-17 08:47:05 +00:00
|
|
|
|
2021-04-20 12:58:11 +00:00
|
|
|
let switch = pipeline.by_name("switch").unwrap();
|
2019-08-17 08:47:05 +00:00
|
|
|
let switch_weak = switch.downgrade();
|
|
|
|
drop(switch);
|
|
|
|
let pipeline_weak = pipeline.downgrade();
|
|
|
|
|
|
|
|
pipeline.clock_join_handle.take().unwrap().join().unwrap();
|
|
|
|
drop(pipeline);
|
|
|
|
|
|
|
|
assert!(switch_weak.upgrade().is_none());
|
|
|
|
assert!(pipeline_weak.upgrade().is_none());
|
|
|
|
}
|