mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-01-24 18:08:15 +00:00
fmp4mux: Write ONVIF Export File Format CorrectStartTime box for ONVIF variant
This commit is contained in:
parent
2b61d51e91
commit
5376596557
4 changed files with 52 additions and 25 deletions
|
@ -15,6 +15,7 @@ gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/g
|
||||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
|
uuid = { version = "1", features = ["v4"] }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstfmp4"
|
name = "gstfmp4"
|
||||||
|
|
|
@ -355,8 +355,7 @@ fn brands_from_variant_and_caps<'a>(
|
||||||
pub(super) fn create_fmp4_header(cfg: super::HeaderConfiguration) -> Result<gst::Buffer, Error> {
|
pub(super) fn create_fmp4_header(cfg: super::HeaderConfiguration) -> Result<gst::Buffer, Error> {
|
||||||
let mut v = vec![];
|
let mut v = vec![];
|
||||||
|
|
||||||
let (brand, compatible_brands) =
|
let (brand, compatible_brands) = brands_from_variant_and_caps(cfg.variant, cfg.streams.iter());
|
||||||
brands_from_variant_and_caps(cfg.variant, cfg.streams.iter().map(|s| &s.1));
|
|
||||||
|
|
||||||
write_box(&mut v, b"ftyp", |v| {
|
write_box(&mut v, b"ftyp", |v| {
|
||||||
// major brand
|
// major brand
|
||||||
|
@ -371,6 +370,42 @@ pub(super) fn create_fmp4_header(cfg: super::HeaderConfiguration) -> Result<gst:
|
||||||
|
|
||||||
write_box(&mut v, b"moov", |v| write_moov(v, &cfg))?;
|
write_box(&mut v, b"moov", |v| write_moov(v, &cfg))?;
|
||||||
|
|
||||||
|
if cfg.variant == super::Variant::ONVIF {
|
||||||
|
write_full_box(
|
||||||
|
&mut v,
|
||||||
|
b"meta",
|
||||||
|
FULL_BOX_VERSION_0,
|
||||||
|
FULL_BOX_FLAGS_NONE,
|
||||||
|
|v| {
|
||||||
|
write_full_box(v, b"hdlr", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||||
|
// Handler type
|
||||||
|
v.extend(b"null");
|
||||||
|
|
||||||
|
// Reserved
|
||||||
|
v.extend([0u8; 3 * 4]);
|
||||||
|
|
||||||
|
// Name
|
||||||
|
v.extend(b"MetadataHandler");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
write_box(v, b"cstb", |v| {
|
||||||
|
// entry count
|
||||||
|
v.extend(1u32.to_be_bytes());
|
||||||
|
|
||||||
|
// track id
|
||||||
|
v.extend(0u32.to_be_bytes());
|
||||||
|
|
||||||
|
// XXX: start UTC time in 100ns units since Jan 1 1601
|
||||||
|
v.extend(0u64.to_be_bytes());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(gst::Buffer::from_mut_slice(v))
|
Ok(gst::Buffer::from_mut_slice(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,7 +420,7 @@ fn write_moov(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), E
|
||||||
write_full_box(v, b"mvhd", FULL_BOX_VERSION_1, FULL_BOX_FLAGS_NONE, |v| {
|
write_full_box(v, b"mvhd", FULL_BOX_VERSION_1, FULL_BOX_FLAGS_NONE, |v| {
|
||||||
write_mvhd(v, cfg, creation_time)
|
write_mvhd(v, cfg, creation_time)
|
||||||
})?;
|
})?;
|
||||||
for (idx, (pad, caps)) in cfg.streams.iter().enumerate() {
|
for (idx, caps) in cfg.streams.iter().enumerate() {
|
||||||
write_box(v, b"trak", |v| {
|
write_box(v, b"trak", |v| {
|
||||||
let mut references = vec![];
|
let mut references = vec![];
|
||||||
|
|
||||||
|
@ -394,7 +429,7 @@ fn write_moov(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), E
|
||||||
&& caps.structure(0).unwrap().name() == "application/x-onvif-metadata"
|
&& caps.structure(0).unwrap().name() == "application/x-onvif-metadata"
|
||||||
{
|
{
|
||||||
// Find the first video track
|
// Find the first video track
|
||||||
for (idx, (_pad, caps)) in cfg.streams.iter().enumerate() {
|
for (idx, caps) in cfg.streams.iter().enumerate() {
|
||||||
let s = caps.structure(0).unwrap();
|
let s = caps.structure(0).unwrap();
|
||||||
|
|
||||||
if matches!(s.name(), "video/x-h264" | "video/x-h265" | "image/jpeg") {
|
if matches!(s.name(), "video/x-h264" | "video/x-h265" | "image/jpeg") {
|
||||||
|
@ -407,7 +442,7 @@ fn write_moov(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), E
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
write_trak(v, cfg, idx, pad, caps, creation_time, &references)
|
write_trak(v, cfg, idx, caps, creation_time, &references)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
write_box(v, b"mvex", |v| write_mvex(v, cfg))?;
|
write_box(v, b"mvex", |v| write_mvex(v, cfg))?;
|
||||||
|
@ -454,7 +489,7 @@ fn write_mvhd(
|
||||||
// Modification time
|
// Modification time
|
||||||
v.extend(creation_time.to_be_bytes());
|
v.extend(creation_time.to_be_bytes());
|
||||||
// Timescale: uses the reference track timescale
|
// Timescale: uses the reference track timescale
|
||||||
v.extend(caps_to_timescale(&cfg.streams[0].1).to_be_bytes());
|
v.extend(caps_to_timescale(&cfg.streams[0]).to_be_bytes());
|
||||||
// Duration
|
// Duration
|
||||||
v.extend(0u64.to_be_bytes());
|
v.extend(0u64.to_be_bytes());
|
||||||
|
|
||||||
|
@ -504,7 +539,6 @@ fn write_trak(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
cfg: &super::HeaderConfiguration,
|
cfg: &super::HeaderConfiguration,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
_pad: &gst_base::AggregatorPad,
|
|
||||||
caps: &gst::CapsRef,
|
caps: &gst::CapsRef,
|
||||||
creation_time: u64,
|
creation_time: u64,
|
||||||
references: &[TrackReference],
|
references: &[TrackReference],
|
||||||
|
@ -1389,7 +1423,7 @@ fn write_mvex(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), E
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (idx, (_pad, _caps)) in cfg.streams.iter().enumerate() {
|
for (idx, _caps) in cfg.streams.iter().enumerate() {
|
||||||
write_full_box(v, b"trex", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
write_full_box(v, b"trex", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||||
write_trex(v, cfg, idx)
|
write_trex(v, cfg, idx)
|
||||||
})?;
|
})?;
|
||||||
|
@ -1400,7 +1434,7 @@ fn write_mvex(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), E
|
||||||
|
|
||||||
fn write_mehd(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), Error> {
|
fn write_mehd(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), Error> {
|
||||||
// Use the reference track timescale
|
// Use the reference track timescale
|
||||||
let timescale = caps_to_timescale(&cfg.streams[0].1);
|
let timescale = caps_to_timescale(&cfg.streams[0]);
|
||||||
|
|
||||||
let duration = cfg
|
let duration = cfg
|
||||||
.duration
|
.duration
|
||||||
|
@ -1443,7 +1477,7 @@ pub(super) fn create_fmp4_fragment_header(
|
||||||
let mut v = vec![];
|
let mut v = vec![];
|
||||||
|
|
||||||
let (brand, compatible_brands) =
|
let (brand, compatible_brands) =
|
||||||
brands_from_variant_and_caps(cfg.variant, cfg.streams.iter().map(|s| &s.1));
|
brands_from_variant_and_caps(cfg.variant, cfg.streams.iter().map(|s| &s.0));
|
||||||
|
|
||||||
write_box(&mut v, b"styp", |v| {
|
write_box(&mut v, b"styp", |v| {
|
||||||
// major brand
|
// major brand
|
||||||
|
@ -1494,7 +1528,7 @@ fn write_moof(
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut data_offset_offsets = vec![];
|
let mut data_offset_offsets = vec![];
|
||||||
for (idx, (_pad, caps, timing_info)) in cfg.streams.iter().enumerate() {
|
for (idx, (caps, timing_info)) in cfg.streams.iter().enumerate() {
|
||||||
// Skip tracks without any buffers for this fragment.
|
// Skip tracks without any buffers for this fragment.
|
||||||
let timing_info = match timing_info {
|
let timing_info = match timing_info {
|
||||||
None => continue,
|
None => continue,
|
||||||
|
|
|
@ -607,7 +607,7 @@ impl FMP4Mux {
|
||||||
"Draining no buffers",
|
"Draining no buffers",
|
||||||
);
|
);
|
||||||
|
|
||||||
streams.push((stream.sinkpad.clone(), stream.caps.clone(), None));
|
streams.push((stream.caps.clone(), None));
|
||||||
drain_buffers.push(VecDeque::new());
|
drain_buffers.push(VecDeque::new());
|
||||||
} else {
|
} else {
|
||||||
let first_gop = gops.first().unwrap();
|
let first_gop = gops.first().unwrap();
|
||||||
|
@ -678,7 +678,6 @@ impl FMP4Mux {
|
||||||
};
|
};
|
||||||
|
|
||||||
streams.push((
|
streams.push((
|
||||||
stream.sinkpad.clone(),
|
|
||||||
stream.caps.clone(),
|
stream.caps.clone(),
|
||||||
Some(super::FragmentTimingInfo {
|
Some(super::FragmentTimingInfo {
|
||||||
start_time,
|
start_time,
|
||||||
|
@ -928,7 +927,7 @@ impl FMP4Mux {
|
||||||
|
|
||||||
// Write mfra only for the main stream, and if there are no buffers for the main stream
|
// Write mfra only for the main stream, and if there are no buffers for the main stream
|
||||||
// in this segment then don't write anything.
|
// in this segment then don't write anything.
|
||||||
if let Some((_pad, _caps, Some(ref timing_info))) = streams.get(0) {
|
if let Some((_caps, Some(ref timing_info))) = streams.get(0) {
|
||||||
state.fragment_offsets.push(super::FragmentOffset {
|
state.fragment_offsets.push(super::FragmentOffset {
|
||||||
time: timing_info.start_time,
|
time: timing_info.start_time,
|
||||||
offset: moof_offset,
|
offset: moof_offset,
|
||||||
|
@ -952,7 +951,7 @@ impl FMP4Mux {
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.write_mfra && at_eos {
|
if settings.write_mfra && at_eos {
|
||||||
match boxes::create_mfra(&streams[0].1, &state.fragment_offsets) {
|
match boxes::create_mfra(&streams[0].0, &state.fragment_offsets) {
|
||||||
Ok(mut mfra) => {
|
Ok(mut mfra) => {
|
||||||
{
|
{
|
||||||
let mfra = mfra.get_mut().unwrap();
|
let mfra = mfra.get_mut().unwrap();
|
||||||
|
@ -1100,11 +1099,10 @@ impl FMP4Mux {
|
||||||
let streams = state
|
let streams = state
|
||||||
.streams
|
.streams
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| (s.sinkpad.clone(), s.caps.clone()))
|
.map(|s| s.caps.clone())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut buffer = boxes::create_fmp4_header(super::HeaderConfiguration {
|
let mut buffer = boxes::create_fmp4_header(super::HeaderConfiguration {
|
||||||
element,
|
|
||||||
variant,
|
variant,
|
||||||
update: at_eos,
|
update: at_eos,
|
||||||
streams: streams.as_slice(),
|
streams: streams.as_slice(),
|
||||||
|
|
|
@ -64,12 +64,10 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct HeaderConfiguration<'a> {
|
pub(crate) struct HeaderConfiguration<'a> {
|
||||||
variant: Variant,
|
variant: Variant,
|
||||||
#[allow(dead_code)]
|
|
||||||
element: &'a FMP4Mux,
|
|
||||||
update: bool,
|
update: bool,
|
||||||
/// First caps must be the video/reference stream. Must be in the order the tracks are going to
|
/// First caps must be the video/reference stream. Must be in the order the tracks are going to
|
||||||
/// be used later for the fragments too.
|
/// be used later for the fragments too.
|
||||||
streams: &'a [(gst_base::AggregatorPad, gst::Caps)],
|
streams: &'a [gst::Caps],
|
||||||
write_mehd: bool,
|
write_mehd: bool,
|
||||||
duration: Option<gst::ClockTime>,
|
duration: Option<gst::ClockTime>,
|
||||||
}
|
}
|
||||||
|
@ -78,11 +76,7 @@ pub(crate) struct HeaderConfiguration<'a> {
|
||||||
pub(crate) struct FragmentHeaderConfiguration<'a> {
|
pub(crate) struct FragmentHeaderConfiguration<'a> {
|
||||||
variant: Variant,
|
variant: Variant,
|
||||||
sequence_number: u32,
|
sequence_number: u32,
|
||||||
streams: &'a [(
|
streams: &'a [(gst::Caps, Option<FragmentTimingInfo>)],
|
||||||
gst_base::AggregatorPad,
|
|
||||||
gst::Caps,
|
|
||||||
Option<FragmentTimingInfo>,
|
|
||||||
)],
|
|
||||||
buffers: &'a [Buffer],
|
buffers: &'a [Buffer],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue