mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-26 05:21:00 +00:00
hlssink3: Add property track-pipeline-clock-for-pdt.
This is required to take care of clock skew between system time and pipeline time. `track-pipeline-clock-for-pdt: true` mean utd time is sampled for first segment and for subsequent segments keep adding the time based on pipeline clock. difference of segment duration and PDT time will match. track-pipeline-clock-for-pdt: false` mean utd time is sampled for each segment. system time may jump forward or backward based on adjustments. If application needs to synchronization of external events `false` is recommended. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1145>
This commit is contained in:
parent
4be24fdcaf
commit
a7fe24a294
3 changed files with 53 additions and 21 deletions
|
@ -2236,6 +2236,18 @@
|
||||||
"type": "guint",
|
"type": "guint",
|
||||||
"writable": true
|
"writable": true
|
||||||
},
|
},
|
||||||
|
"pdt-follows-pipeline-clock": {
|
||||||
|
"blurb": "As there might be drift between the wallclock and pipeline clock, this controls whether the Program-Date-Time markers should follow the pipeline clock rate (true), or be skewed to match the wallclock rate (false).",
|
||||||
|
"conditionally-available": false,
|
||||||
|
"construct": false,
|
||||||
|
"construct-only": false,
|
||||||
|
"controllable": false,
|
||||||
|
"default": "true",
|
||||||
|
"mutable": "null",
|
||||||
|
"readable": true,
|
||||||
|
"type": "gboolean",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
"playlist-length": {
|
"playlist-length": {
|
||||||
"blurb": "Length of HLS playlist. To allow players to conform to section 6.3.3 of the HLS specification, this should be at least 3. If set to 0, the playlist will be infinite.",
|
"blurb": "Length of HLS playlist. To allow players to conform to section 6.3.3 of the HLS specification, this should be at least 3. If set to 0, the playlist will be infinite.",
|
||||||
"conditionally-available": false,
|
"conditionally-available": false,
|
||||||
|
|
|
@ -28,6 +28,7 @@ const DEFAULT_PLAYLIST_LENGTH: u32 = 5;
|
||||||
const DEFAULT_PLAYLIST_TYPE: HlsSink3PlaylistType = HlsSink3PlaylistType::Unspecified;
|
const DEFAULT_PLAYLIST_TYPE: HlsSink3PlaylistType = HlsSink3PlaylistType::Unspecified;
|
||||||
const DEFAULT_I_FRAMES_ONLY_PLAYLIST: bool = false;
|
const DEFAULT_I_FRAMES_ONLY_PLAYLIST: bool = false;
|
||||||
const DEFAULT_PROGRAM_DATE_TIME_TAG: bool = false;
|
const DEFAULT_PROGRAM_DATE_TIME_TAG: bool = false;
|
||||||
|
const DEFAULT_CLOCK_TRACKING_FOR_PDT: bool = true;
|
||||||
const DEFAULT_SEND_KEYFRAME_REQUESTS: bool = true;
|
const DEFAULT_SEND_KEYFRAME_REQUESTS: bool = true;
|
||||||
|
|
||||||
const SIGNAL_GET_PLAYLIST_STREAM: &str = "get-playlist-stream";
|
const SIGNAL_GET_PLAYLIST_STREAM: &str = "get-playlist-stream";
|
||||||
|
@ -71,6 +72,7 @@ struct Settings {
|
||||||
target_duration: u32,
|
target_duration: u32,
|
||||||
i_frames_only: bool,
|
i_frames_only: bool,
|
||||||
enable_program_date_time: bool,
|
enable_program_date_time: bool,
|
||||||
|
pdt_follows_pipeline_clock: bool,
|
||||||
send_keyframe_requests: bool,
|
send_keyframe_requests: bool,
|
||||||
|
|
||||||
splitmuxsink: gst::Element,
|
splitmuxsink: gst::Element,
|
||||||
|
@ -101,6 +103,7 @@ impl Default for Settings {
|
||||||
send_keyframe_requests: DEFAULT_SEND_KEYFRAME_REQUESTS,
|
send_keyframe_requests: DEFAULT_SEND_KEYFRAME_REQUESTS,
|
||||||
i_frames_only: DEFAULT_I_FRAMES_ONLY_PLAYLIST,
|
i_frames_only: DEFAULT_I_FRAMES_ONLY_PLAYLIST,
|
||||||
enable_program_date_time: DEFAULT_PROGRAM_DATE_TIME_TAG,
|
enable_program_date_time: DEFAULT_PROGRAM_DATE_TIME_TAG,
|
||||||
|
pdt_follows_pipeline_clock: DEFAULT_CLOCK_TRACKING_FOR_PDT,
|
||||||
|
|
||||||
splitmuxsink,
|
splitmuxsink,
|
||||||
giostreamsink,
|
giostreamsink,
|
||||||
|
@ -112,7 +115,7 @@ impl Default for Settings {
|
||||||
|
|
||||||
pub(crate) struct StartedState {
|
pub(crate) struct StartedState {
|
||||||
base_date_time: Option<DateTime<Utc>>,
|
base_date_time: Option<DateTime<Utc>>,
|
||||||
base_running_time: Option<gst::ClockTime>,
|
pdt_base_running_time: Option<gst::ClockTime>,
|
||||||
playlist: Playlist,
|
playlist: Playlist,
|
||||||
fragment_opened_at: Option<gst::ClockTime>,
|
fragment_opened_at: Option<gst::ClockTime>,
|
||||||
fragment_running_time: Option<gst::ClockTime>,
|
fragment_running_time: Option<gst::ClockTime>,
|
||||||
|
@ -128,7 +131,7 @@ impl StartedState {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
base_date_time: None,
|
base_date_time: None,
|
||||||
base_running_time: None,
|
pdt_base_running_time: None,
|
||||||
playlist: Playlist::new(target_duration, playlist_type, i_frames_only),
|
playlist: Playlist::new(target_duration, playlist_type, i_frames_only),
|
||||||
current_segment_location: None,
|
current_segment_location: None,
|
||||||
fragment_opened_at: None,
|
fragment_opened_at: None,
|
||||||
|
@ -436,7 +439,7 @@ impl BinImpl for HlsSink3 {
|
||||||
gst::element_error!(
|
gst::element_error!(
|
||||||
self.obj(),
|
self.obj(),
|
||||||
gst::StreamError::Failed,
|
gst::StreamError::Failed,
|
||||||
("Framented closed in wrong state"),
|
("Fragment closed in wrong state"),
|
||||||
["Fragment closed but element is in stopped state"]
|
["Fragment closed but element is in stopped state"]
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
@ -444,41 +447,47 @@ impl BinImpl for HlsSink3 {
|
||||||
State::Started(state) => state,
|
State::Started(state) => state,
|
||||||
};
|
};
|
||||||
|
|
||||||
if state.base_running_time.is_none() && state.fragment_running_time.is_some() {
|
let fragment_pts = state
|
||||||
state.base_running_time = state.fragment_running_time;
|
.fragment_running_time
|
||||||
|
.expect("fragment running time must be set by format-location-full");
|
||||||
|
|
||||||
|
if state.pdt_base_running_time.is_none() {
|
||||||
|
state.pdt_base_running_time = state.fragment_running_time;
|
||||||
}
|
}
|
||||||
// Calculate the mapping from running time to UTC
|
// Calculate the mapping from running time to UTC
|
||||||
if state.base_date_time.is_none() && state.fragment_running_time.is_some() {
|
// calculate base_date_time for each segment for !pdt_follows_pipeline_clock
|
||||||
let fragment_pts = state.fragment_running_time.unwrap();
|
// when pdt_follows_pipeline_clock is set, we calculate the base time every time
|
||||||
|
// this avoids the drift between pdt tag and external clock (if gst clock has skew w.r.t external clock)
|
||||||
|
if state.base_date_time.is_none() || !settings.pdt_follows_pipeline_clock {
|
||||||
let now_utc = Utc::now();
|
let now_utc = Utc::now();
|
||||||
let now_gst = settings.giostreamsink.clock().unwrap().time().unwrap();
|
let now_gst = settings.giostreamsink.clock().unwrap().time().unwrap();
|
||||||
let pts_clock_time =
|
let pts_clock_time =
|
||||||
fragment_pts + settings.giostreamsink.base_time().unwrap();
|
fragment_pts + settings.giostreamsink.base_time().unwrap();
|
||||||
|
|
||||||
let diff = now_gst.checked_sub(pts_clock_time).unwrap();
|
let diff = now_gst.checked_sub(pts_clock_time).expect("time between fragments running time and current running time overflow");
|
||||||
let pts_utc = now_utc
|
let pts_utc = now_utc
|
||||||
.checked_sub_signed(Duration::nanoseconds(diff.nseconds() as i64))
|
.checked_sub_signed(Duration::nanoseconds(diff.nseconds() as i64))
|
||||||
.unwrap();
|
.expect("offsetting the utc with gstreamer clock-diff overflow");
|
||||||
|
|
||||||
state.base_date_time = Some(pts_utc);
|
state.base_date_time = Some(pts_utc);
|
||||||
}
|
}
|
||||||
|
|
||||||
let fragment_date_time = if settings.enable_program_date_time
|
let fragment_date_time = if settings.enable_program_date_time
|
||||||
&& state.base_running_time.is_some()
|
&& state.pdt_base_running_time.is_some()
|
||||||
&& state.fragment_running_time.is_some()
|
|
||||||
{
|
{
|
||||||
// Add the diff of running time to UTC time
|
// Add the diff of running time to UTC time
|
||||||
// date_time = first_segment_utc + (current_seg_running_time - first_seg_running_time)
|
// date_time = first_segment_utc + (current_seg_running_time - first_seg_running_time)
|
||||||
state.base_date_time.unwrap().checked_add_signed(
|
state
|
||||||
Duration::nanoseconds(
|
.base_date_time
|
||||||
|
.unwrap()
|
||||||
|
.checked_add_signed(Duration::nanoseconds(
|
||||||
state
|
state
|
||||||
.fragment_running_time
|
.fragment_running_time
|
||||||
.opt_checked_sub(state.base_running_time)
|
.opt_checked_sub(state.pdt_base_running_time)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.nseconds() as i64,
|
.nseconds() as i64,
|
||||||
),
|
))
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -544,6 +553,11 @@ impl ObjectImpl for HlsSink3 {
|
||||||
.blurb("put EXT-X-PROGRAM-DATE-TIME tag in the playlist")
|
.blurb("put EXT-X-PROGRAM-DATE-TIME tag in the playlist")
|
||||||
.default_value(DEFAULT_PROGRAM_DATE_TIME_TAG)
|
.default_value(DEFAULT_PROGRAM_DATE_TIME_TAG)
|
||||||
.build(),
|
.build(),
|
||||||
|
glib::ParamSpecBoolean::builder("pdt-follows-pipeline-clock")
|
||||||
|
.nick("Whether Program-Date-Time should follow the pipeline clock")
|
||||||
|
.blurb("As there might be drift between the wallclock and pipeline clock, this controls whether the Program-Date-Time markers should follow the pipeline clock rate (true), or be skewed to match the wallclock rate (false).")
|
||||||
|
.default_value(DEFAULT_CLOCK_TRACKING_FOR_PDT)
|
||||||
|
.build(),
|
||||||
glib::ParamSpecBoolean::builder("send-keyframe-requests")
|
glib::ParamSpecBoolean::builder("send-keyframe-requests")
|
||||||
.nick("Send Keyframe Requests")
|
.nick("Send Keyframe Requests")
|
||||||
.blurb("Send keyframe requests to ensure correct fragmentation. If this is disabled then the input must have keyframes in regular intervals.")
|
.blurb("Send keyframe requests to ensure correct fragmentation. If this is disabled then the input must have keyframes in regular intervals.")
|
||||||
|
@ -614,6 +628,9 @@ impl ObjectImpl for HlsSink3 {
|
||||||
"enable-program-date-time" => {
|
"enable-program-date-time" => {
|
||||||
settings.enable_program_date_time = value.get().expect("type checked upstream");
|
settings.enable_program_date_time = value.get().expect("type checked upstream");
|
||||||
}
|
}
|
||||||
|
"pdt-follows-pipeline-clock" => {
|
||||||
|
settings.pdt_follows_pipeline_clock = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
"send-keyframe-requests" => {
|
"send-keyframe-requests" => {
|
||||||
settings.send_keyframe_requests = value.get().expect("type checked upstream");
|
settings.send_keyframe_requests = value.get().expect("type checked upstream");
|
||||||
settings
|
settings
|
||||||
|
@ -642,6 +659,7 @@ impl ObjectImpl for HlsSink3 {
|
||||||
}
|
}
|
||||||
"i-frames-only" => settings.i_frames_only.to_value(),
|
"i-frames-only" => settings.i_frames_only.to_value(),
|
||||||
"enable-program-date-time" => settings.enable_program_date_time.to_value(),
|
"enable-program-date-time" => settings.enable_program_date_time.to_value(),
|
||||||
|
"pdt-follows-pipeline-clock" => settings.pdt_follows_pipeline_clock.to_value(),
|
||||||
"send-keyframe-requests" => settings.send_keyframe_requests.to_value(),
|
"send-keyframe-requests" => settings.send_keyframe_requests.to_value(),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
@ -753,7 +771,7 @@ impl ObjectImpl for HlsSink3 {
|
||||||
gst::info!(CAT, imp: self_, "Got fragment-id: {}", fragment_id);
|
gst::info!(CAT, imp: self_, "Got fragment-id: {}", fragment_id);
|
||||||
|
|
||||||
let mut state_guard = state.lock().unwrap();
|
let mut state_guard = state.lock().unwrap();
|
||||||
let mut state = match &mut *state_guard {
|
let state = match &mut *state_guard {
|
||||||
State::Stopped => {
|
State::Stopped => {
|
||||||
gst::error!(
|
gst::error!(
|
||||||
CAT,
|
CAT,
|
||||||
|
@ -773,7 +791,8 @@ impl ObjectImpl for HlsSink3 {
|
||||||
.expect("segment not available")
|
.expect("segment not available")
|
||||||
.downcast_ref::<gst::ClockTime>()
|
.downcast_ref::<gst::ClockTime>()
|
||||||
.expect("no time segment");
|
.expect("no time segment");
|
||||||
state.fragment_running_time = segment.to_running_time(buffer.pts().unwrap());
|
state.fragment_running_time =
|
||||||
|
segment.to_running_time(buffer.pts().unwrap());
|
||||||
} else {
|
} else {
|
||||||
gst::warning!(
|
gst::warning!(
|
||||||
CAT,
|
CAT,
|
||||||
|
@ -859,7 +878,7 @@ impl ElementImpl for HlsSink3 {
|
||||||
// reset mapping from rt to utc. during pause
|
// reset mapping from rt to utc. during pause
|
||||||
// rt is stopped but utc keep moving so need to
|
// rt is stopped but utc keep moving so need to
|
||||||
// calculate the mapping again
|
// calculate the mapping again
|
||||||
state.base_running_time = None;
|
state.pdt_base_running_time = None;
|
||||||
state.base_date_time = None
|
state.base_date_time = None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,8 +71,9 @@ impl Playlist {
|
||||||
/// Adds a new segment to the playlist.
|
/// Adds a new segment to the playlist.
|
||||||
pub fn add_segment(&mut self, uri: String, duration: f32, date_time: Option<DateTime<Utc>>) {
|
pub fn add_segment(&mut self, uri: String, duration: f32, date_time: Option<DateTime<Utc>>) {
|
||||||
self.start();
|
self.start();
|
||||||
// TODO: We are adding date-time to each segment, hence during write all the segments have
|
// We are adding date-time to each segment.Putting date-time-tag only for the first segment in the playlist
|
||||||
// program-date-time header.
|
// is also valid. But it is better to put program-date-time tag for every segment to take care of any drift.
|
||||||
|
// FFMPEG also put PDT tag for each segment.
|
||||||
self.inner.segments.push(MediaSegment {
|
self.inner.segments.push(MediaSegment {
|
||||||
uri,
|
uri,
|
||||||
duration,
|
duration,
|
||||||
|
|
Loading…
Reference in a new issue