diff --git a/mux/mp4/src/mp4mux/boxes.rs b/mux/mp4/src/mp4mux/boxes.rs index d8aa9625b..71e489465 100644 --- a/mux/mp4/src/mp4mux/boxes.rs +++ b/mux/mp4/src/mp4mux/boxes.rs @@ -309,7 +309,7 @@ fn write_trak( if !references.is_empty() { write_box(v, b"tref", |v| write_tref(v, header, references))?; } - write_box(v, b"edts", |v| write_edts(v, stream))?; + write_box(v, b"edts", |v| write_edts(v, header, stream))?; Ok(()) } @@ -2288,32 +2288,69 @@ fn write_tref( Ok(()) } -fn write_edts(v: &mut Vec, stream: &super::Stream) -> Result<(), Error> { - write_full_box(v, b"elst", FULL_BOX_VERSION_1, 0, |v| write_elst(v, stream))?; +fn write_edts( + v: &mut Vec, + header: &super::Header, + stream: &super::Stream, +) -> Result<(), Error> { + write_full_box(v, b"elst", FULL_BOX_VERSION_1, 0, |v| { + write_elst(v, header, stream) + })?; Ok(()) } -fn write_elst(v: &mut Vec, stream: &super::Stream) -> Result<(), Error> { +fn write_elst( + v: &mut Vec, + header: &super::Header, + stream: &super::Stream, +) -> Result<(), Error> { + let movie_timescale = header_to_timescale(header); + // Entry count - v.extend((stream.elst_infos.len() as u32).to_be_bytes()); + let mut num_entries = 0u32; + let entry_count_position = v.len(); + // Entry count, rewritten in the end + v.extend(0u32.to_be_bytes()); for elst_info in &stream.elst_infos { - v.extend( - elst_info - .duration - .expect("Should have been set by `get_elst_infos`") - .to_be_bytes(), - ); + // Edit duration (in movie timescale) + let edit_duration = elst_info + .duration + .expect("Should have been set by `get_elst_infos`") + .nseconds() + .mul_div_round(movie_timescale as u64, gst::ClockTime::SECOND.nseconds()) + .unwrap(); - // Media time - v.extend(elst_info.start.to_be_bytes()); + if edit_duration == 0 { + continue; + } + v.extend(edit_duration.to_be_bytes()); + + // Media time (in media timescale) + let media_time = elst_info + .start + .map(|start| { + i64::try_from(start) + .unwrap() + .mul_div_round( + stream.timescale as i64, + gst::ClockTime::SECOND.nseconds() as i64, + ) + .unwrap() + }) + .unwrap_or(-1i64); + v.extend(media_time.to_be_bytes()); // Media rate v.extend(1u16.to_be_bytes()); v.extend(0u16.to_be_bytes()); + num_entries += 1; } + // Rewrite entry count + v[entry_count_position..][..4].copy_from_slice(&num_entries.to_be_bytes()); + Ok(()) } diff --git a/mux/mp4/src/mp4mux/imp.rs b/mux/mp4/src/mp4mux/imp.rs index 1599e27d8..419a3c307 100644 --- a/mux/mp4/src/mp4mux/imp.rs +++ b/mux/mp4/src/mp4mux/imp.rs @@ -6,7 +6,7 @@ // // SPDX-License-Identifier: MPL-2.0 -use anyhow::{anyhow, Context}; +use anyhow::{bail, Context}; use gst::glib; use gst::prelude::*; use gst::subclass::prelude::*; @@ -158,7 +158,6 @@ impl Stream { min_earliest_pts: gst::ClockTime, ) -> Result, anyhow::Error> { let mut elst_infos = self.elst_infos.clone(); - let timescale = self.timescale(); let earliest_pts = self .earliest_pts .expect("Streams without earliest_pts should have been skipped"); @@ -169,57 +168,47 @@ impl Stream { // If no elst info were set, use the whole track if self.elst_infos.is_empty() { let start = if let Some(start_dts) = self.start_dts { - ((gst::Signed::Positive(earliest_pts) - start_dts) - .nseconds() - .positive() - .unwrap_or(0) - .mul_div_round(timescale as u64, gst::ClockTime::SECOND.nseconds()) - .context("too big track duration")?) as i64 + gst::Signed::Positive(earliest_pts) - start_dts } else { - 0i64 + gst::Signed::Positive(gst::ClockTime::ZERO) }; elst_infos.push(super::ElstInfo { - start, - duration: Some( - (end_pts - earliest_pts) - .nseconds() - .mul_div_round(timescale as u64, gst::ClockTime::SECOND.nseconds()) - .context("too big track duration")?, - ), + start: Some(start), + duration: Some(end_pts - earliest_pts), }); } // Add a gap at the beginning if needed - if min_earliest_pts != earliest_pts { - let gap_duration = (earliest_pts - min_earliest_pts) - .nseconds() - .mul_div_round(timescale as u64, gst::ClockTime::SECOND.nseconds()) - .context("too big gap")?; + if earliest_pts > min_earliest_pts { + let gap_duration = earliest_pts - min_earliest_pts; - if gap_duration > 0 { - elst_infos.insert( - 0, - super::ElstInfo { - start: -1, - duration: Some(gap_duration), - }, - ); - } + elst_infos.insert( + 0, + super::ElstInfo { + start: None, + duration: Some(gap_duration), + }, + ); } - let mut iter = elst_infos.iter_mut().peekable(); + let mut iter = elst_infos + .iter_mut() + .filter(|e| e.start.is_some()) + .peekable(); while let Some(&mut ref mut elst_info) = iter.next() { - if elst_info.duration.unwrap_or(0u64) == 0u64 { + if elst_info + .duration + .map_or(true, |duration| duration.is_zero()) + { elst_info.duration = if let Some(next) = iter.peek_mut() { - Some((next.start - elst_info.start) as u64) - } else { Some( - (end_pts - earliest_pts) - .nseconds() - .mul_div_round(timescale as u64, gst::ClockTime::SECOND.nseconds()) - .context("too big track duration")?, + (next.start.unwrap() - elst_info.start.unwrap()) + .positive() + .unwrap_or(gst::ClockTime::ZERO), ) + } else { + Some(end_pts - earliest_pts) } } } @@ -355,53 +344,51 @@ impl MP4Mux { .get::("rate") .unwrap_or_else(|_| stream.timescale() as i32); - let gstclocktime_to_samples = move |v: gst::ClockTime| { - v.nseconds() - .mul_div_round(timescale as u64, gst::ClockTime::SECOND.nseconds()) - .context("Invalid start in the AudioClipMeta") + let samples_to_gstclocktime = move |v: u64| { + let nseconds = v + .mul_div_round(gst::ClockTime::SECOND.nseconds(), timescale as u64) + .context("Invalid start in the AudioClipMeta")?; + Ok::<_, anyhow::Error>(gst::ClockTime::from_nseconds(nseconds)) }; - let generic_to_samples = move |t| -> Result, anyhow::Error> { + let generic_to_gstclocktime = move |t| -> Result, anyhow::Error> { if let gst::GenericFormattedValue::Default(Some(v)) = t { let v = u64::from(v); - Ok(Some(v).filter(|x| x != &0u64)) + let v = samples_to_gstclocktime(v)?; + Ok(Some(v).filter(|x| !x.is_zero())) } else if let gst::GenericFormattedValue::Time(Some(v)) = t { - Ok(Some(gstclocktime_to_samples(v)?)) + Ok(Some(v).filter(|x| !x.is_zero())) } else { Ok(None) } }; - let start = generic_to_samples(cmeta.start())?; - let end = generic_to_samples(cmeta.end())?; + let start = generic_to_gstclocktime(cmeta.start())?; + let end = generic_to_gstclocktime(cmeta.end())?; if end.is_none() && start.is_none() { - return Err(anyhow!( - "No start or end time in `default` format in the AudioClipingMeta" - )); + bail!("No start or end time in `default` format in the AudioClipingMeta"); } - let start = if let Some(start) = generic_to_samples(cmeta.start())? { - start + gstclocktime_to_samples(buffer.pts)? + let start = if let Some(start) = generic_to_gstclocktime(cmeta.start())? { + start + buffer.pts } else { - 0 + gst::ClockTime::ZERO }; - let duration = if let Some(e) = end { + let duration = if let Some(end) = end { Some( - gstclocktime_to_samples(buffer.pts)? - + gstclocktime_to_samples( - buffer - .duration - .context("No duration on buffer, we can't add edit list")?, - )? - - e, + buffer.pts + + buffer + .duration + .context("No duration on buffer, we can't add edit list")? + - end, ) } else { None }; stream.elst_infos.push(super::ElstInfo { - start: start as i64, + start: Some(start.into()), duration, }); diff --git a/mux/mp4/src/mp4mux/mod.rs b/mux/mp4/src/mp4mux/mod.rs index 9c67d5cb1..feeea425d 100644 --- a/mux/mp4/src/mp4mux/mod.rs +++ b/mux/mp4/src/mp4mux/mod.rs @@ -174,8 +174,8 @@ pub(crate) struct Chunk { #[derive(Debug, Clone)] pub(crate) struct ElstInfo { - start: i64, - duration: Option, + start: Option>, + duration: Option, } #[derive(Debug)]