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