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: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/2193>
This commit is contained in:
Sebastian Dröge 2025-04-09 17:14:12 +03:00 committed by GStreamer Marge Bot
parent f86e7e6c33
commit c5e4181613
4 changed files with 108 additions and 9 deletions

View file

@ -3598,6 +3598,11 @@
"desc": "Update", "desc": "Update",
"name": "update", "name": "update",
"value": "2" "value": "2"
},
{
"desc": "Caps",
"name": "caps",
"value": "3"
} }
] ]
}, },

View file

@ -921,14 +921,14 @@ impl FMP4Mux {
} }
// Caps/tag changes are allowed only in case that the // 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 // CAUTION: This function logs an error if operation is not
// allowed so it should be evaluated only in case the caps/tags // allowed so it should be evaluated only in case the caps/tags
// would change otherwise (e. g. right-most operand in boolean // would change otherwise (e. g. right-most operand in boolean
// expressions). // expressions).
fn header_update_allowed(&self, reason: &str) -> bool { 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!( gst::debug!(
CAT, CAT,
imp = self, imp = self,
@ -3643,17 +3643,30 @@ impl FMP4Mux {
let class = aggregator.class(); let class = aggregator.class();
let variant = class.as_ref().variant; 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); return Ok(None);
} }
assert!(!at_eos || state.streams.iter().all(|s| s.queued_gops.is_empty())); assert!(!at_eos || state.streams.iter().all(|s| s.queued_gops.is_empty()));
let duration = state let duration = if at_eos
&& [
super::HeaderUpdateMode::Update,
super::HeaderUpdateMode::Rewrite,
]
.contains(&settings.header_update_mode)
{
state
.end_pts .end_pts
.opt_checked_sub(state.earliest_pts) .opt_checked_sub(state.earliest_pts)
.ok() .ok()
.flatten(); .flatten()
} else {
None
};
let streams = state let streams = state
.streams .streams
@ -3750,7 +3763,7 @@ impl FMP4Mux {
match updated_header { match updated_header {
Ok(Some((buffer_list, caps))) => { Ok(Some((buffer_list, caps))) => {
match settings.header_update_mode { match settings.header_update_mode {
super::HeaderUpdateMode::None => unreachable!(), super::HeaderUpdateMode::None | super::HeaderUpdateMode::Caps => unreachable!(),
super::HeaderUpdateMode::Rewrite => { super::HeaderUpdateMode::Rewrite => {
let mut q = gst::query::Seeking::new(gst::Format::Bytes); let mut q = gst::query::Seeking::new(gst::Format::Bytes);
if self.obj().src_pad().peer_query(&mut q) && q.result().0 { if self.obj().src_pad().peer_query(&mut q) && q.result().0 {

View file

@ -326,14 +326,51 @@ pub(crate) struct FragmentOffset {
offset: u64, offset: u64,
} }
/**
* GstFMP4MuxHeaderUpdateMode:
*
* How and when updating of the header (`moov`, initialization segment) is allowed.
*/
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, glib::Enum)] #[derive(Debug, Clone, Copy, PartialEq, Eq, glib::Enum)]
#[repr(i32)] #[repr(i32)]
#[enum_type(name = "GstFMP4MuxHeaderUpdateMode")] #[enum_type(name = "GstFMP4MuxHeaderUpdateMode")]
pub(crate) enum HeaderUpdateMode { 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, 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, 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, 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, glib::Enum, Default)]

View file

@ -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")); 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") let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32) .field("width", 1920i32)
.field("height", 1080i32) .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")); 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") let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32) .field("width", 1920i32)
.field("height", 1080i32) .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 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); 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") let caps1 = gst::Caps::builder("video/x-h264")
.field("width", 1920i32) .field("width", 1920i32)
.field("height", 1080i32) .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 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); 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") let caps1 = gst::Caps::builder("video/x-h264")
.field("width", 1920i32) .field("width", 1920i32)
.field("height", 1080i32) .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")); 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") let caps = gst::Caps::builder("video/x-h264")
.field("width", 1280i32) .field("width", 1280i32)
.field("height", 720i32) .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")); 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") let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32) .field("width", 1920i32)
.field("height", 1080i32) .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")); 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") let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32) .field("width", 1920i32)
.field("height", 1080i32) .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")); 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") let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32) .field("width", 1920i32)
.field("height", 1080i32) .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")); 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") let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32) .field("width", 1920i32)
.field("height", 1080i32) .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")); 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") let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32) .field("width", 1920i32)
.field("height", 1080i32) .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")); 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") let caps = gst::Caps::builder("video/x-h264")
.field("width", 1920i32) .field("width", 1920i32)
.field("height", 1080i32) .field("height", 1080i32)