mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-26 05:21:00 +00:00
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:
parent
dba91bceca
commit
371ac83169
2 changed files with 79 additions and 41 deletions
|
@ -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" }
|
||||||
|
|
|
@ -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,28 +150,29 @@ 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 =
|
||||||
|
|start: gst::ClockTime, duration: gst::ClockTime, repeat: usize| {
|
||||||
|
let mut s = dash_mpd::S {
|
||||||
|
t: Some(start.mseconds() as i64),
|
||||||
|
d: duration.mseconds() as i64,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
if repeat > 0 {
|
if repeat > 0 {
|
||||||
writeln!(
|
s.r = Some(repeat as i64);
|
||||||
&mut segment_timeline,
|
|
||||||
" <S t=\"{time}\" d=\"{duration}\" r=\"{repeat}\" />",
|
|
||||||
time = start.mseconds(),
|
|
||||||
duration = duration.mseconds(),
|
|
||||||
repeat = repeat
|
|
||||||
).unwrap();
|
|
||||||
} else {
|
|
||||||
writeln!(
|
|
||||||
&mut segment_timeline,
|
|
||||||
" <S t=\"{time}\" d=\"{duration}\" />",
|
|
||||||
time = start.mseconds(),
|
|
||||||
duration = duration.mseconds()
|
|
||||||
).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
segments.push(s);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut start = None;
|
let mut start = None;
|
||||||
|
@ -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");
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue