Revert "fix TX clicks в TUN: 1 ProcessTXBlock на тик вместо 2-3"

This reverts commit e813ada6b2.
This commit is contained in:
2026-05-07 17:32:27 +03:00
parent e813ada6b2
commit c59c6c8bcc
+34 -29
View File
@@ -527,52 +527,56 @@ begin
end; end;
procedure TTXDSPThread.Execute; procedure TTXDSPThread.Execute;
// Тикает по таймеру с периодом одного mic-буфера (FAudioBufSize/FAudioRate). // Аналог tx_thread в piHPSDR/transmitter.c:
// Семафор НЕ используется как сигнал «есть mic-данные» — он бы давал // Тикает с периодом одного mic-буфера (FAudioBufSize/FAudioRate).
// немедленные возвраты из WaitFor (auto-reset event) и приводил к // Если mic-данные накопились — обрабатывает их; если нет — обрабатывает тишину.
// over-production IQ-потока: за один реальный период радио мы шлём // Это обеспечивает непрерывный поток DUC IQ к железу и TX-сигнал на спектре/водопаде.
// 2–3 блока вместо одного. Это и было слышно в TUN как щелчки.
// Семафор оставлен только для будилки на shutdown (Terminate → Set).
//
// За один тик:
// - Pull mic из PortAudio (sound-card path).
// - Inner loop: процессим все накопившиеся полные блоки (обычно 1).
// - Если ничего реального не процессили — гоним один блок тишины,
// чтобы DUC IQ поток не прерывался и FIFO радио не голодал.
var var
Avail: Integer; Avail: Integer;
PeriodMs: Integer; PeriodMs: Integer;
ProcessedAny: Boolean;
begin begin
// 512 сэмплов / 48000 Гц = ~10.67 мс → округляем вниз до 10 мс
PeriodMs := Max(1, FEngine.FAudioBufSize * 1000 div FEngine.FAudioRate); PeriodMs := Max(1, FEngine.FAudioBufSize * 1000 div FEngine.FAudioRate);
while not Terminated do while not Terminated do
begin begin
// Ждём ровно PeriodMs. Сем сигналится только при Terminate → быстрый shutdown. // Ждём сигнала от mic-данных ИЛИ истечения периода —
// в piHPSDR timing идёт от receive thread; здесь — от таймаута
RTLEventWaitFor(FEngine.FTXMicSem, PeriodMs); RTLEventWaitFor(FEngine.FTXMicSem, PeriodMs);
if Terminated then Break; if Terminated then Break;
if not FEngine.FTXActive then Continue; if not FEngine.FTXActive then Continue;
// Если выбран sound-card mic — тянем сэмплы из внешнего источника
// (PortAudio через FOnPullMic в MainForm) ПЕРЕД проверкой ring buffer.
// Callback сам пушит сэмплы в FTXMicRing через PushTXMicSampleD.
if (FEngine.FTXMicSource = txmsSoundCard) and if (FEngine.FTXMicSource = txmsSoundCard) and
Assigned(FEngine.FOnPullMic) then Assigned(FEngine.FOnPullMic) then
FEngine.FOnPullMic(FEngine.FAudioBufSize * 2); FEngine.FOnPullMic(FEngine.FAudioBufSize * 2);
ProcessedAny := False; // Обрабатываем накопившиеся блоки с реальными mic-данными
repeat repeat
Avail := (FEngine.FTXMicHead - FEngine.FTXMicTail + TX_MIC_RING) Avail := (FEngine.FTXMicHead - FEngine.FTXMicTail + TX_MIC_RING)
and (TX_MIC_RING - 1); and (TX_MIC_RING - 1);
if (Avail >= FEngine.FAudioBufSize) and not Terminated then if (Avail >= FEngine.FAudioBufSize) and not Terminated then
begin FEngine.ProcessTXBlock
FEngine.ProcessTXBlock; else
ProcessedAny := True;
end else
Break; Break;
until False; until False;
if Terminated then Break; if Terminated then Break;
if not ProcessedAny then // Если mic-данных не было — обрабатываем один блок тишины, чтобы
FEngine.ProcessTXBlock; // DUC IQ поток не прерывался и спектр/водопад обновлялись.
// НО для sound-card mic неполный блок (0 < Avail < AudioBufSize) нельзя
// подменять тишиной: PortAudio часто отдаёт 128-сэмпловые callback'и,
// и если в кольце уже есть голосовой хвост — он застрянет, а в эфир
// уйдёт тишина. Слышно как чередование «голос/тишина» = треск ≈50–100 Гц.
// В этом случае ждём следующий тик, хвост накопится до полного блока.
Avail := (FEngine.FTXMicHead - FEngine.FTXMicTail + TX_MIC_RING)
and (TX_MIC_RING - 1);
if (Avail = 0) or (FEngine.FTXMicSource <> txmsSoundCard) then
if Avail < FEngine.FAudioBufSize then
FEngine.ProcessTXBlock;
end; end;
end; end;
@@ -1610,10 +1614,8 @@ begin
end; end;
// если буфер полон — сэмпл отбрасывается (overrun) // если буфер полон — сэмпл отбрасывается (overrun)
end; end;
// Семафор намеренно не сигналим — TX-поток тикает по таймеру
// (PeriodMs = AudioBufSize/AudioRate). Сигнал давал бы немедленный RTLEventSetEvent(FTXMicSem); // будим TX поток
// возврат из WaitFor → лишний ProcessTXBlock → over-production
// IQ-потока относительно темпа радио → щелчки/FIFO-overrun.
end; end;
procedure TWDSPEngine.PushTXMicSampleD(const S: Double); procedure TWDSPEngine.PushTXMicSampleD(const S: Double);
@@ -1625,7 +1627,8 @@ begin
FTXMicRing[FTXMicHead] := S; FTXMicRing[FTXMicHead] := S;
FTXMicHead := newHead; FTXMicHead := newHead;
end; end;
// overrun → дроп, как в HW path. Сем не сигналим — см. PushTXMicSamples16. // overrun → дроп, как в HW path
RTLEventSetEvent(FTXMicSem);
end; end;
procedure TWDSPEngine.PushTXMicSamplesD(const Src: array of Double; N: Integer); procedure TWDSPEngine.PushTXMicSamplesD(const Src: array of Double; N: Integer);
@@ -1646,7 +1649,9 @@ begin
end; end;
// overrun → дроп // overrun → дроп
end; end;
// Сем не сигналим — см. PushTXMicSamples16.
if cnt > 0 then
RTLEventSetEvent(FTXMicSem);
end; end;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------