From 06f1a7e81815977d2be45ec863d08de754dc9b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 14 Apr 2025 16:08:44 +0300 Subject: [PATCH] mp4mux: Write btrt box from the bitrate tags if existing Part-of: --- mux/mp4/src/mp4mux/boxes.rs | 51 ++++++++++++++--- mux/mp4/src/mp4mux/imp.rs | 106 ++++++++++++++++++++++++++++++++++-- mux/mp4/src/mp4mux/mod.rs | 4 ++ 3 files changed, 150 insertions(+), 11 deletions(-) diff --git a/mux/mp4/src/mp4mux/boxes.rs b/mux/mp4/src/mp4mux/boxes.rs index 004310dc9..bb01a0827 100644 --- a/mux/mp4/src/mp4mux/boxes.rs +++ b/mux/mp4/src/mp4mux/boxes.rs @@ -1082,7 +1082,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(()) })?; @@ -1616,7 +1632,7 @@ fn av1_tier(tier: Option<&str>) -> u8 { fn write_audio_sample_entry( v: &mut Vec, - _header: &super::Header, + header: &super::Header, stream: &super::Stream, ) -> Result<(), Error> { let s = stream.caps.structure(0).unwrap(); @@ -1683,7 +1699,7 @@ fn write_audio_sample_entry( if map.len() < 2 { bail!("too small codec_data"); } - write_esds_aac(v, &map)?; + write_esds_aac(v, header, stream, &map)?; } "audio/x-opus" => { write_dops(v, &stream.caps)?; @@ -1713,7 +1729,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 @@ -1723,7 +1755,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, + _header: &super::Header, + stream: &super::Stream, + codec_data: &[u8], +) -> Result<(), Error> { let calculate_len = |mut len| { if len > 260144641 { bail!("too big descriptor length"); @@ -1801,10 +1838,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/mp4/src/mp4mux/imp.rs b/mux/mp4/src/mp4mux/imp.rs index a77fdf3b5..b89b08d7a 100644 --- a/mux/mp4/src/mp4mux/imp.rs +++ b/mux/mp4/src/mp4mux/imp.rs @@ -155,6 +155,9 @@ struct Stream { /// Orientation from tags, stream orientation takes precedence over global orientation global_orientation: &'static TransformMatrix, stream_orientation: Option<&'static TransformMatrix>, + + avg_bitrate: Option, + max_bitrate: Option, } impl Stream { @@ -1152,6 +1155,8 @@ impl MP4Mux { 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(); @@ -1159,7 +1164,7 @@ impl MP4Mux { let lang = lang.get(); gst::trace!( CAT, - imp = self, + obj = pad, "Received language code from tags: {:?}", lang ); @@ -1179,7 +1184,7 @@ impl MP4Mux { } else if let Some(orientation) = tag.get::() { gst::trace!( CAT, - imp = self, + obj = pad, "Received image orientation from tags: {:?}", orientation.get(), ); @@ -1189,6 +1194,41 @@ impl MP4Mux { } 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, + obj = pad, + "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, obj = pad, "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) @@ -1289,6 +1329,8 @@ impl MP4Mux { language_code, global_orientation, stream_orientation, + max_bitrate, + avg_bitrate, }); } @@ -1518,7 +1560,7 @@ impl AggregatorImpl for MP4Mux { let lang = tag_value.get(); gst::trace!( CAT, - imp = self, + obj = aggregator_pad, "Received language code from tags: {:?}", lang ); @@ -1530,7 +1572,7 @@ impl AggregatorImpl for MP4Mux { if tag.scope() == gst::TagScope::Global { gst::info!( CAT, - imp = self, + obj = aggregator_pad, "Language tags scoped 'global' are considered stream tags", ); } @@ -1563,6 +1605,60 @@ impl AggregatorImpl for MP4Mux { break; } } + } else if let Some(bitrate) = tag + .get::() + .filter(|br| br.get() > 0 && br.get() < u32::MAX) + { + let bitrate = bitrate.get(); + gst::trace!( + CAT, + obj = aggregator_pad, + "Received maximum bitrate from tags: {:?}", + bitrate + ); + + if tag.scope() == gst::TagScope::Global { + gst::info!( + CAT, + obj = aggregator_pad, + "Bitrate tags scoped 'global' are considered stream tags", + ); + } + + let mut state = self.state.lock().unwrap(); + for stream in &mut state.streams { + if &stream.sinkpad == aggregator_pad { + stream.max_bitrate = Some(bitrate); + break; + } + } + } else if let Some(bitrate) = tag + .get::() + .filter(|br| br.get() > 0 && br.get() < u32::MAX) + { + let bitrate = bitrate.get(); + gst::trace!( + CAT, + obj = aggregator_pad, + "Received bitrate from tags: {:?}", + bitrate + ); + + if tag.scope() == gst::TagScope::Global { + gst::info!( + CAT, + obj = aggregator_pad, + "Bitrate tags scoped 'global' are considered stream tags", + ); + } + + let mut state = self.state.lock().unwrap(); + for stream in &mut state.streams { + if &stream.sinkpad == aggregator_pad { + stream.avg_bitrate = Some(bitrate); + break; + } + } } self.parent_sink_event(aggregator_pad, event) @@ -1805,6 +1901,8 @@ impl AggregatorImpl for MP4Mux { extra_header_data: stream.extra_header_data.clone(), language_code: stream.language_code, orientation: stream.orientation(), + max_bitrate: stream.max_bitrate, + avg_bitrate: stream.avg_bitrate, chunks: stream.chunks, }); } diff --git a/mux/mp4/src/mp4mux/mod.rs b/mux/mp4/src/mp4mux/mod.rs index 6981cb67c..b4d3ccbc6 100644 --- a/mux/mp4/src/mp4mux/mod.rs +++ b/mux/mp4/src/mp4mux/mod.rs @@ -231,6 +231,10 @@ pub(crate) struct Stream { // Language code from tags language_code: Option<[u8; 3]>, + /// Bitrate tags + avg_bitrate: Option, + max_bitrate: Option, + /// Orientation from tags orientation: &'static TransformMatrix,