mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
fix RX leak after TX→RX: drop IQ during TX in non-DUP, Windows post-TX hold
В non-DUP при MOX/TUN на возврате слышен и виден на водопаде хвост
своего сигнала. Во время TX RX-IQ оседал в RXA pipeline / FFT-истории
RX-анализатора и «выстреливал» при открытии гейта.
- WDSPEngine: KeepRXDuringTX (DUP-флаг). В non-DUP TX PushIQItemToDSP
дропает пакеты на входе DSP-потока — RXA и анализатор не кормятся.
- Windows-only post-TX hold (250 мс): дропаем И входящие RX-IQ, И аудио
в окне после MOX-off, чтобы скрыть «хвост» от FPGA TX-buffer/PA
slew-down радио. На Linux код-путь не активируется (буфер OS мелкий).
- Network: ClearDUCIQQueue + IsTransmitting guard в DUC sender thread,
чтобы недопереданные TX-IQ пакеты не уходили после MOX-off.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+52
-2
@@ -216,6 +216,18 @@ type
|
||||
FTXMicSem: PRTLEvent; // сигнал: есть новые mic сэмплы
|
||||
FTXThread: TThread;
|
||||
FTXActive: Boolean;
|
||||
// DUP-флаг от UI. True — RX-анализатор и RXA-канал продолжают работать
|
||||
// во время TX (full-duplex). False — RX-IQ пакеты дропаются на входе DSP
|
||||
// потока и не кормят RXA/анализатор. Это не трогает SetChannelState
|
||||
// (мы выяснили на практике что стоп/старт RXA добавляет задержки на
|
||||
// Windows без видимого выигрыша).
|
||||
FKeepRXDuringTX: Boolean;
|
||||
// Дедлайн (GetTickCount64), до которого RX-аудио не пишется в FOnAudio.
|
||||
// Используется на Windows для маскировки «хвоста» TX leakage (radio
|
||||
// FPGA TX-buffer + PA slew-down → RX1 IQ → audio) после MOX-off.
|
||||
// На Linux выставляется в 0 (UI не зовёт BeginPostTXMute) — поведение
|
||||
// не меняется.
|
||||
FPostTXMuteUntil: QWord;
|
||||
FActiveDisplayID: Integer; // RX_DISP_ID или TX_DISP_ID
|
||||
FTXMicSource: TTXMicSource;
|
||||
FOnPullMic: TPullMicSamplesFunc;
|
||||
@@ -325,6 +337,12 @@ type
|
||||
DataOffset: Integer;
|
||||
IQPairs: Integer);
|
||||
procedure FlushRX;
|
||||
// Замьютить RX-аудио на DurationMs от текущего момента. UI зовёт это
|
||||
// на TX→RX в non-DUP только под Windows — там OS-уровневый PortAudio
|
||||
// буфер удерживает «хвост» с TX leakage из радио, который AudioOut.Clear
|
||||
// не достаёт. На Linux буфер маленький и проблемы нет, поэтому метод
|
||||
// не вызывается.
|
||||
procedure BeginPostTXMute(DurationMs: Integer);
|
||||
|
||||
// RX управление
|
||||
procedure SetMode(Mode: Integer);
|
||||
@@ -432,6 +450,10 @@ type
|
||||
property OnWaterfall: TOnWaterfallReady read FOnWaterfall write FOnWaterfall;
|
||||
property OnTXIQ: TOnTXIQReady read FOnTXIQ write FOnTXIQ;
|
||||
property TXActive: Boolean read FTXActive;
|
||||
// Включает full-duplex поведение RX-тракта: RXA не останавливается на TX,
|
||||
// RX-анализатор продолжает получать IQ. Должен ставиться UI ДО SetTXRun.
|
||||
property KeepRXDuringTX: Boolean read FKeepRXDuringTX
|
||||
write FKeepRXDuringTX;
|
||||
// Активный display ID — RX_DISP_ID или TX_DISP_ID (зависит от FTXActive).
|
||||
// SpectrumView/Waterfall должны читать пиксели именно из этого ID.
|
||||
property ActiveDisplayID: Integer read FActiveDisplayID;
|
||||
@@ -741,6 +763,8 @@ begin
|
||||
FTXMicHead := 0;
|
||||
FTXMicTail := 0;
|
||||
FTXActive := False;
|
||||
FKeepRXDuringTX := False;
|
||||
FPostTXMuteUntil := 0;
|
||||
FActiveDisplayID := RX_DISP_ID;
|
||||
FTXMicSource := txmsRadio;
|
||||
FOnPullMic := nil;
|
||||
@@ -1124,14 +1148,34 @@ begin
|
||||
if Length(FRXOut) > 0 then FillChar(FRXOut[0], Length(FRXOut) * SizeOf(Double), 0);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.BeginPostTXMute(DurationMs: Integer);
|
||||
begin
|
||||
if DurationMs <= 0 then
|
||||
FPostTXMuteUntil := 0
|
||||
else
|
||||
FPostTXMuteUntil := GetTickCount64 + QWord(DurationMs);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.PushIQItemToDSP(const Item: TIQQueueItem);
|
||||
// Вызывается из DSP потока — декодирует IQ и накапливает до FBufSize
|
||||
// Вызывается из DSP потока — декодирует IQ и накапливает до FBufSize.
|
||||
// В non-DUP TX (FTXActive and not FKeepRXDuringTX) полностью пропускаем
|
||||
// IQ-пакет: не кормим RX-анализатор и не накапливаем для fexchange0.
|
||||
// Иначе TX leakage из DDC оседает в RXA pipeline / RX_DISP_ID FFT-истории
|
||||
// и звучит/виден на водопаде первые секунды после возврата на RX.
|
||||
var
|
||||
i, Pos: Integer;
|
||||
IR, QR: LongInt;
|
||||
const
|
||||
SCALE = 1.0 / 8388608.0;
|
||||
begin
|
||||
if FTXActive and not FKeepRXDuringTX then Exit;
|
||||
// Post-TX hold: дропаем входящие RX-IQ пакеты пока активно окно мьюта.
|
||||
// Это закрывает ОБА канала сразу — и водопад/спектр (анализатор не
|
||||
// кормится), и аудио (RXA не запускается, FOnAudio не вызывается).
|
||||
// Радио продолжает дотравливать FPGA TX-buffer + PA slew-down первые
|
||||
// ~50–200мс после MOX-off; этот хвост попадает в RX1 как leak. Дропаем
|
||||
// его на входе — single-source-of-truth.
|
||||
if (FPostTXMuteUntil <> 0) and (GetTickCount64 < FPostTXMuteUntil) then Exit;
|
||||
for i := 0 to Item.IQPairs - 1 do
|
||||
begin
|
||||
Pos := i * 6;
|
||||
@@ -1217,7 +1261,13 @@ begin
|
||||
// Аудио колбэк — FAudioBufSize сэмплов @ FAudioRate (после децимации 4:1).
|
||||
// При TX RX-аудио глушим (как в Thetis/piHPSDR при не-duplex MOX),
|
||||
// иначе оператор слышит свой эфир через RX-цепь и получает feedback.
|
||||
if Assigned(FOnAudio) and not FMuted and not FTXActive then
|
||||
// Дополнительный гейт FPostTXMuteUntil закрывает «хвост» TX leakage
|
||||
// на Windows (radio FPGA TX-buffer + PA slew-down). Cheap-path: проверка
|
||||
// через unsigned-сравнение, GetTickCount64 не зовётся когда дедлайн = 0.
|
||||
if (FPostTXMuteUntil <> 0) and (GetTickCount64 >= FPostTXMuteUntil) then
|
||||
FPostTXMuteUntil := 0;
|
||||
if Assigned(FOnAudio) and not FMuted and not FTXActive
|
||||
and (FPostTXMuteUntil = 0) then
|
||||
begin
|
||||
for i := 0 to FAudioBufSize - 1 do
|
||||
begin
|
||||
|
||||
Reference in New Issue
Block a user