From 65545982e3b2b58324f0f7032cc517fe60da28f7 Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Thu, 21 May 2026 20:30:14 +0300 Subject: [PATCH] Fix TX mic hoarseness: pre-roll flush bug and wider stall margin Pre-roll bug: when MIC_PREROLL_SAMP is an exact multiple of OPUS_FRAME_SAMP (1920/960=2), the N<=Want branch filled the buffer but never flushed it, losing the first 40ms of audio and leaving the ring empty long enough for silence blocks to be injected into WDSP. StallCount threshold raised from 2 to 4 (~43ms) to cover OS scheduler jitter of up to 22ms, preventing false silence injection when an Opus frame arrives slightly late relative to the DSP tick. Co-Authored-By: Claude Sonnet 4.6 --- WDSPEngine.pas | 35 ++++++++++++++++++++++++++--------- WebServer.pas | 4 +++- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/WDSPEngine.pas b/WDSPEngine.pas index dbaaf6e..762b80a 100644 --- a/WDSPEngine.pas +++ b/WDSPEngine.pas @@ -557,17 +557,19 @@ procedure TTXDSPThread.Execute; // Если mic-данные накопились — обрабатывает их; если нет — обрабатывает тишину. // Это обеспечивает непрерывный поток DUC IQ к железу и TX-сигнал на спектре/водопаде. var - Avail: Integer; - PeriodMs: Integer; - PeriodUs: Integer; - AccUs: Integer; - DidBlock: Boolean; + Avail: Integer; + PeriodMs: Integer; + PeriodUs: Integer; + AccUs: Integer; + DidBlock: Boolean; + StallCount: Integer; begin // 512 samples / 48000 Hz = 10.666 ms. Do not floor this to 10 ms: // that over-produces TX IQ by ~6.7%, eventually forcing DUC queue // corrections/drops and producing periodic sidebands on a pure TUN tone. - PeriodUs := Max(1000, FEngine.FAudioBufSize * 1000000 div FEngine.FAudioRate); - AccUs := 0; + PeriodUs := Max(1000, FEngine.FAudioBufSize * 1000000 div FEngine.FAudioRate); + AccUs := 0; + StallCount := 0; while not Terminated do begin @@ -616,8 +618,23 @@ begin // лишний TX-блок тишины вместо ожидания полного 512-sample блока. Avail := (FEngine.FTXMicHead - FEngine.FTXMicTail + TX_MIC_RING) and (TX_MIC_RING - 1); - if (not DidBlock) and (Avail = 0) then - FEngine.ProcessTXBlock; + // Тишину генерируем только после 4 подряд пустых тиков (~43 ms). + // Порог 43 ms даёт 22 ms запаса относительно периода Opus-фрейма (20 ms), + // что покрывает джиттер планировщика ОС (~10 ms) и сетевой джиттер. + // При реальной потере пакета тишина всё равно инжектируется. + if not DidBlock then + begin + if Avail = 0 then + begin + Inc(StallCount); + if StallCount >= 4 then + FEngine.ProcessTXBlock; + end + else + StallCount := 0; + end + else + StallCount := 0; end; end; diff --git a/WebServer.pas b/WebServer.pas index 312d776..434e168 100644 --- a/WebServer.pas +++ b/WebServer.pas @@ -603,9 +603,11 @@ begin Want := MIC_PREROLL_SAMP - FMicPreRollPos; if N <= Want then begin - // Pre-roll ещё не набран: просто копим Move(P^, FMicPreRoll[FMicPreRollPos], N * SizeOf(Single)); Inc(FMicPreRollPos, N); + // N = Want точно заполняет pre-roll — надо слить (иначе буфер потерян) + if FMicPreRollPos = MIC_PREROLL_SAMP then + FlushMicToCallback(@FMicPreRoll[0], MIC_PREROLL_SAMP); end else begin