From 1ee64efc8110a337c7687d6284f0eb55d7b3a379 Mon Sep 17 00:00:00 2001 From: Smittix Date: Sat, 7 Feb 2026 14:55:22 +0000 Subject: [PATCH] Fix PD120 SSTV decode hang and false leader tone detection Fix infinite CPU spin in PD120 decoding caused by a 1-sample rounding mismatch between line_samples (24407) and the sum of sub-component samples (24408). The feed() while loop would re-enter _decode_line() endlessly when the buffer was too short by 1 sample. Added a stall guard that breaks the loop when no progress is made. Fix false "leader tone detected" in the signal monitor by requiring the detected tone to dominate the other tone by 2x, matching the approach already used by the VIS detector. Co-Authored-By: Claude Opus 4.6 --- utils/sstv/image_decoder.py | 9 ++++++++- utils/sstv/sstv_decoder.py | 9 +++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/utils/sstv/image_decoder.py b/utils/sstv/image_decoder.py index 13a9eda..c7f2391 100644 --- a/utils/sstv/image_decoder.py +++ b/utils/sstv/image_decoder.py @@ -138,9 +138,16 @@ class SSTVImageDecoder: self._buffer = np.concatenate([self._buffer, samples]) - # Process complete lines + # Process complete lines. + # Guard against stalls: if _decode_line() cannot consume data + # (e.g. sub-component samples exceed line_samples due to rounding), + # break out and wait for more audio. while not self._complete and len(self._buffer) >= self._line_samples: + prev_line = self._current_line + prev_len = len(self._buffer) self._decode_line() + if self._current_line == prev_line and len(self._buffer) == prev_len: + break # No progress — need more data # Prevent unbounded buffer growth - keep at most 2 lines worth max_buffer = self._line_samples * 2 diff --git a/utils/sstv/sstv_decoder.py b/utils/sstv/sstv_decoder.py index 9daa9ac..36834bc 100644 --- a/utils/sstv/sstv_decoder.py +++ b/utils/sstv/sstv_decoder.py @@ -487,9 +487,14 @@ class SSTVDecoder: sync_energy = goertzel_mag(samples, 1200.0, SAMPLE_RATE) noise_floor = max(rms * 0.5, 0.001) - if leader_energy > noise_floor * 5: + # Require the tone to both exceed the noise floor AND + # dominate the other tone by 2x to avoid false positives + # from broadband noise. + if (leader_energy > noise_floor * 5 + and leader_energy > sync_energy * 2): sstv_tone = 'leader' - elif sync_energy > noise_floor * 5: + elif (sync_energy > noise_floor * 5 + and sync_energy > leader_energy * 2): sstv_tone = 'sync' elif signal_level > 10: sstv_tone = 'noise'