mp4mux: Write btrt box from the bitrate tags if existing

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/2193>
This commit is contained in:
Sebastian Dröge 2025-04-14 16:08:44 +03:00 committed by GStreamer Marge Bot
parent 8b92f8c5c0
commit 06f1a7e818
3 changed files with 150 additions and 11 deletions

View file

@ -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(()) Ok(())
})?; })?;
@ -1616,7 +1632,7 @@ fn av1_tier(tier: Option<&str>) -> u8 {
fn write_audio_sample_entry( fn write_audio_sample_entry(
v: &mut Vec<u8>, v: &mut Vec<u8>,
_header: &super::Header, header: &super::Header,
stream: &super::Stream, stream: &super::Stream,
) -> Result<(), Error> { ) -> Result<(), Error> {
let s = stream.caps.structure(0).unwrap(); let s = stream.caps.structure(0).unwrap();
@ -1683,7 +1699,7 @@ fn write_audio_sample_entry(
if map.len() < 2 { if map.len() < 2 {
bail!("too small codec_data"); bail!("too small codec_data");
} }
write_esds_aac(v, &map)?; write_esds_aac(v, header, stream, &map)?;
} }
"audio/x-opus" => { "audio/x-opus" => {
write_dops(v, &stream.caps)?; 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 // TODO: chnl box for channel ordering? probably not needed for AAC
@ -1723,7 +1755,12 @@ fn write_audio_sample_entry(
Ok(()) Ok(())
} }
fn write_esds_aac(v: &mut Vec<u8>, codec_data: &[u8]) -> Result<(), Error> { fn write_esds_aac(
v: &mut Vec<u8>,
_header: &super::Header,
stream: &super::Stream,
codec_data: &[u8],
) -> Result<(), Error> {
let calculate_len = |mut len| { let calculate_len = |mut len| {
if len > 260144641 { if len > 260144641 {
bail!("too big descriptor length"); bail!("too big descriptor length");
@ -1801,10 +1838,10 @@ fn write_esds_aac(v: &mut Vec<u8>, codec_data: &[u8]) -> Result<(), Error> {
v.extend([0u8; 3]); v.extend([0u8; 3]);
// Max bitrate // Max bitrate
v.extend(0u32.to_be_bytes()); v.extend(stream.max_bitrate.unwrap_or(0u32).to_be_bytes());
// Avg bitrate // Avg bitrate
v.extend(0u32.to_be_bytes()); v.extend(stream.avg_bitrate.unwrap_or(0u32).to_be_bytes());
// Decoder specific info // Decoder specific info
v.push(0x05); v.push(0x05);

View file

@ -155,6 +155,9 @@ struct Stream {
/// Orientation from tags, stream orientation takes precedence over global orientation /// Orientation from tags, stream orientation takes precedence over global orientation
global_orientation: &'static TransformMatrix, global_orientation: &'static TransformMatrix,
stream_orientation: Option<&'static TransformMatrix>, stream_orientation: Option<&'static TransformMatrix>,
avg_bitrate: Option<u32>,
max_bitrate: Option<u32>,
} }
impl Stream { impl Stream {
@ -1152,6 +1155,8 @@ impl MP4Mux {
let mut stream_orientation = Default::default(); let mut stream_orientation = Default::default();
let mut global_orientation = Default::default(); let mut global_orientation = Default::default();
let mut language_code = None; let mut language_code = None;
let mut avg_bitrate = None;
let mut max_bitrate = None;
pad.sticky_events_foreach(|ev| { pad.sticky_events_foreach(|ev| {
if let gst::EventView::Tag(ev) = ev.view() { if let gst::EventView::Tag(ev) = ev.view() {
let tag = ev.tag(); let tag = ev.tag();
@ -1159,7 +1164,7 @@ impl MP4Mux {
let lang = lang.get(); let lang = lang.get();
gst::trace!( gst::trace!(
CAT, CAT,
imp = self, obj = pad,
"Received language code from tags: {:?}", "Received language code from tags: {:?}",
lang lang
); );
@ -1179,7 +1184,7 @@ impl MP4Mux {
} else if let Some(orientation) = tag.get::<gst::tags::ImageOrientation>() { } else if let Some(orientation) = tag.get::<gst::tags::ImageOrientation>() {
gst::trace!( gst::trace!(
CAT, CAT,
imp = self, obj = pad,
"Received image orientation from tags: {:?}", "Received image orientation from tags: {:?}",
orientation.get(), orientation.get(),
); );
@ -1189,6 +1194,41 @@ impl MP4Mux {
} else { } else {
stream_orientation = Some(TransformMatrix::from_tag(self, ev)); stream_orientation = Some(TransformMatrix::from_tag(self, ev));
} }
} else if let Some(bitrate) = tag
.get::<gst::tags::MaximumBitrate>()
.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::<gst::tags::Bitrate>()
.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) std::ops::ControlFlow::Continue(gst::EventForeachAction::Keep)
@ -1289,6 +1329,8 @@ impl MP4Mux {
language_code, language_code,
global_orientation, global_orientation,
stream_orientation, stream_orientation,
max_bitrate,
avg_bitrate,
}); });
} }
@ -1518,7 +1560,7 @@ impl AggregatorImpl for MP4Mux {
let lang = tag_value.get(); let lang = tag_value.get();
gst::trace!( gst::trace!(
CAT, CAT,
imp = self, obj = aggregator_pad,
"Received language code from tags: {:?}", "Received language code from tags: {:?}",
lang lang
); );
@ -1530,7 +1572,7 @@ impl AggregatorImpl for MP4Mux {
if tag.scope() == gst::TagScope::Global { if tag.scope() == gst::TagScope::Global {
gst::info!( gst::info!(
CAT, CAT,
imp = self, obj = aggregator_pad,
"Language tags scoped 'global' are considered stream tags", "Language tags scoped 'global' are considered stream tags",
); );
} }
@ -1563,6 +1605,60 @@ impl AggregatorImpl for MP4Mux {
break; break;
} }
} }
} else if let Some(bitrate) = tag
.get::<gst::tags::MaximumBitrate>()
.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::<gst::tags::Bitrate>()
.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) self.parent_sink_event(aggregator_pad, event)
@ -1805,6 +1901,8 @@ impl AggregatorImpl for MP4Mux {
extra_header_data: stream.extra_header_data.clone(), extra_header_data: stream.extra_header_data.clone(),
language_code: stream.language_code, language_code: stream.language_code,
orientation: stream.orientation(), orientation: stream.orientation(),
max_bitrate: stream.max_bitrate,
avg_bitrate: stream.avg_bitrate,
chunks: stream.chunks, chunks: stream.chunks,
}); });
} }

View file

@ -231,6 +231,10 @@ pub(crate) struct Stream {
// Language code from tags // Language code from tags
language_code: Option<[u8; 3]>, language_code: Option<[u8; 3]>,
/// Bitrate tags
avg_bitrate: Option<u32>,
max_bitrate: Option<u32>,
/// Orientation from tags /// Orientation from tags
orientation: &'static TransformMatrix, orientation: &'static TransformMatrix,