From f86e7e6c3328d257024acd974ae4c859e27c40e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 9 Apr 2025 15:16:52 +0300 Subject: [PATCH] fmp4mux: Write btrt box from the bitrate tags if existing Only take the tags into account that arrive before the muxer created its streams, otherwise we would have to re-create the header every time the bitrate changes. Part-of: --- mux/fmp4/src/fmp4mux/boxes.rs | 51 +++++++++++++++++++++++---- mux/fmp4/src/fmp4mux/imp.rs | 65 +++++++++++++++++++++++++++++++++-- mux/fmp4/src/fmp4mux/mod.rs | 2 ++ 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/mux/fmp4/src/fmp4mux/boxes.rs b/mux/fmp4/src/fmp4mux/boxes.rs index 19c4fd1a1..2694c700e 100644 --- a/mux/fmp4/src/fmp4mux/boxes.rs +++ b/mux/fmp4/src/fmp4mux/boxes.rs @@ -1300,7 +1300,23 @@ fn write_visual_sample_entry( })?; } - // TODO: write btrt bitrate box based on tags + if stream.avg_bitrate.is_some() || stream.max_bitrate.is_some() { + write_box(v, b"btrt", |v| { + // Buffer size DB + // TODO + v.extend(0u32.to_be_bytes()); + + // Maximum bitrate + let max_bitrate = stream.max_bitrate.or(stream.avg_bitrate).unwrap(); + v.extend(max_bitrate.to_be_bytes()); + + // Average bitrate + let avg_bitrate = stream.avg_bitrate.or(stream.max_bitrate).unwrap(); + v.extend(avg_bitrate.to_be_bytes()); + + Ok(()) + })?; + } Ok(()) })?; @@ -1348,7 +1364,7 @@ fn av1_tier(tier: Option<&str>) -> u8 { fn write_audio_sample_entry( v: &mut Vec, - _cfg: &super::HeaderConfiguration, + cfg: &super::HeaderConfiguration, stream: &super::HeaderStream, ) -> Result<(), Error> { let s = stream.caps.structure(0).unwrap(); @@ -1415,7 +1431,7 @@ fn write_audio_sample_entry( if map.len() < 2 { bail!("too small codec_data"); } - write_esds_aac(v, &map)?; + write_esds_aac(v, cfg, stream, &map)?; } "audio/x-opus" => { write_dops(v, &stream.caps)?; @@ -1445,7 +1461,23 @@ fn write_audio_sample_entry( )?; } - // TODO: write btrt bitrate box based on tags + if stream.avg_bitrate.is_some() || stream.max_bitrate.is_some() { + write_box(v, b"btrt", |v| { + // Buffer size DB + // TODO + v.extend(0u32.to_be_bytes()); + + // Maximum bitrate + let max_bitrate = stream.max_bitrate.or(stream.avg_bitrate).unwrap(); + v.extend(max_bitrate.to_be_bytes()); + + // Average bitrate + let avg_bitrate = stream.avg_bitrate.or(stream.max_bitrate).unwrap(); + v.extend(avg_bitrate.to_be_bytes()); + + Ok(()) + })?; + } // TODO: chnl box for channel ordering? probably not needed for AAC @@ -1455,7 +1487,12 @@ fn write_audio_sample_entry( Ok(()) } -fn write_esds_aac(v: &mut Vec, codec_data: &[u8]) -> Result<(), Error> { +fn write_esds_aac( + v: &mut Vec, + _cfg: &super::HeaderConfiguration, + stream: &super::HeaderStream, + codec_data: &[u8], +) -> Result<(), Error> { let calculate_len = |mut len| { if len > 260144641 { bail!("too big descriptor length"); @@ -1533,10 +1570,10 @@ fn write_esds_aac(v: &mut Vec, codec_data: &[u8]) -> Result<(), Error> { v.extend([0u8; 3]); // Max bitrate - v.extend(0u32.to_be_bytes()); + v.extend(stream.max_bitrate.unwrap_or(0u32).to_be_bytes()); // Avg bitrate - v.extend(0u32.to_be_bytes()); + v.extend(stream.avg_bitrate.unwrap_or(0u32).to_be_bytes()); // Decoder specific info v.push(0x05); diff --git a/mux/fmp4/src/fmp4mux/imp.rs b/mux/fmp4/src/fmp4mux/imp.rs index ca9ae1ffb..c5a55e4d3 100644 --- a/mux/fmp4/src/fmp4mux/imp.rs +++ b/mux/fmp4/src/fmp4mux/imp.rs @@ -285,6 +285,9 @@ struct Stream { /// Orientation from tags, stream orientation takes precedence over global orientation global_orientation: &'static TransformMatrix, stream_orientation: Option<&'static TransformMatrix>, + /// Bitrate tags + avg_bitrate: Option, + max_bitrate: Option, /// Edit list entries for this stream. elst_infos: Vec, @@ -3421,10 +3424,20 @@ impl FMP4Mux { let mut stream_orientation = Default::default(); let mut global_orientation = Default::default(); let mut language_code = None; + let mut avg_bitrate = None; + let mut max_bitrate = None; pad.sticky_events_foreach(|ev| { if let gst::EventView::Tag(ev) = ev.view() { let tag = ev.tag(); - if let Some(l) = tag.get::() { + if let Some(lang) = tag.get::() { + let lang = lang.get(); + gst::trace!( + CAT, + imp = self, + "Received language code from tags: {:?}", + lang + ); + // There is no header field for global // language code, maybe because it does not // really make sense, global language tags are @@ -3436,13 +3449,55 @@ impl FMP4Mux { "Language tags scoped 'global' are considered stream tags", ); } - language_code = Stream::parse_language_code(l.get()); - } else if tag.get::().is_some() { + language_code = Stream::parse_language_code(lang); + } else if let Some(orientation) = tag.get::() { + gst::trace!( + CAT, + imp = self, + "Received image orientation from tags: {:?}", + orientation.get(), + ); + if tag.scope() == gst::TagScope::Global { global_orientation = TransformMatrix::from_tag(self, ev); } else { stream_orientation = Some(TransformMatrix::from_tag(self, ev)); } + } else if let Some(bitrate) = tag + .get::() + .filter(|br| br.get() > 0 && br.get() < u32::MAX) + { + let bitrate = bitrate.get(); + gst::trace!( + CAT, + imp = self, + "Received maximum bitrate from tags: {:?}", + bitrate + ); + + if tag.scope() == gst::TagScope::Global { + gst::info!( + CAT, + obj = pad, + "Bitrate tags scoped 'global' are considered stream tags", + ); + } + max_bitrate = Some(bitrate); + } else if let Some(bitrate) = tag + .get::() + .filter(|br| br.get() > 0 && br.get() < u32::MAX) + { + let bitrate = bitrate.get(); + gst::trace!(CAT, imp = self, "Received bitrate from tags: {:?}", bitrate); + + if tag.scope() == gst::TagScope::Global { + gst::info!( + CAT, + obj = pad, + "Bitrate tags scoped 'global' are considered stream tags", + ); + } + avg_bitrate = Some(bitrate); } } std::ops::ControlFlow::Continue(gst::EventForeachAction::Keep) @@ -3536,6 +3591,8 @@ impl FMP4Mux { language_code, global_orientation, stream_orientation, + avg_bitrate, + max_bitrate, elst_infos: Vec::new(), pending_split_now: Vec::new(), }); @@ -3610,6 +3667,8 @@ impl FMP4Mux { extra_header_data: s.extra_header_data.clone(), language_code: s.language_code, orientation: s.orientation(), + max_bitrate: s.max_bitrate, + avg_bitrate: s.avg_bitrate, elst_infos: s.get_elst_infos().unwrap_or_else(|e| { gst::error!(CAT, "Could not prepare edit lists: {e:?}"); diff --git a/mux/fmp4/src/fmp4mux/mod.rs b/mux/fmp4/src/fmp4mux/mod.rs index 45d7c425d..1375dbb06 100644 --- a/mux/fmp4/src/fmp4mux/mod.rs +++ b/mux/fmp4/src/fmp4mux/mod.rs @@ -218,6 +218,8 @@ pub(crate) struct HeaderStream { // Tags meta for audio language and video orientation language_code: Option<[u8; 3]>, orientation: &'static TransformMatrix, + avg_bitrate: Option, + max_bitrate: Option, /// Edit list clipping information elst_infos: Vec,