mirror of
https://github.com/smittix/intercept.git
synced 2026-07-18 06:18:11 -07:00
Fix SSTV decoder thread lifecycle and VIS detection reliability
Three bugs preventing the live SSTV pipeline from working: 1. Race condition: self._running was set AFTER starting the decode thread, so the thread checked the flag, found it False, and exited immediately without ever processing audio. 2. Ghost running state: when the decode thread exited (e.g. rtl_fm died), self._running stayed True. The decoder reported as running but was dead, and subsequent start() calls returned without doing anything - permanently stuck until app restart. 3. VIS detection fragility: unclassifiable windows at tone transition boundaries (mixed energy from two tones) caused the state machine to reset from LEADER/BREAK states back to IDLE, dropping valid VIS headers on real signals. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -283,8 +283,10 @@ class SSTVDecoder:
|
|||||||
try:
|
try:
|
||||||
freq_hz = self._get_doppler_corrected_freq_hz()
|
freq_hz = self._get_doppler_corrected_freq_hz()
|
||||||
self._current_tuned_freq_hz = freq_hz
|
self._current_tuned_freq_hz = freq_hz
|
||||||
self._start_pipeline(freq_hz)
|
# Set _running BEFORE starting the pipeline so the decode
|
||||||
|
# thread sees it as True on its first loop iteration.
|
||||||
self._running = True
|
self._running = True
|
||||||
|
self._start_pipeline(freq_hz)
|
||||||
|
|
||||||
# Start Doppler tracking thread if enabled
|
# Start Doppler tracking thread if enabled
|
||||||
if self._doppler_enabled:
|
if self._doppler_enabled:
|
||||||
@@ -306,6 +308,7 @@ class SSTVDecoder:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self._running = False
|
||||||
logger.error(f"Failed to start SSTV decoder: {e}")
|
logger.error(f"Failed to start SSTV decoder: {e}")
|
||||||
self._emit_progress(DecodeProgress(
|
self._emit_progress(DecodeProgress(
|
||||||
status='error',
|
status='error',
|
||||||
@@ -433,7 +436,26 @@ class SSTVDecoder:
|
|||||||
break
|
break
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
logger.info("Audio decode thread stopped")
|
# Clean up if the thread exits while we thought we were running.
|
||||||
|
# This prevents a "ghost running" state where is_running is True
|
||||||
|
# but the thread has already died (e.g. rtl_fm exited).
|
||||||
|
with self._lock:
|
||||||
|
was_running = self._running
|
||||||
|
self._running = False
|
||||||
|
if was_running and self._rtl_process:
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
self._rtl_process.terminate()
|
||||||
|
self._rtl_process.wait(timeout=2)
|
||||||
|
self._rtl_process = None
|
||||||
|
|
||||||
|
if was_running:
|
||||||
|
logger.warning("Audio decode thread stopped unexpectedly")
|
||||||
|
self._emit_progress(DecodeProgress(
|
||||||
|
status='error',
|
||||||
|
message='Decode pipeline stopped unexpectedly'
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
logger.info("Audio decode thread stopped")
|
||||||
|
|
||||||
def _save_decoded_image(self, decoder: SSTVImageDecoder,
|
def _save_decoded_image(self, decoder: SSTVImageDecoder,
|
||||||
mode_name: str | None) -> None:
|
mode_name: str | None) -> None:
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ class VISDetector:
|
|||||||
# Transition to BREAK; this window counts as break window 1
|
# Transition to BREAK; this window counts as break window 1
|
||||||
self._tone_counter = 1
|
self._tone_counter = 1
|
||||||
self._state = VISState.BREAK
|
self._state = VISState.BREAK
|
||||||
|
elif tone is None:
|
||||||
|
pass # Ambiguous window at tone boundary — stay in state
|
||||||
else:
|
else:
|
||||||
self._tone_counter = 0
|
self._tone_counter = 0
|
||||||
self._state = VISState.IDLE
|
self._state = VISState.IDLE
|
||||||
@@ -207,6 +209,8 @@ class VISDetector:
|
|||||||
# Transition to LEADER_2; this window counts
|
# Transition to LEADER_2; this window counts
|
||||||
self._tone_counter = 1
|
self._tone_counter = 1
|
||||||
self._state = VISState.LEADER_2
|
self._state = VISState.LEADER_2
|
||||||
|
elif tone is None:
|
||||||
|
pass # Ambiguous window at tone boundary — stay in state
|
||||||
else:
|
else:
|
||||||
self._tone_counter = 0
|
self._tone_counter = 0
|
||||||
self._state = VISState.IDLE
|
self._state = VISState.IDLE
|
||||||
@@ -227,6 +231,8 @@ class VISDetector:
|
|||||||
self._data_bits = []
|
self._data_bits = []
|
||||||
self._bit_accumulator = []
|
self._bit_accumulator = []
|
||||||
self._state = VISState.DATA_BITS
|
self._state = VISState.DATA_BITS
|
||||||
|
elif tone is None:
|
||||||
|
pass # Ambiguous window at tone boundary — stay in state
|
||||||
else:
|
else:
|
||||||
self._tone_counter = 0
|
self._tone_counter = 0
|
||||||
self._state = VISState.IDLE
|
self._state = VISState.IDLE
|
||||||
|
|||||||
Reference in New Issue
Block a user