ka9q-python 3.19.0__tar.gz → 3.20.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/CHANGELOG.md +20 -0
- {ka9q_python-3.19.0/ka9q_python.egg-info → ka9q_python-3.20.0}/PKG-INFO +1 -1
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/__init__.py +2 -1
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/slot_clock.py +54 -2
- {ka9q_python-3.19.0 → ka9q_python-3.20.0/ka9q_python.egg-info}/PKG-INFO +1 -1
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/pyproject.toml +1 -1
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_slot_clock.py +45 -1
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/LICENSE +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/MANIFEST.in +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/README.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/API_REFERENCE.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/ARCHITECTURE.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/CLI_GUIDE.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/GETTING_STARTED.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/INSTALLATION.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/MULTI_STREAM.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/RECIPES.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/REQUIREMENTS.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/RTP_TIMING_SUPPORT.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/SECURITY.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/TESTING_GUIDE.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/docs/TUI_GUIDE.md +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/advanced_features_demo.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/channel_cleanup_example.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/codar_oceanography.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/diagnostics/diagnose_packets.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/diagnostics/repro_utc_bug.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/discover_example.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/grape_integration_example.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/hf_band_scanner.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/multi_stream_smoke.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/rtp_recorder_example.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/simple_am_radio.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/spectrum_example.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/stream_example.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/superdarn_recorder.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/test_channel_operations.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/test_improvements.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/test_timing_fields.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/tune.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/examples/tune_example.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/_multicast.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/addressing.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/cli.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/compat.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/control.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/discovery.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/exceptions.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/managed_stream.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/monitor.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/multi_stream.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/pps_calibrator.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/resequencer.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/rtp_recorder.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/spectrum_stream.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/status.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/status_listener.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/stream.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/stream_quality.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/tui.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/types.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q/utils.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q_python.egg-info/SOURCES.txt +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q_python.egg-info/dependency_links.txt +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q_python.egg-info/entry_points.txt +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q_python.egg-info/requires.txt +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q_python.egg-info/top_level.txt +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/ka9q_radio_compat +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/scripts/check_upstream_drift.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/scripts/sync_types.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/setup.cfg +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/__init__.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/conftest.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_addressing.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_channel_verification.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_client_id_destination.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_create_split_encoding.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_decode_description.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_decode_functions.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_encode_functions.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_encode_socket.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_ensure_channel_encoding.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_filter_edges.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_integration.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_iq_20khz_f32.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_lifetime.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_listen_multicast.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_managed_stream_recovery.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_monitor.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_multicast_helpers.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_multihomed.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_multistream_gap_storm.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_multistream_prune.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_native_discovery.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_parse_rtp_samples_iq.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_performance_fixes.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_protocol_compat.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_remove_channel.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_rtp_recorder.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_security_features.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_spectrum.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_ssrc_dest_unit.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_ssrc_encoding_unit.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_ssrc_radiod_host_unit.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_status_decoder.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_status_listener.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_ttl_warning.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_tune.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_tune_cli.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_tune_debug.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_tune_live.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_tune_method.py +0 -0
- {ka9q_python-3.19.0 → ka9q_python-3.20.0}/tests/test_upstream_drift.py +0 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.20.0] - 2026-06-29
|
|
4
|
+
|
|
5
|
+
### Added — `SlotClock` ambiguous-unwrap guard (fail loud, never silent)
|
|
6
|
+
|
|
7
|
+
- **`SlotClock.offset_of_rtp` now raises `SlotClockDesyncError`** when a wrapped
|
|
8
|
+
RTP timestamp is more than `2**30` samples (half the signed-32 window, ~24.8 h
|
|
9
|
+
@ 12 kHz / ~4.7 h @ 64 kHz IQ) from the unwrap high-water in either direction.
|
|
10
|
+
The high-water unwrap (3.18.0) keeps continuous streams correct past the
|
|
11
|
+
`2**31` window, but it only disambiguates while each step stays within
|
|
12
|
+
`±2**31` of the high-water. A *single* forward jump `>= 2**31` samples (a
|
|
13
|
+
`>49.7 h` stall-then-resume, a missed re-anchor, or a discontinuous timestamp)
|
|
14
|
+
aliased into a backward step — `advance()` then harvested **0 slots forever on
|
|
15
|
+
live RF**, the exact silent failure `SlotClock` exists to prevent.
|
|
16
|
+
- **`SlotClock.advance` catches the desync, logs an ERROR, drops the anchor, and
|
|
17
|
+
returns `[]`**, so the caller's normal `if not clk.anchored: clk.anchor(...)`
|
|
18
|
+
path re-establishes the grid on a fresh RTP reference instead of stalling. The
|
|
19
|
+
`2**30` margin never false-fires: continuous advancing steps a few hundred
|
|
20
|
+
samples per call.
|
|
21
|
+
- **`SlotClockDesyncError` is exported** from the package.
|
|
22
|
+
|
|
3
23
|
## [3.19.0] - 2026-06-28
|
|
4
24
|
|
|
5
25
|
### Removed / Changed — simplify how we look at radiod (defer to RTP)
|
|
@@ -115,7 +115,7 @@ from .managed_stream import (
|
|
|
115
115
|
StreamState,
|
|
116
116
|
)
|
|
117
117
|
from .multi_stream import MultiStream
|
|
118
|
-
from .slot_clock import SlotClock, Slot, rtp_diff
|
|
118
|
+
from .slot_clock import SlotClock, Slot, SlotClockDesyncError, rtp_diff
|
|
119
119
|
from .spectrum_stream import SpectrumStream
|
|
120
120
|
from .status_listener import StatusListener, StatusListenerStats
|
|
121
121
|
|
|
@@ -165,6 +165,7 @@ __all__ = [
|
|
|
165
165
|
# Slot timing (epoch-aligned, RTP-referenced)
|
|
166
166
|
'SlotClock',
|
|
167
167
|
'Slot',
|
|
168
|
+
'SlotClockDesyncError',
|
|
168
169
|
'rtp_diff',
|
|
169
170
|
|
|
170
171
|
# Stream API (sample-oriented)
|
|
@@ -68,6 +68,28 @@ def rtp_diff(a: int, b: int) -> int:
|
|
|
68
68
|
return d
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
# Largest single unwrap step (in samples) we trust from the high-water. The
|
|
72
|
+
# signed-32 unwrap is only unambiguous within +/-2**31 of the high-water; we
|
|
73
|
+
# refuse anything past the half-window (2**30 ~= 24.8 h @ 12 kHz, ~4.7 h @
|
|
74
|
+
# 64 kHz IQ) so the unwrap fails LOUD with a wide margin instead of silently
|
|
75
|
+
# aliasing a huge forward jump into a backward step (-> advance() harvests 0
|
|
76
|
+
# slots on live RF: the exact silent failure SlotClock exists to prevent).
|
|
77
|
+
# Continuous advancing steps by a few hundred samples per call, so this never
|
|
78
|
+
# false-fires in normal operation — only a >24.8 h stall, a missed re-anchor,
|
|
79
|
+
# or a bogus/discontinuous timestamp reaches it.
|
|
80
|
+
_SAFE_UNWRAP_SAMPLES = 2 ** 30
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class SlotClockDesyncError(RuntimeError):
|
|
84
|
+
"""A wrapped RTP timestamp is too far from the unwrap high-water to be
|
|
85
|
+
disambiguated (a >24.8 h gap, a missed re-anchor, or a discontinuous/bogus
|
|
86
|
+
timestamp). The grid can no longer be trusted from this timestamp alone;
|
|
87
|
+
the caller must re-anchor against a fresh RTP reference. ``advance``
|
|
88
|
+
catches this, drops the anchor, and returns no slots, so the caller's
|
|
89
|
+
normal ``if not clk.anchored: clk.anchor(...)`` path re-establishes the
|
|
90
|
+
grid rather than silently stalling at zero decodes."""
|
|
91
|
+
|
|
92
|
+
|
|
71
93
|
@dataclass(frozen=True)
|
|
72
94
|
class Slot:
|
|
73
95
|
"""One completed, epoch-aligned slot.
|
|
@@ -189,10 +211,26 @@ class SlotClock:
|
|
|
189
211
|
within the last slot), which are always within one signed-32 window of
|
|
190
212
|
the high-water — so the unwrap is unambiguous. Advances the high-water
|
|
191
213
|
when a query is ahead of it; never rewinds it on a stale/past query.
|
|
214
|
+
|
|
215
|
+
Raises ``SlotClockDesyncError`` if the timestamp is more than
|
|
216
|
+
``_SAFE_UNWRAP_SAMPLES`` (half the signed-32 window) from the
|
|
217
|
+
high-water in either direction: at that distance a wrapped 32-bit
|
|
218
|
+
value can no longer be told apart from a ~2**32-aliased one, so any
|
|
219
|
+
unwrap would be a guess. This converts the old silent alias (huge
|
|
220
|
+
forward jump read as a backward step -> 0 slots forever) into a loud,
|
|
221
|
+
recoverable signal.
|
|
192
222
|
"""
|
|
193
223
|
assert self._anchor_rtp is not None
|
|
194
224
|
ref_rtp = (self._anchor_rtp + self._latest_off) & 0xFFFFFFFF
|
|
195
|
-
|
|
225
|
+
delta = rtp_diff(rtp_timestamp, ref_rtp)
|
|
226
|
+
if abs(delta) > _SAFE_UNWRAP_SAMPLES:
|
|
227
|
+
raise SlotClockDesyncError(
|
|
228
|
+
f"rtp={rtp_timestamp} is {delta} samples from the unwrap "
|
|
229
|
+
f"high-water (offset {self._latest_off}); |delta| exceeds the "
|
|
230
|
+
f"safe window {_SAFE_UNWRAP_SAMPLES} — cannot unwrap "
|
|
231
|
+
f"unambiguously, re-anchor required"
|
|
232
|
+
)
|
|
233
|
+
off = self._latest_off + delta
|
|
196
234
|
if off > self._latest_off:
|
|
197
235
|
self._latest_off = off
|
|
198
236
|
return off
|
|
@@ -209,7 +247,21 @@ class SlotClock:
|
|
|
209
247
|
"""
|
|
210
248
|
if self._anchor_rtp is None or self._next_boundary_off is None:
|
|
211
249
|
return []
|
|
212
|
-
|
|
250
|
+
try:
|
|
251
|
+
latest_off = self.offset_of_rtp(latest_rtp_timestamp)
|
|
252
|
+
except SlotClockDesyncError as exc:
|
|
253
|
+
# A wrapped timestamp this far from the high-water can't be
|
|
254
|
+
# unwrapped safely. Fail LOUD and drop the anchor so the caller's
|
|
255
|
+
# ``if not anchored: anchor()`` path re-establishes the grid on a
|
|
256
|
+
# fresh RTP reference — instead of silently harvesting 0 slots on
|
|
257
|
+
# live RF (the failure mode this whole class exists to prevent).
|
|
258
|
+
logger.error(
|
|
259
|
+
"SlotClock(cadence=%.3fs, sr=%d): %s; dropping anchor to force "
|
|
260
|
+
"a clean re-anchor (was at slot index %d)",
|
|
261
|
+
self.cadence_sec, self.sample_rate, exc, self._next_index,
|
|
262
|
+
)
|
|
263
|
+
self.reset()
|
|
264
|
+
return []
|
|
213
265
|
out: List[Slot] = []
|
|
214
266
|
while latest_off >= (
|
|
215
267
|
self._next_boundary_off + self.cadence_samples + self.settle_samples
|
|
@@ -3,7 +3,13 @@ import math
|
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
|
-
from ka9q.slot_clock import
|
|
6
|
+
from ka9q.slot_clock import (
|
|
7
|
+
SlotClock,
|
|
8
|
+
Slot,
|
|
9
|
+
SlotClockDesyncError,
|
|
10
|
+
rtp_diff,
|
|
11
|
+
_SAFE_UNWRAP_SAMPLES,
|
|
12
|
+
)
|
|
7
13
|
|
|
8
14
|
|
|
9
15
|
SR = 12000
|
|
@@ -136,6 +142,44 @@ def test_offset_of_rtp_unwraps_past_window():
|
|
|
136
142
|
assert off > 2 ** 31 # we genuinely crossed the signed-32 boundary
|
|
137
143
|
|
|
138
144
|
|
|
145
|
+
def test_safe_unwrap_limit_allows_full_half_window_step():
|
|
146
|
+
"""A single step of exactly the safe limit (half the signed-32 window) is
|
|
147
|
+
still unambiguous and must be accepted — the guard rejects only *past* it."""
|
|
148
|
+
clk = SlotClock(15.0, SR)
|
|
149
|
+
clk.anchor(rtp_timestamp=0, utc=900_000_000.0)
|
|
150
|
+
off = _SAFE_UNWRAP_SAMPLES
|
|
151
|
+
assert clk.offset_of_rtp(off & 0xFFFFFFFF) == off
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_offset_of_rtp_raises_on_ambiguous_jump():
|
|
155
|
+
"""A single unwrap step past the safe half-window can alias a huge forward
|
|
156
|
+
jump into a backward step; offset_of_rtp must raise rather than guess."""
|
|
157
|
+
clk = SlotClock(15.0, SR)
|
|
158
|
+
clk.anchor(rtp_timestamp=0, utc=900_000_000.0)
|
|
159
|
+
with pytest.raises(SlotClockDesyncError):
|
|
160
|
+
clk.offset_of_rtp((_SAFE_UNWRAP_SAMPLES + 1) & 0xFFFFFFFF)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_advance_big_jump_fails_loud_and_drops_anchor(caplog):
|
|
164
|
+
"""Regression: a single advance() forward jump > 2**31 from the anchor used
|
|
165
|
+
to alias to a backward step and silently harvest 0 slots forever (live RF,
|
|
166
|
+
zero decodes). The guard must instead log loudly, drop the anchor, and
|
|
167
|
+
return [] so the caller re-anchors — turning a silent stall into recovery."""
|
|
168
|
+
clk = SlotClock(15.0, SR, settle_sec=1.5)
|
|
169
|
+
clk.anchor(rtp_timestamp=0, utc=900_000_000.0)
|
|
170
|
+
# 50 h in samples: 2,160,000,000 > 2**31 (2,147,483,648) in ONE call.
|
|
171
|
+
big = 50 * 3600 * SR & 0xFFFFFFFF
|
|
172
|
+
with caplog.at_level("ERROR"):
|
|
173
|
+
slots = clk.advance(big)
|
|
174
|
+
assert slots == [] # no garbage slots
|
|
175
|
+
assert not clk.anchored # anchor dropped -> caller must re-anchor
|
|
176
|
+
assert any("SlotClock" in r.message for r in caplog.records)
|
|
177
|
+
# and recovery works: re-anchor near the real leading edge, harvest normally
|
|
178
|
+
clk.anchor(rtp_timestamp=big, utc=900_180_000.0) # multiple of 15
|
|
179
|
+
out = clk.advance((big + 180000 + 24000 + 1) & 0xFFFFFFFF)
|
|
180
|
+
assert len(out) == 1 and out[0].index == 0
|
|
181
|
+
|
|
182
|
+
|
|
139
183
|
def test_settle_delays_completion():
|
|
140
184
|
clk = SlotClock(15.0, SR, settle_sec=2.0)
|
|
141
185
|
clk.anchor(rtp_timestamp=0, utc=900_000_000.0) # 900000000 % 15 == 0 -> boundary0 at offset 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|