73 lines
2.7 KiB
Rust
73 lines
2.7 KiB
Rust
|
|
//! Shared live-playback timing helpers for server-side sink pipelines.
|
||
|
|
//!
|
||
|
|
//! The HDMI and UAC paths live in separate GStreamer pipelines, so they need a
|
||
|
|
//! common clock source and a shared base-time derived from the same session PTS
|
||
|
|
//! timeline before sink synchronization can meaningfully align them.
|
||
|
|
|
||
|
|
use gst::prelude::*;
|
||
|
|
use gstreamer as gst;
|
||
|
|
|
||
|
|
/// Pin one playback pipeline to the shared system clock.
|
||
|
|
///
|
||
|
|
/// Inputs: the pipeline that will present live audio or video.
|
||
|
|
/// Outputs: none; the pipeline is configured in place.
|
||
|
|
/// Why: separate playback pipelines must use the same clock before their
|
||
|
|
/// session-rebased timestamps can line up.
|
||
|
|
pub(crate) fn prepare_pipeline_clock_sync(pipeline: &gst::Pipeline) {
|
||
|
|
let clock = gst::SystemClock::obtain();
|
||
|
|
pipeline.use_clock(Some(&clock));
|
||
|
|
pipeline.set_start_time(None::<gst::ClockTime>);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Align one playback pipeline so the supplied session PTS lands on "now".
|
||
|
|
///
|
||
|
|
/// Inputs: the pipeline plus the first session-local PTS, in microseconds,
|
||
|
|
/// that should render immediately.
|
||
|
|
/// Outputs: none; the pipeline base-time is updated in place.
|
||
|
|
/// Why: audio and video pipelines start at different wall-clock moments, so
|
||
|
|
/// each one must translate the shared session timeline back into the same
|
||
|
|
/// absolute base-time on its first packet.
|
||
|
|
pub(crate) fn align_pipeline_to_session_clock(pipeline: &gst::Pipeline, session_pts_us: u64) {
|
||
|
|
let clock = gst::SystemClock::obtain();
|
||
|
|
let now_ns = clock
|
||
|
|
.time()
|
||
|
|
.map(|value| value.nseconds())
|
||
|
|
.unwrap_or_default();
|
||
|
|
let base_time_ns = session_base_time_ns(now_ns, session_pts_us);
|
||
|
|
|
||
|
|
pipeline.use_clock(Some(&clock));
|
||
|
|
pipeline.set_start_time(None::<gst::ClockTime>);
|
||
|
|
pipeline.set_base_time(gst::ClockTime::from_nseconds(base_time_ns));
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Turn on clock-synchronized presentation when a sink exposes the standard
|
||
|
|
/// `sync` property.
|
||
|
|
///
|
||
|
|
/// Inputs: the sink element from the HDMI or audio playback path.
|
||
|
|
/// Outputs: none; the sink is configured in place when supported.
|
||
|
|
/// Why: timestamps only matter when the sink actually honors them.
|
||
|
|
pub(crate) fn enable_sink_clock_sync(sink: &gst::Element) {
|
||
|
|
if sink.has_property("sync", None) {
|
||
|
|
sink.set_property("sync", true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn session_base_time_ns(clock_time_ns: u64, session_pts_us: u64) -> u64 {
|
||
|
|
clock_time_ns.saturating_sub(session_pts_us.saturating_mul(1_000))
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::session_base_time_ns;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn session_base_time_subtracts_pts_from_clock_time() {
|
||
|
|
assert_eq!(session_base_time_ns(9_000_000, 3_000), 6_000_000);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn session_base_time_saturates_at_zero() {
|
||
|
|
assert_eq!(session_base_time_ns(2_000_000, 3_000), 0);
|
||
|
|
}
|
||
|
|
}
|