fmp4mux: Added tests for caps change feature

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1762>
This commit is contained in:
Jochen Henneberg 2024-08-21 09:48:51 +02:00
parent 0c8b84d8a8
commit 1be92c4b86

View file

@ -2363,6 +2363,45 @@ fn test_chunking_single_stream_gops_after_fragment_end_after_next_chunk_end() {
assert_eq!(ev.type_(), gst::EventType::Eos); assert_eq!(ev.type_(), gst::EventType::Eos);
} }
#[test]
fn test_early_eos() {
init();
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32)
.field("height", 1080i32)
.field("framerate", gst::Fraction::new(30, 1))
.field("stream-format", "avc")
.field("alignment", "au")
.field("codec_data", gst::Buffer::with_size(1).unwrap())
.build();
h.element()
.unwrap()
.set_property("fragment-duration", 1.seconds());
h.set_src_caps(caps);
h.play();
for i in 0..5 {
let mut buffer = gst::Buffer::with_size(1).unwrap();
{
let buffer = buffer.get_mut().unwrap();
buffer.set_pts(i * 100.mseconds());
buffer.set_dts(i * 100.mseconds());
buffer.set_duration(100.mseconds());
buffer.set_flags(gst::BufferFlags::DELTA_UNIT);
}
assert_eq!(h.push(buffer), Ok(gst::FlowSuccess::Ok));
}
h.push_event(gst::event::Eos::new());
assert_eq!(h.buffers_in_queue(), 0);
}
#[test] #[test]
fn test_roundtrip_vp9_flac() { fn test_roundtrip_vp9_flac() {
init(); init();
@ -2380,3 +2419,409 @@ fn test_roundtrip_vp9_flac() {
let pipeline = pipeline.downcast().unwrap(); let pipeline = pipeline.downcast().unwrap();
to_completion(&pipeline); to_completion(&pipeline);
} }
#[track_caller]
fn test_caps_changed_verify(
h: &mut gst_check::Harness,
num_bufs: usize,
caps_changed: bool,
chunk: bool,
) {
for i in 0..num_bufs {
let b = h.pull().unwrap();
// FIXME: Rust 1.71 does not detect that the match is exhaustive so a `_` pattern has to be
// added, but newer Rust warns (correctly) about that pattern being unreachable.
#[allow(unreachable_patterns)]
match (caps_changed, i, chunk) {
(true, 0, _) => assert_eq!(
b.flags(),
gst::BufferFlags::HEADER | gst::BufferFlags::DISCONT
),
(false, 0, false) | (true, 1, false) => assert_eq!(b.flags(), gst::BufferFlags::HEADER),
(false, 0, true) | (true, 1, true) => assert_eq!(
b.flags(),
gst::BufferFlags::HEADER | gst::BufferFlags::DELTA_UNIT
),
(false, 1, _) | (_, 2.., _) => {
if i == num_bufs - 1 {
assert_eq!(
b.flags(),
gst::BufferFlags::MARKER | gst::BufferFlags::DELTA_UNIT
);
} else {
assert_eq!(b.flags(), gst::BufferFlags::DELTA_UNIT);
}
}
_ => unreachable!(),
}
}
}
#[track_caller]
fn test_caps_changed_buffers(
h: &mut gst_check::Harness,
num_bufs: u64,
gop_size: u64,
caps_change: u64,
duration: u64,
key_frame_on_caps_change: bool,
drop_first_buffer: bool,
) {
for i in 0..num_bufs {
let mut buffer = gst::Buffer::with_size(1).unwrap();
{
let buffer = buffer.get_mut().unwrap();
buffer.set_pts(i * duration.mseconds());
buffer.set_dts(i * duration.mseconds());
buffer.set_duration(duration.mseconds());
if i % gop_size != 0 && (i != caps_change || !key_frame_on_caps_change) {
buffer.set_flags(gst::BufferFlags::DELTA_UNIT);
}
}
if i == 0 && drop_first_buffer {
continue;
}
if i == caps_change {
let caps = gst::Caps::builder("video/x-h264")
.field("width", 1280i32)
.field("height", 720i32)
.field("framerate", gst::Fraction::new(30, 1))
.field("stream-format", "avc")
.field("alignment", "au")
.field("codec_data", gst::Buffer::from_slice([1, 2, 3, 4]))
.build();
h.push_event(gst::event::Caps::new(&caps));
}
assert_eq!(h.push(buffer), Ok(gst::FlowSuccess::Ok));
}
}
#[test]
fn test_caps_change_at_gop_boundary() {
init();
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32)
.field("height", 1080i32)
.field("framerate", gst::Fraction::new(30, 1))
.field("stream-format", "avc")
.field("alignment", "au")
.field("codec_data", gst::Buffer::from_slice([1, 2, 3, 4]))
.build();
h.element()
.unwrap()
.set_property("fragment-duration", 1.seconds());
h.set_src_caps(caps);
h.play();
test_caps_changed_buffers(&mut h, 30, 10, 10, 100, true, false);
h.crank_single_clock_wait().unwrap();
// Initial fragment with HEADER and DISCONT
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
h.crank_single_clock_wait().unwrap();
// Full GOP with HEADER and DISCONT due to caps change
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
h.crank_single_clock_wait().unwrap();
h.push_event(gst::event::Eos::new());
// Full GOP with HEADER but no DISCONT because no caps change
test_caps_changed_verify(&mut h, 1 + 10, false, false);
assert_eq!(h.buffers_in_queue(), 0);
}
#[test]
fn test_caps_change_at_gop_boundary_compatible() {
init();
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
let caps = gst::Caps::builder("video/x-h264")
.field("width", 1280i32)
.field("height", 720i32)
.field("framerate", gst::Fraction::new(10, 1))
.field("stream-format", "avc")
.field("alignment", "au")
.field("codec_data", gst::Buffer::from_slice([1, 2, 3, 4]))
.build();
h.element()
.unwrap()
.set_property("fragment-duration", 1.seconds());
h.set_src_caps(caps);
h.play();
test_caps_changed_buffers(&mut h, 30, 10, 10, 100, true, false);
h.crank_single_clock_wait().unwrap();
// Initial fragment with HEADER and DISCONT
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
h.crank_single_clock_wait().unwrap();
// Full GOP with HEADER but no DISCONT because compatible caps
// change
test_caps_changed_verify(&mut h, 1 + 10, false, false);
h.crank_single_clock_wait().unwrap();
h.push_event(gst::event::Eos::new());
// Full GOP with HEADER but no DISCONT because no caps change
test_caps_changed_verify(&mut h, 1 + 10, false, false);
assert_eq!(h.buffers_in_queue(), 0);
}
#[test]
fn test_caps_change_at_gop_boundary_not_allowed() {
init();
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32)
.field("height", 1080i32)
.field("framerate", gst::Fraction::new(30, 1))
.field("stream-format", "avc")
.field("alignment", "au")
.field("codec_data", gst::Buffer::from_slice([1, 2, 3, 4]))
.build();
h.element()
.unwrap()
.set_property("fragment-duration", 1.seconds());
h.element()
.unwrap()
.set_property_from_str("header-update-mode", "rewrite");
h.set_src_caps(caps);
h.play();
test_caps_changed_buffers(&mut h, 30, 10, 10, 100, true, false);
h.crank_single_clock_wait().unwrap();
// Initial fragment with HEADER and DISCONT
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
h.crank_single_clock_wait().unwrap();
// Full GOP with HEADER but no DISCONT because caps change not
// allowed from header-update-modex
test_caps_changed_verify(&mut h, 1 + 10, false, false);
h.crank_single_clock_wait().unwrap();
h.push_event(gst::event::Eos::new());
// Full GOP with HEADER but no DISCONT because no caps change
test_caps_changed_verify(&mut h, 1 + 10, false, false);
assert_eq!(h.buffers_in_queue(), 0);
}
#[test]
fn test_caps_change_within_gop() {
init();
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32)
.field("height", 1080i32)
.field("framerate", gst::Fraction::new(30, 1))
.field("stream-format", "avc")
.field("alignment", "au")
.field("codec_data", gst::Buffer::from_slice([1, 2, 3, 4]))
.build();
h.element()
.unwrap()
.set_property("fragment-duration", 1.seconds());
h.set_src_caps(caps);
h.play();
test_caps_changed_buffers(&mut h, 20, 10, 5, 100, true, false);
h.crank_single_clock_wait().unwrap();
// Initial fragment with HEADER and DISCONT
test_caps_changed_verify(&mut h, 1 + 1 + 5, true, false);
h.crank_single_clock_wait().unwrap();
// Reduced GOP with HEADER and DISCONT due to caps change
test_caps_changed_verify(&mut h, 1 + 1 + 5, true, false);
h.crank_single_clock_wait().unwrap();
h.push_event(gst::event::Eos::new());
// Full GOP with HEADER but no DISCONT because no caps change
test_caps_changed_verify(&mut h, 1 + 10, false, false);
assert_eq!(h.buffers_in_queue(), 0);
}
#[test]
fn test_caps_change_within_gop_start_without_key() {
init();
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32)
.field("height", 1080i32)
.field("framerate", gst::Fraction::new(30, 1))
.field("stream-format", "avc")
.field("alignment", "au")
.field("codec_data", gst::Buffer::from_slice([1, 2, 3, 4]))
.build();
h.element()
.unwrap()
.set_property("fragment-duration", 1.seconds());
h.set_src_caps(caps);
h.play();
test_caps_changed_buffers(&mut h, 20, 10, 5, 100, true, true);
// Same as test_caps_change_within_gop() but without the first
// fragment since all frames are dropped due to missing key frame
h.crank_single_clock_wait().unwrap();
// Reduced GOP with HEADER and DISCONT due to caps change
test_caps_changed_verify(&mut h, 1 + 1 + 5, true, false);
h.crank_single_clock_wait().unwrap();
h.push_event(gst::event::Eos::new());
// Full GOP with HEADER but no DISCONT because no caps change
test_caps_changed_verify(&mut h, 1 + 10, false, false);
assert_eq!(h.buffers_in_queue(), 0);
}
#[test]
fn test_caps_change_within_gop_chunked() {
init();
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32)
.field("height", 1080i32)
.field("framerate", gst::Fraction::new(30, 1))
.field("stream-format", "avc")
.field("alignment", "au")
.field("codec_data", gst::Buffer::from_slice([1, 2, 3, 4]))
.build();
h.element()
.unwrap()
.set_property("fragment-duration", 1.seconds());
h.element()
.unwrap()
.set_property("chunk-duration", 300.mseconds());
h.set_src_caps(caps);
h.play();
test_caps_changed_buffers(&mut h, 22, 10, 5, 30, true, false);
h.crank_single_clock_wait().unwrap();
// Initial fragment with HEADER and DISCONT
test_caps_changed_verify(&mut h, 1 + 1 + 5, true, false);
h.crank_single_clock_wait().unwrap();
// Fragment with HEADER and DISCONT due to caps change
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
h.crank_single_clock_wait().unwrap();
// Reduced chunk due to GOP end inbetween
test_caps_changed_verify(&mut h, 1 + 5, false, true);
h.crank_single_clock_wait().unwrap();
h.push_event(gst::event::Eos::new());
// Everything left until EOS
test_caps_changed_verify(&mut h, 1 + 2, false, true);
assert_eq!(h.buffers_in_queue(), 0);
}
#[test]
fn test_caps_change_within_gop_no_key() {
init();
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32)
.field("height", 1080i32)
.field("framerate", gst::Fraction::new(30, 1))
.field("stream-format", "avc")
.field("alignment", "au")
.field("codec_data", gst::Buffer::from_slice([1, 2, 3, 4]))
.build();
h.element()
.unwrap()
.set_property("fragment-duration", 1.seconds());
h.set_src_caps(caps);
h.play();
test_caps_changed_buffers(&mut h, 22, 10, 5, 100, false, false);
h.crank_single_clock_wait().unwrap();
// Initial fragment with HEADER and DISCONT
test_caps_changed_verify(&mut h, 1 + 1 + 5, true, false);
h.crank_single_clock_wait().unwrap();
// Reduced GOP with HEADER and DISCONT due to caps change
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
h.crank_single_clock_wait().unwrap();
h.push_event(gst::event::Eos::new());
// Everything left until EOS
test_caps_changed_verify(&mut h, 1 + 2, false, false);
assert_eq!(h.buffers_in_queue(), 0);
}
#[test]
fn test_caps_change_before_first_frame() {
init();
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32)
.field("height", 1080i32)
.field("framerate", gst::Fraction::new(30, 1))
.field("stream-format", "avc")
.field("alignment", "au")
.field("codec_data", gst::Buffer::from_slice([1, 2, 3, 4]))
.build();
h.element()
.unwrap()
.set_property("fragment-duration", 1.seconds());
h.set_src_caps(caps);
h.play();
test_caps_changed_buffers(&mut h, 22, 10, 0, 100, true, false);
h.crank_single_clock_wait().unwrap();
// Initial fragment with HEADER and DISCONT
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
h.crank_single_clock_wait().unwrap();
// 2nd fragment with HEADER
test_caps_changed_verify(&mut h, 1 + 10, false, false);
assert_eq!(h.buffers_in_queue(), 0);
}