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 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 20:30:14 +03:00
co-authored by Claude Sonnet 4.6
parent ebca3b4137
commit 65545982e3
2 changed files with 29 additions and 10 deletions
+26 -9
View File
@@ -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;
+3 -1
View File
@@ -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