lesavka/server/src/media_timing.rs

73 lines
2.7 KiB
Rust
Raw Normal View History

2026-04-24 18:26:19 -03:00
//! 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);
}
}