Skip to main content

dash_runtime/timing/
availability.rs

1//! Segment availability arithmetic for dynamic MPDs (ISO/IEC 23009-1
2//! 5.3.9.5.3; DASH-IF IOP v4.3 4.3.3.2). Integer microseconds in `i128`, so
3//! no input can wrap.
4//!
5//! * `SegAvailStart = AST + PeriodStart + MPDstart + MPDduration - ATO`
6//! * `SegAvailEnd   = AST + PeriodStart + MPDstart + 2 * MPDduration + TSB`
7//!   (`SegAvailStart` without the ATO, plus one more duration and the
8//!   time-shift buffer; Annex A.3.4 prints the same quantity)
9//! * Initialization Segment: available from `AST + PeriodStart` (5.3.9.5.2).
10
11const MICROS_PER_SECOND: i128 = 1_000_000;
12const MICROS_PER_SECOND_F64: f64 = 1_000_000.0;
13const ATO_CLAMP_US: i128 = 1 << 80;
14
15/// Convert `@availabilityTimeOffset` seconds (possibly `INF` or negative) to
16/// microseconds, clamped so later sums stay in range. A NaN is zero.
17#[must_use]
18pub fn ato_seconds_to_us(seconds: f64) -> i128 {
19    let us = seconds * MICROS_PER_SECOND_F64;
20    (us as i128).clamp(-ATO_CLAMP_US, ATO_CLAMP_US)
21}
22
23/// Ticks to microseconds, rounded toward negative infinity.
24#[must_use]
25pub fn ticks_to_us(ticks: i128, timescale: u64) -> i128 {
26    ticks
27        .saturating_mul(MICROS_PER_SECOND)
28        .div_euclid(i128::from(timescale.max(1)))
29}
30
31/// [`segment_available_from`] for a segment given in ticks: the end of the
32/// segment (`MPDstart + MPDduration`) is converted once, so rounding never
33/// accumulates.
34#[must_use]
35pub fn segment_available_from_ticks(
36    ast_us: i128,
37    period_start_us: i128,
38    start_ticks: i128,
39    duration_ticks: i128,
40    timescale: u64,
41    ato_us: i128,
42) -> i128 {
43    segment_available_from(
44        ast_us,
45        period_start_us,
46        ticks_to_us(start_ticks.saturating_add(duration_ticks), timescale),
47        0,
48        ato_us,
49    )
50}
51
52/// [`segment_available_until`] for a segment given in ticks.
53#[must_use]
54pub fn segment_available_until_ticks(
55    ast_us: i128,
56    period_start_us: i128,
57    start_ticks: i128,
58    duration_ticks: i128,
59    timescale: u64,
60    tsb_us: Option<i128>,
61) -> Option<i128> {
62    let end2 = start_ticks.saturating_add(duration_ticks.saturating_mul(2));
63    segment_available_until(
64        ast_us,
65        period_start_us,
66        ticks_to_us(end2, timescale),
67        0,
68        tsb_us,
69    )
70}
71
72/// Availability start of a Media Segment.
73#[must_use]
74pub fn segment_available_from(
75    ast_us: i128,
76    period_start_us: i128,
77    mpd_start_us: i128,
78    mpd_duration_us: i128,
79    ato_us: i128,
80) -> i128 {
81    ast_us
82        .saturating_add(period_start_us)
83        .saturating_add(mpd_start_us)
84        .saturating_add(mpd_duration_us)
85        .saturating_sub(ato_us)
86}
87
88/// Availability end of a Media Segment; `None` without a time-shift buffer
89/// depth (the segment never leaves the window).
90#[must_use]
91pub fn segment_available_until(
92    ast_us: i128,
93    period_start_us: i128,
94    mpd_start_us: i128,
95    mpd_duration_us: i128,
96    tsb_us: Option<i128>,
97) -> Option<i128> {
98    tsb_us.map(|tsb| {
99        ast_us
100            .saturating_add(period_start_us)
101            .saturating_add(mpd_start_us)
102            .saturating_add(mpd_duration_us.saturating_mul(2))
103            .saturating_add(tsb)
104    })
105}
106
107/// Availability start of the Initialization Segment.
108#[must_use]
109pub fn init_available_from(ast_us: i128, period_start_us: i128) -> i128 {
110    ast_us.saturating_add(period_start_us)
111}
112
113/// Number of complete segments of constant duration `duration_ticks` at
114/// `utc_us`: the largest `n` with `AST + PS + n * d - ATO <= utc`, computed
115/// exactly against per-segment microsecond rounding
116/// (`floor(n * d * 1e6 / timescale)`).
117#[must_use]
118pub fn complete_count_constant(
119    utc_us: i128,
120    ast_us: i128,
121    period_start_us: i128,
122    ato_us: i128,
123    duration_ticks: u64,
124    timescale: u64,
125) -> u64 {
126    if duration_ticks == 0 || timescale == 0 {
127        return 0;
128    }
129    let x = utc_us
130        .saturating_sub(ast_us)
131        .saturating_sub(period_start_us)
132        .saturating_add(ato_us);
133    let x1 = x.saturating_add(1);
134    if x1 <= 0 {
135        return 0;
136    }
137    let numerator = x1.saturating_mul(i128::from(timescale)).saturating_sub(1);
138    let denominator = i128::from(duration_ticks).saturating_mul(MICROS_PER_SECOND);
139    let n = numerator / denominator;
140    u64::try_from(n.max(0)).unwrap_or(u64::MAX)
141}