mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-09-03 02:03:48 +00:00
fmp4mux: Use correct timescales for edit lists
The duration is using the movie timescale while the media time is using the media / track timescale. Previously both were using the track timescale. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/2167>
This commit is contained in:
parent
8a70f1617a
commit
3a47abd0fa
3 changed files with 110 additions and 76 deletions
|
@ -543,32 +543,70 @@ struct TrackReference {
|
||||||
track_ids: Vec<u32>,
|
track_ids: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_edts(v: &mut Vec<u8>, stream: &super::HeaderStream) -> Result<(), Error> {
|
fn write_edts(
|
||||||
write_full_box(v, b"elst", FULL_BOX_VERSION_1, 0, |v| write_elst(v, stream))?;
|
v: &mut Vec<u8>,
|
||||||
|
cfg: &super::HeaderConfiguration,
|
||||||
|
stream: &super::HeaderStream,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
write_full_box(v, b"elst", FULL_BOX_VERSION_1, 0, |v| {
|
||||||
|
write_elst(v, cfg, stream)
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_elst(v: &mut Vec<u8>, stream: &super::HeaderStream) -> Result<(), Error> {
|
fn write_elst(
|
||||||
|
v: &mut Vec<u8>,
|
||||||
|
cfg: &super::HeaderConfiguration,
|
||||||
|
stream: &super::HeaderStream,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let movie_timescale = header_configuration_to_timescale(cfg);
|
||||||
|
let track_timescale = header_stream_to_timescale(stream);
|
||||||
|
|
||||||
// Entry count
|
// 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 {
|
for elst_info in &stream.elst_infos {
|
||||||
v.extend(
|
// Edit duration (in movie timescale)
|
||||||
elst_info
|
let edit_duration = elst_info
|
||||||
.duration
|
.duration
|
||||||
.expect("Should have been set by `get_elst_infos`")
|
.expect("Should have been set by `get_elst_infos`")
|
||||||
.to_be_bytes(),
|
.nseconds()
|
||||||
);
|
.mul_div_round(movie_timescale as u64, gst::ClockTime::SECOND.nseconds())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Media time
|
if edit_duration == 0 {
|
||||||
v.extend(elst_info.start.to_be_bytes());
|
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(
|
||||||
|
track_timescale as i64,
|
||||||
|
gst::ClockTime::SECOND.nseconds() as i64,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.unwrap_or(-1i64);
|
||||||
|
v.extend(media_time.to_be_bytes());
|
||||||
|
|
||||||
// Media rate
|
// Media rate
|
||||||
v.extend(1u16.to_be_bytes());
|
v.extend(1u16.to_be_bytes());
|
||||||
v.extend(0u16.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -591,7 +629,7 @@ fn write_trak(
|
||||||
// TODO: write edts optionally for negative DTS instead of offsetting the DTS
|
// TODO: write edts optionally for negative DTS instead of offsetting the DTS
|
||||||
write_box(v, b"mdia", |v| write_mdia(v, cfg, stream, creation_time))?;
|
write_box(v, b"mdia", |v| write_mdia(v, cfg, stream, creation_time))?;
|
||||||
if !stream.elst_infos.is_empty() && cfg.write_edts {
|
if !stream.elst_infos.is_empty() && cfg.write_edts {
|
||||||
if let Err(e) = write_edts(v, stream) {
|
if let Err(e) = write_edts(v, cfg, stream) {
|
||||||
gst::warning!(CAT, "Failed to write edts: {e}");
|
gst::warning!(CAT, "Failed to write edts: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use gst::subclass::prelude::*;
|
||||||
use gst_base::prelude::*;
|
use gst_base::prelude::*;
|
||||||
use gst_base::subclass::prelude::*;
|
use gst_base::subclass::prelude::*;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::{bail, Context};
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
@ -268,29 +268,28 @@ struct Stream {
|
||||||
impl Stream {
|
impl Stream {
|
||||||
fn get_elst_infos(&self) -> Result<Vec<super::ElstInfo>, anyhow::Error> {
|
fn get_elst_infos(&self) -> Result<Vec<super::ElstInfo>, anyhow::Error> {
|
||||||
let mut elst_infos = self.elst_infos.clone();
|
let mut elst_infos = self.elst_infos.clone();
|
||||||
let timescale = self.timescale();
|
|
||||||
let earliest_pts = self.earliest_pts.unwrap_or(gst::ClockTime::ZERO);
|
let earliest_pts = self.earliest_pts.unwrap_or(gst::ClockTime::ZERO);
|
||||||
let end_pts = self
|
let end_pts = self
|
||||||
.end_pts
|
.end_pts
|
||||||
.unwrap_or(gst::ClockTime::from_nseconds(u64::MAX - 1));
|
.unwrap_or(gst::ClockTime::from_nseconds(u64::MAX - 1));
|
||||||
|
|
||||||
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() {
|
while let Some(&mut ref mut elst_info) = iter.next() {
|
||||||
if elst_info.duration.unwrap_or(0) == 0 {
|
if elst_info
|
||||||
|
.duration
|
||||||
|
.map_or(true, |duration| duration.is_zero())
|
||||||
|
{
|
||||||
elst_info.duration = if let Some(next) = iter.peek_mut() {
|
elst_info.duration = if let Some(next) = iter.peek_mut() {
|
||||||
let duration = next.start - elst_info.start;
|
|
||||||
if duration < 0 {
|
|
||||||
anyhow::bail!("Invalid edit list entry, negative duration");
|
|
||||||
}
|
|
||||||
Some(duration as u64)
|
|
||||||
} else {
|
|
||||||
Some(
|
Some(
|
||||||
(end_pts.checked_sub(earliest_pts))
|
(next.start.unwrap() - elst_info.start.unwrap())
|
||||||
.expect("stream end < stream start?")
|
.positive()
|
||||||
.nseconds()
|
.unwrap_or(gst::ClockTime::ZERO),
|
||||||
.mul_div_round(timescale as u64, gst::ClockTime::SECOND.nseconds())
|
|
||||||
.context("too big track duration")?,
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
Some(end_pts - earliest_pts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -447,60 +446,57 @@ impl FMP4Mux {
|
||||||
buffer: &PreQueuedBuffer,
|
buffer: &PreQueuedBuffer,
|
||||||
stream: &mut Stream,
|
stream: &mut Stream,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
if let Some(cmeta) = buffer.buffer.meta::<gst_audio::AudioClippingMeta>() {
|
let cmeta = if let Some(cmeta) = buffer.buffer.meta::<gst_audio::AudioClippingMeta>() {
|
||||||
let timescale = stream
|
cmeta
|
||||||
.caps
|
} else {
|
||||||
.structure(0)
|
return Ok(());
|
||||||
.unwrap()
|
};
|
||||||
.get::<i32>("rate")
|
|
||||||
.unwrap_or_else(|_| stream.timescale() as i32);
|
|
||||||
|
|
||||||
let gstclocktime_to_samples = move |v: gst::ClockTime| {
|
let timescale = stream
|
||||||
v.nseconds()
|
.caps
|
||||||
.mul_div_round(timescale as u64, gst::ClockTime::SECOND.nseconds())
|
.structure(0)
|
||||||
.context("Invalid start in the AudioClipMeta")
|
.unwrap()
|
||||||
};
|
.get::<i32>("rate")
|
||||||
|
.unwrap_or_else(|_| stream.timescale() as i32);
|
||||||
|
|
||||||
let generic_to_samples = move |t| -> Result<Option<u64>, anyhow::Error> {
|
let samples_to_gstclocktime = move |v: u64| {
|
||||||
if let gst::GenericFormattedValue::Default(Some(v)) = t {
|
let nseconds = v
|
||||||
let v = v.into();
|
.mul_div_round(gst::ClockTime::SECOND.nseconds(), timescale as u64)
|
||||||
Ok(Some(v).filter(|x| x != &0u64))
|
.context("Invalid start in the AudioClipMeta")?;
|
||||||
} else if let gst::GenericFormattedValue::Time(Some(v)) = t {
|
Ok::<_, anyhow::Error>(gst::ClockTime::from_nseconds(nseconds))
|
||||||
Ok(Some(gstclocktime_to_samples(v)?))
|
};
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let start = generic_to_samples(cmeta.start())?;
|
let generic_to_gstclocktime = move |t| -> Result<Option<gst::ClockTime>, anyhow::Error> {
|
||||||
let end = generic_to_samples(cmeta.end())?;
|
if let gst::GenericFormattedValue::Default(Some(v)) = t {
|
||||||
if end.is_none() && start.is_none() {
|
let v = u64::from(v);
|
||||||
return Err(anyhow::anyhow!(
|
let v = samples_to_gstclocktime(v)?;
|
||||||
"No start or end time in `default` format in the AudioClipingMeta"
|
Ok(Some(v).filter(|x| !x.is_zero()))
|
||||||
));
|
} else if let gst::GenericFormattedValue::Time(Some(v)) = t {
|
||||||
|
Ok(Some(v).filter(|x| !x.is_zero()))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let start = if let Some(start) = generic_to_samples(cmeta.start())? {
|
let start = generic_to_gstclocktime(cmeta.start())?;
|
||||||
start + gstclocktime_to_samples(buffer.pts)?
|
let end = generic_to_gstclocktime(cmeta.end())?;
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
let duration = if let Some(e) = end {
|
|
||||||
Some(
|
|
||||||
gstclocktime_to_samples(buffer.end_pts)?
|
|
||||||
.checked_sub(e)
|
|
||||||
.context("Invalid end in the AudioClipMeta")?,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
stream.elst_infos.push(super::ElstInfo {
|
if end.is_none() && start.is_none() {
|
||||||
start: start as i64,
|
bail!("No start or end time in `default` format in the AudioClipingMeta");
|
||||||
duration,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let start = if let Some(start) = generic_to_gstclocktime(cmeta.start())? {
|
||||||
|
start + buffer.pts
|
||||||
|
} else {
|
||||||
|
gst::ClockTime::ZERO
|
||||||
|
};
|
||||||
|
let duration = end.map(|end| buffer.end_pts - end);
|
||||||
|
|
||||||
|
stream.elst_infos.push(super::ElstInfo {
|
||||||
|
start: Some(start.into()),
|
||||||
|
duration,
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -197,8 +197,8 @@ pub(crate) struct HeaderConfiguration {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct ElstInfo {
|
pub(crate) struct ElstInfo {
|
||||||
start: i64,
|
start: Option<gst::Signed<gst::ClockTime>>,
|
||||||
duration: Option<u64>,
|
duration: Option<gst::ClockTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
Loading…
Reference in a new issue