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