From c59c6c8bcc47086d84f63e6dfbf9f232772637fb Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Thu, 7 May 2026 17:32:27 +0300 Subject: [PATCH] =?UTF-8?q?Revert=20"fix=20TX=20clicks=20=D0=B2=20TUN:=201?= =?UTF-8?q?=20ProcessTXBlock=20=D0=BD=D0=B0=20=D1=82=D0=B8=D0=BA=20=D0=B2?= =?UTF-8?q?=D0=BC=D0=B5=D1=81=D1=82=D0=BE=202-3"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit e813ada6b258ba3ff9337b1aee83b2d6e9e9ba95. --- WDSPEngine.pas | 63 +++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/WDSPEngine.pas b/WDSPEngine.pas index ac6d7b2..9edfe60 100644 --- a/WDSPEngine.pas +++ b/WDSPEngine.pas @@ -527,52 +527,56 @@ begin end; procedure TTXDSPThread.Execute; -// Тикает по таймеру с периодом одного mic-буфера (FAudioBufSize/FAudioRate). -// Семафор НЕ используется как сигнал «есть mic-данные» — он бы давал -// немедленные возвраты из WaitFor (auto-reset event) и приводил к -// over-production IQ-потока: за один реальный период радио мы шлём -// 2–3 блока вместо одного. Это и было слышно в TUN как щелчки. -// Семафор оставлен только для будилки на shutdown (Terminate → Set). -// -// За один тик: -// - Pull mic из PortAudio (sound-card path). -// - Inner loop: процессим все накопившиеся полные блоки (обычно 1). -// - Если ничего реального не процессили — гоним один блок тишины, -// чтобы DUC IQ поток не прерывался и FIFO радио не голодал. +// Аналог tx_thread в piHPSDR/transmitter.c: +// Тикает с периодом одного mic-буфера (FAudioBufSize/FAudioRate). +// Если mic-данные накопились — обрабатывает их; если нет — обрабатывает тишину. +// Это обеспечивает непрерывный поток DUC IQ к железу и TX-сигнал на спектре/водопаде. var - Avail: Integer; - PeriodMs: Integer; - ProcessedAny: Boolean; + Avail: Integer; + PeriodMs: Integer; begin + // 512 сэмплов / 48000 Гц = ~10.67 мс → округляем вниз до 10 мс PeriodMs := Max(1, FEngine.FAudioBufSize * 1000 div FEngine.FAudioRate); while not Terminated do begin - // Ждём ровно PeriodMs. Сем сигналится только при Terminate → быстрый shutdown. + // Ждём сигнала от mic-данных ИЛИ истечения периода — + // в piHPSDR timing идёт от receive thread; здесь — от таймаута RTLEventWaitFor(FEngine.FTXMicSem, PeriodMs); if Terminated then Break; if not FEngine.FTXActive then Continue; + // Если выбран sound-card mic — тянем сэмплы из внешнего источника + // (PortAudio через FOnPullMic в MainForm) ПЕРЕД проверкой ring buffer. + // Callback сам пушит сэмплы в FTXMicRing через PushTXMicSampleD. if (FEngine.FTXMicSource = txmsSoundCard) and Assigned(FEngine.FOnPullMic) then FEngine.FOnPullMic(FEngine.FAudioBufSize * 2); - ProcessedAny := False; + // Обрабатываем накопившиеся блоки с реальными mic-данными repeat Avail := (FEngine.FTXMicHead - FEngine.FTXMicTail + TX_MIC_RING) and (TX_MIC_RING - 1); if (Avail >= FEngine.FAudioBufSize) and not Terminated then - begin - FEngine.ProcessTXBlock; - ProcessedAny := True; - end else + FEngine.ProcessTXBlock + else Break; until False; if Terminated then Break; - if not ProcessedAny then - FEngine.ProcessTXBlock; + // Если mic-данных не было — обрабатываем один блок тишины, чтобы + // 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; @@ -1610,10 +1614,8 @@ begin end; // если буфер полон — сэмпл отбрасывается (overrun) end; - // Семафор намеренно не сигналим — TX-поток тикает по таймеру - // (PeriodMs = AudioBufSize/AudioRate). Сигнал давал бы немедленный - // возврат из WaitFor → лишний ProcessTXBlock → over-production - // IQ-потока относительно темпа радио → щелчки/FIFO-overrun. + + RTLEventSetEvent(FTXMicSem); // будим TX поток end; procedure TWDSPEngine.PushTXMicSampleD(const S: Double); @@ -1625,7 +1627,8 @@ begin FTXMicRing[FTXMicHead] := S; FTXMicHead := newHead; end; - // overrun → дроп, как в HW path. Сем не сигналим — см. PushTXMicSamples16. + // overrun → дроп, как в HW path + RTLEventSetEvent(FTXMicSem); end; procedure TWDSPEngine.PushTXMicSamplesD(const Src: array of Double; N: Integer); @@ -1646,7 +1649,9 @@ begin end; // overrun → дроп end; - // Сем не сигналим — см. PushTXMicSamples16. + + if cnt > 0 then + RTLEventSetEvent(FTXMicSem); end; // ---------------------------------------------------------------------------