From 00c9a6fdd98a4209fb79dab046081d204e502e0f Mon Sep 17 00:00:00 2001 From: Smittix Date: Mon, 9 Feb 2026 19:25:07 +0000 Subject: [PATCH] Fix DMR audio/text deadlock: start ffmpeg per-client, not at launch Starting ffmpeg at decoder launch caused a pipe-buffer deadlock: ffmpeg stdout filled up (~64KB on Linux) before the browser connected to the audio stream, back-pressuring the entire pipeline and freezing dsd-fme stderr output (no text data, no syncs, no calls). New architecture: a mux thread always drains dsd-fme stdout to keep the pipeline flowing. ffmpeg starts lazily per-client when /dmr/audio/stream is requested (matching the listening post pattern). The mux forwards decoded audio to the active ffmpeg with silence fill during voice gaps, and discards audio when no client is connected. Co-Authored-By: Claude Opus 4.6 --- routes/dmr.py | 195 +++++++++++++++++++++++++++----------------------- 1 file changed, 107 insertions(+), 88 deletions(-) diff --git a/routes/dmr.py b/routes/dmr.py index 8878219..ec4db2d 100644 --- a/routes/dmr.py +++ b/routes/dmr.py @@ -37,14 +37,19 @@ dmr_bp = Blueprint('dmr', __name__, url_prefix='/dmr') dmr_rtl_process: Optional[subprocess.Popen] = None dmr_dsd_process: Optional[subprocess.Popen] = None -dmr_audio_process: Optional[subprocess.Popen] = None dmr_thread: Optional[threading.Thread] = None dmr_running = False -dmr_audio_running = False +dmr_has_audio = False # True when ffmpeg available and dsd outputs audio dmr_lock = threading.Lock() dmr_queue: queue.Queue = queue.Queue(maxsize=QUEUE_MAX_SIZE) dmr_active_device: Optional[int] = None +# Audio mux: the sole reader of dsd-fme stdout. Writes to an ffmpeg +# stdin when a streaming client is connected, discards otherwise. +# This prevents dsd-fme from blocking on stdout (which would also +# freeze stderr / text data output). +_active_ffmpeg_stdin: Optional[object] = None # set by stream endpoint + VALID_PROTOCOLS = ['auto', 'dmr', 'p25', 'nxdn', 'dstar', 'provoice'] # Classic dsd flags @@ -209,40 +214,40 @@ _HEARTBEAT_INTERVAL = 3.0 # seconds between heartbeats when decoder is idle _SILENCE_CHUNK = b'\x00' * 1600 -def _audio_bridge(dsd_stdout, ffmpeg_stdin): - """Bridge DSD audio output to ffmpeg, inserting silence during gaps. +def _dsd_audio_mux(dsd_stdout): + """Mux thread: sole reader of dsd-fme stdout. - Digital voice is intermittent — the decoder only outputs PCM during - active voice transmissions. Without this bridge, ffmpeg would block - waiting for its first input bytes and never write the WAV header, - causing the browser ``