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