fmp4: dash_vod example: use dash-mpd to generate the manifest

Maybe a bit overkill for such simple example but more exemplary for
actual applications.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1177>
This commit is contained in:
Guillaume Desmottes 2023-04-12 16:24:37 +02:00
parent dba91bceca
commit 371ac83169
2 changed files with 79 additions and 41 deletions

View file

@ -28,6 +28,9 @@ gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] } gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] }
m3u8-rs = "5.0" m3u8-rs = "5.0"
chrono = "0.4" chrono = "0.4"
dash-mpd = { version = "0.7", default-features = false }
quick-xml = { version = "0.28", features = ["serialize"] }
serde = "1"
[build-dependencies] [build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" } gst-plugin-version-helper = { path="../../version-helper" }

View file

@ -14,7 +14,6 @@
use gst::prelude::*; use gst::prelude::*;
use std::fmt::Write;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -151,29 +150,30 @@ fn main() -> Result<(), Error> {
println!("writing manifest to {}", path.display()); println!("writing manifest to {}", path.display());
let duration = state.end_time.opt_checked_sub(state.start_time).ok().flatten().unwrap().mseconds() as f64 / 1000.0; let duration = state
.end_time
.opt_checked_sub(state.start_time)
.ok()
.flatten()
.unwrap()
.mseconds();
// Write the whole segment timeline out here, compressing multiple segments with // Write the whole segment timeline out here, compressing multiple segments with
// the same duration to a repeated segment. // the same duration to a repeated segment.
let mut segment_timeline = String::new(); let mut segments = vec![];
let mut write_segment = |start: gst::ClockTime, duration: gst::ClockTime, repeat: usize| { let mut write_segment =
if repeat > 0 { |start: gst::ClockTime, duration: gst::ClockTime, repeat: usize| {
writeln!( let mut s = dash_mpd::S {
&mut segment_timeline, t: Some(start.mseconds() as i64),
" <S t=\"{time}\" d=\"{duration}\" r=\"{repeat}\" />", d: duration.mseconds() as i64,
time = start.mseconds(), ..Default::default()
duration = duration.mseconds(), };
repeat = repeat if repeat > 0 {
).unwrap(); s.r = Some(repeat as i64);
} else { }
writeln!(
&mut segment_timeline, segments.push(s);
" <S t=\"{time}\" d=\"{duration}\" />", };
time = start.mseconds(),
duration = duration.mseconds()
).unwrap();
}
};
let mut start = None; let mut start = None;
let mut num_segments = 0; let mut num_segments = 0;
@ -203,26 +203,61 @@ fn main() -> Result<(), Error> {
write_segment(start.unwrap(), last_duration.unwrap(), num_segments - 1); write_segment(start.unwrap(), last_duration.unwrap(), num_segments - 1);
} }
let manifest = format!(r###"<?xml version="1.0" encoding="UTF-8"?> let segment_template = dash_mpd::SegmentTemplate {
<MPD timescale: Some(1000),
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" initialization: Some("init.cmfi".to_string()),
xmlns="urn:mpeg:dash:schema:mpd:2011" media: Some("segment_$Number$.cmfv".to_string()),
xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" SegmentTimeline: Some(dash_mpd::SegmentTimeline { segments }),
type="static" ..Default::default()
mediaPresentationDuration="PT{duration:.3}S" };
profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">
<Period> let rep = dash_mpd::Representation {
<AdaptationSet mimeType="video/mp4" codecs="avc1.4d0228" frameRate="30/1" segmentAlignment="true" startWithSAP="1"> id: Some("A".to_string()),
<Representation id="A" bandwidth="2048000" with="1280" height="720"> width: Some(1280),
<SegmentTemplate timescale="1000" initialization="init.cmfi" media="segment_$Number$.cmfv"> height: Some(720),
<SegmentTimeline> bandwidth: Some(2048000),
{segment_timeline} </SegmentTimeline> SegmentTemplate: Some(segment_template),
</SegmentTemplate> ..Default::default()
</Representation> };
</AdaptationSet>
</Period> let adapt = dash_mpd::AdaptationSet {
</MPD> contentType: Some("video".to_string()),
"###); mimeType: Some("video/mp4".to_string()),
codecs: Some("avc1.4d0228".to_string()),
frameRate: Some("30/1".to_string()),
segmentAlignment: Some(true),
subsegmentStartsWithSAP: Some(1),
representations: vec![rep],
..Default::default()
};
let period = dash_mpd::Period {
adaptations: vec![adapt],
..Default::default()
};
let mpd = dash_mpd::MPD {
mpdtype: Some("static".to_string()),
xmlns: Some("urn:mpeg:dash:schema:mpd:2011".to_string()),
schemaLocation: Some("urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd".to_string()),
profiles: Some("urn:mpeg:dash:profile:isoff-on-demand:2011".to_string()),
periods: vec![period],
mediaPresentationDuration: Some(std::time::Duration::from_millis(duration)),
..Default::default()
};
use serde::ser::Serialize;
let mut xml = String::new();
let mut ser = quick_xml::se::Serializer::new(&mut xml);
ser.indent(' ', 4);
mpd.serialize(ser).unwrap();
let manifest = format!(
r###"<?xml version="1.0" encoding="UTF-8"?>
{xml}
"###
);
std::fs::write(path, manifest).expect("failed to write manifest"); std::fs::write(path, manifest).expect("failed to write manifest");
}) })