From c5e41816139ddfe2f41ecc17eb10795b34346a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 9 Apr 2025 17:14:12 +0300 Subject: [PATCH] fmp4mux: Only allow caps-related header updates if header-update-mode=caps In none mode nothing is expecting updated headers, in the other existing modes the goal is to get an updated header at the end with the duration. So add a new mode specifically for caps changes. Part-of: --- docs/plugins/gst_plugins_cache.json | 5 ++++ mux/fmp4/src/fmp4mux/imp.rs | 31 ++++++++++++++------ mux/fmp4/src/fmp4mux/mod.rs | 37 ++++++++++++++++++++++++ mux/fmp4/tests/tests.rs | 44 +++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 9 deletions(-) diff --git a/docs/plugins/gst_plugins_cache.json b/docs/plugins/gst_plugins_cache.json index 4509931e9..6623d9f8c 100644 --- a/docs/plugins/gst_plugins_cache.json +++ b/docs/plugins/gst_plugins_cache.json @@ -3598,6 +3598,11 @@ "desc": "Update", "name": "update", "value": "2" + }, + { + "desc": "Caps", + "name": "caps", + "value": "3" } ] }, diff --git a/mux/fmp4/src/fmp4mux/imp.rs b/mux/fmp4/src/fmp4mux/imp.rs index c5a55e4d3..6deaaabab 100644 --- a/mux/fmp4/src/fmp4mux/imp.rs +++ b/mux/fmp4/src/fmp4mux/imp.rs @@ -921,14 +921,14 @@ impl FMP4Mux { } // Caps/tag changes are allowed only in case that the - // header-update-mode is None. + // header-update-mode is Caps. // // CAUTION: This function logs an error if operation is not // allowed so it should be evaluated only in case the caps/tags // would change otherwise (e. g. right-most operand in boolean // expressions). fn header_update_allowed(&self, reason: &str) -> bool { - if self.settings.lock().unwrap().header_update_mode == super::HeaderUpdateMode::None { + if self.settings.lock().unwrap().header_update_mode == super::HeaderUpdateMode::Caps { gst::debug!( CAT, imp = self, @@ -3643,17 +3643,30 @@ impl FMP4Mux { let class = aggregator.class(); let variant = class.as_ref().variant; - if settings.header_update_mode == super::HeaderUpdateMode::None && at_eos { + if [super::HeaderUpdateMode::None, super::HeaderUpdateMode::Caps] + .contains(&settings.header_update_mode) + && at_eos + { return Ok(None); } assert!(!at_eos || state.streams.iter().all(|s| s.queued_gops.is_empty())); - let duration = state - .end_pts - .opt_checked_sub(state.earliest_pts) - .ok() - .flatten(); + let duration = if at_eos + && [ + super::HeaderUpdateMode::Update, + super::HeaderUpdateMode::Rewrite, + ] + .contains(&settings.header_update_mode) + { + state + .end_pts + .opt_checked_sub(state.earliest_pts) + .ok() + .flatten() + } else { + None + }; let streams = state .streams @@ -3750,7 +3763,7 @@ impl FMP4Mux { match updated_header { Ok(Some((buffer_list, caps))) => { match settings.header_update_mode { - super::HeaderUpdateMode::None => unreachable!(), + super::HeaderUpdateMode::None | super::HeaderUpdateMode::Caps => unreachable!(), super::HeaderUpdateMode::Rewrite => { let mut q = gst::query::Seeking::new(gst::Format::Bytes); if self.obj().src_pad().peer_query(&mut q) && q.result().0 { diff --git a/mux/fmp4/src/fmp4mux/mod.rs b/mux/fmp4/src/fmp4mux/mod.rs index 1375dbb06..fd606c825 100644 --- a/mux/fmp4/src/fmp4mux/mod.rs +++ b/mux/fmp4/src/fmp4mux/mod.rs @@ -326,14 +326,51 @@ pub(crate) struct FragmentOffset { offset: u64, } +/** + * GstFMP4MuxHeaderUpdateMode: + * + * How and when updating of the header (`moov`, initialization segment) is allowed. + */ #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, Copy, PartialEq, Eq, glib::Enum)] #[repr(i32)] #[enum_type(name = "GstFMP4MuxHeaderUpdateMode")] pub(crate) enum HeaderUpdateMode { + /** + * GstFMP4MuxHeaderUpdateMode:none: + * + * Don't allow and do any header updates at all. + * + * Caps changes are not allowed in this mode. + */ None, + /** + * GstFMP4MuxHeaderUpdateMode:rewrite: + * + * Try rewriting the initial header with the overall duration at the very end. + * + * Caps changes are not allowed in this mode. + */ Rewrite, + /** + * GstFMP4MuxHeaderUpdateMode:update: + * + * Send an updated version of the initial header with the overall duration at + * the very end. + * + * Caps changes are not allowed in this mode. + */ Update, + /** + * GstFMP4MuxHeaderUpdateMode:caps: + * + * Send an updated header whenever caps or tag changes are pending that affect the initial + * header. The updated header does not have the duration set and will always be followed by a + * new fragment. + * + * Since: plugins-rs-0.14.0 + */ + Caps, } #[derive(Debug, Clone, Copy, PartialEq, Eq, glib::Enum, Default)] diff --git a/mux/fmp4/tests/tests.rs b/mux/fmp4/tests/tests.rs index 3e1bf76c9..e6c2555f3 100644 --- a/mux/fmp4/tests/tests.rs +++ b/mux/fmp4/tests/tests.rs @@ -2501,6 +2501,10 @@ fn test_caps_change_at_gop_boundary() { let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src")); + h.element() + .unwrap() + .set_property_from_str("header-update-mode", "caps"); + let caps = gst::Caps::builder("video/x-h264") .field("width", 1920i32) .field("height", 1080i32) @@ -2541,6 +2545,10 @@ fn test_language_change_at_gop_boundary() { let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src")); + h.element() + .unwrap() + .set_property_from_str("header-update-mode", "caps"); + let caps = gst::Caps::builder("video/x-h264") .field("width", 1920i32) .field("height", 1080i32) @@ -2603,6 +2611,10 @@ fn test_caps_change_at_gop_boundary_multi_stream() { let mut h1 = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src")); let mut h2 = gst_check::Harness::with_element(&h1.element().unwrap(), Some("sink_1"), None); + h1.element() + .unwrap() + .set_property_from_str("header-update-mode", "caps"); + let caps1 = gst::Caps::builder("video/x-h264") .field("width", 1920i32) .field("height", 1080i32) @@ -2770,6 +2782,10 @@ fn test_caps_change_at_gop_boundary_chunked_multi_stream() { let mut h1 = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src")); let mut h2 = gst_check::Harness::with_element(&h1.element().unwrap(), Some("sink_1"), None); + h1.element() + .unwrap() + .set_property_from_str("header-update-mode", "caps"); + let caps1 = gst::Caps::builder("video/x-h264") .field("width", 1920i32) .field("height", 1080i32) @@ -2918,6 +2934,10 @@ fn test_caps_change_at_gop_boundary_compatible() { let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src")); + h.element() + .unwrap() + .set_property_from_str("header-update-mode", "caps"); + let caps = gst::Caps::builder("video/x-h264") .field("width", 1280i32) .field("height", 720i32) @@ -2959,6 +2979,10 @@ fn test_caps_change_at_gop_boundary_not_allowed() { let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src")); + h.element() + .unwrap() + .set_property_from_str("header-update-mode", "caps"); + let caps = gst::Caps::builder("video/x-h264") .field("width", 1920i32) .field("height", 1080i32) @@ -3004,6 +3028,10 @@ fn test_caps_change_within_gop() { let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src")); + h.element() + .unwrap() + .set_property_from_str("header-update-mode", "caps"); + let caps = gst::Caps::builder("video/x-h264") .field("width", 1920i32) .field("height", 1080i32) @@ -3044,6 +3072,10 @@ fn test_caps_change_within_gop_start_without_key() { let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src")); + h.element() + .unwrap() + .set_property_from_str("header-update-mode", "caps"); + let caps = gst::Caps::builder("video/x-h264") .field("width", 1920i32) .field("height", 1080i32) @@ -3083,6 +3115,10 @@ fn test_caps_change_within_gop_chunked() { let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src")); + h.element() + .unwrap() + .set_property_from_str("header-update-mode", "caps"); + let caps = gst::Caps::builder("video/x-h264") .field("width", 1920i32) .field("height", 1080i32) @@ -3130,6 +3166,10 @@ fn test_caps_change_within_gop_no_key() { let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src")); + h.element() + .unwrap() + .set_property_from_str("header-update-mode", "caps"); + let caps = gst::Caps::builder("video/x-h264") .field("width", 1920i32) .field("height", 1080i32) @@ -3171,6 +3211,10 @@ fn test_caps_change_before_first_frame() { let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src")); + h.element() + .unwrap() + .set_property_from_str("header-update-mode", "caps"); + let caps = gst::Caps::builder("video/x-h264") .field("width", 1920i32) .field("height", 1080i32)