mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
fix(tci): всплески на передаче — TX-аудио просилось общим тиком 20 мс
Посреди передачи из MSHV на водопаде появлялись всплески своего сигнала. Цепочка: очередь DUC пустеет дольше подушки отправителя (DUC_FIFO_THROTTLE = 2000 отсчётов = 10.4 мс) → FIFO радио сохнет → модуляция обрывается → в эфире остаётся голая несущая на частоте гетеродина DUC, в стороне от тона ровно на звуковой сдвиг. Доказано pcap-съёмом: шесть всплесков в дампе — ровно столько, сколько видел оператор, и каждый стоит за паузой 10.3-23.5 мс, а паузы 8 мс и короче не дали ни одного. Виноват не клиент и не блокировка UI, а зернистость НАШЕГО запроса. Слой первый: PushTxChrono жил на общем тике сервера 20 мс, а просил блок клиента целиком (2048 отсчётов = 42.7 мс) — маркер выходил через два или три тика, то есть через 40 или 60 мс. Слой второй: MSHV отвечает пачками по 4-5 блоков раз в ~44 мс (STREAM_C = 4096 при 96 кГц), и мелкий квант этого не лечит — нужен запас не меньше пачки. Сделано: * квант запроса = один блок TXA (512 отсчётов движка), а не блок клиента; * свой поток-планировщик TxTickLoop с АБСОЛЮТНЫМИ дедлайнами (опоздание одного пробуждения не сдвигает сетку); общий тик маркеров больше не шлёт; * бухгалтерия Owed/InFlight в кадрах на канал, гасится по k до интерполятора; потолок долга обязан быть выше окна в полёте (TCI_TX_OWED_HEADROOM_Q), иначе связывающим становится он и подача падает до 58% реального времени при полностью исправном клиенте; * SendBinNow: маркеры пишутся в сокет напрямую под FWriteLock, минуя очередь (та выпускается лишь на пробуждении потока клиента, TCI_POLL_MS = 20 мс — вдвое больше кванта, и подача снова рвалась); * FReapLock: планировщик TX — новый поток, а правило «клиента освобождает только тик-поток» держалось на том, что им же он и пользуется; * аванс под зернистость клиента: измеряется по ПЕРИОДУ между пачками (размер пачки зависит от того, сколько мы запросили ⇒ положительная обратная связь), переживает конец передачи, умеет уменьшаться по выдержке TCI_TX_LEAD_DOWN_MS, потолок TCI_TX_LEAD_MAX_MS; * старт передачи: KickTxTick будит планировщика на фронте PTT, TxPreWarm шлёт один маркер ДО SetMOX (41 мс раздумий клиента накладываются на нашу же подготовку тракта) под гейтом «передатчик свободен и чужого источника нет», PrimeDUCIQ для источника TCI растянут до Max(6096, аванс×4) — путь микрофона радио, CW и web не затронут; * посев аванса TCI_TX_LEAD_DEF_MS = 50 мс, пока про клиента ничего не известно: обучение к первому осушению физически не успевает. Монотонные часы одного источника для всех потоков — PlatformUtils.MonotonicUs (абсолютные дедлайны не терпят часов, способных прыгнуть от NTP). На железе: опасных осушений посреди передачи НОЛЬ (было 12 за 11 с), четыре передачи из пяти вообще без единого, включая старт; прогон 15:21 чист везде, в том числе на первой передаче после подключения. Всплесков оператор больше не видит. Приборы: TxTrace.pas (EWSDR_TXTRACE=1) и стенд test/hpsdr — кольцевой tcpdump capture.sh, разбор дампа pcap_tx_scan.py, разбор трассы txtrace_scan.py. Стенд test/tci: часть F «Пейсинг TX», 260 проверок, провалов нет; живой клиент с рампой, RTT и потерями — test/tci/tx_chrono_bench.py. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014gmVQnna1i4EbSZ2VGm6KD
This commit is contained in:
+19
-2
@@ -51,7 +51,7 @@ uses
|
||||
HPSDRProtocol, HPSDRNetwork, RadioBackend, PlutoBackend, IIOBindings,
|
||||
WDSPEngine, AudioOutput, AudioInput, BeaconDecoder, BeaconFEC, DMRDecoder,
|
||||
Settings, ChannelStore, FMRepeater, BoardUtils, DeviceStore, CWMorse, CWKeyer,
|
||||
CWDecoder;
|
||||
CWDecoder, TxTrace;
|
||||
|
||||
// Панадаптеры (этап 3): потолок MAX_PANS живёт в WDSPEngine (общий для
|
||||
// движка/контроллера/UI). Фактический лимит бэкенда — Caps.MaxPans (Pluto=1).
|
||||
@@ -446,6 +446,7 @@ type
|
||||
FWebMicActive: Boolean; // True если текущий TX идёт через txmsWeb
|
||||
FTCIMicRequested: Boolean; // TCI-клиент попросил TRX:…,tci
|
||||
FTCIMicActive: Boolean; // True если текущий TX модулируется из TCI
|
||||
FTCITxLeadFrames: Integer; // аванс подачи TCI, отсчётов 48 кГц (ставит адаптер)
|
||||
FSendAudioToRadio: Boolean;
|
||||
FAudioOutDevName: string;
|
||||
FAudioInDevName: string;
|
||||
@@ -1296,6 +1297,10 @@ type
|
||||
// Клиент TCI просит брать модуляцию из своего аудиопотока (TRX:0,true,tci,
|
||||
// §4.2). Ставит адаптер, читает SetMOX при выборе источника.
|
||||
property TCIMicRequested: Boolean read FTCIMicRequested write FTCIMicRequested;
|
||||
// Сколько звука клиент TCI держит в авансе (отсчётов 48 кГц). Ставит
|
||||
// адаптер по измеренной зернистости клиента; SetMOX по этому числу
|
||||
// растягивает нулевой pre-roll DUC.
|
||||
property TCITxLeadFrames: Integer read FTCITxLeadFrames write FTCITxLeadFrames;
|
||||
// Идёт ли текущая передача с модуляцией из TCI (адаптеру — гнать ли
|
||||
// маркеры TX_CHRONO).
|
||||
property TCIMicActive: Boolean read FTCIMicActive;
|
||||
@@ -6298,6 +6303,9 @@ begin
|
||||
// прекратить — ключ обязан отпуститься немедленно.
|
||||
if (not On_) and CWXBusy then CWXAbort;
|
||||
FTransmitting := On_;
|
||||
if TraceOn and (On_ <> WasTransmitting) then
|
||||
if Assigned(FDSPEngine) then TR(trkPtt, Ord(On_), Ord(FDSPEngine.TXMicSource))
|
||||
else TR(trkPtt, Ord(On_), -1);
|
||||
// 2TON: прямое снятие MOX (кнопкой/CAT/web) гасит и двухтональник.
|
||||
if (not On_) and FTwoTone then
|
||||
begin
|
||||
@@ -6331,7 +6339,15 @@ begin
|
||||
// на bfo-глитч тона (устранён коммитом f2d0519). Снижено до 2 блоков ради
|
||||
// латентности старта TX (~10мс). Если под нагрузкой DUP / быстром пере-PTT
|
||||
// вернётся щелчок РОВНО на старте — это реальный underrun, вернуть 8144.
|
||||
FNetwork.PrimeDUCIQ(6096);
|
||||
// ★При модуляции из TCI 6096 пар (31.75 мс) не хватает: клиент отвечает на
|
||||
// первый маркер только через ~33 мс, плюс наша задержка на фронте — радио
|
||||
// остаётся без IQ и даёт всплеск на старте КАЖДОЙ передачи. Растягиваем
|
||||
// pre-roll до измеренного аванса клиента (в парах @192 кГц = кадры × 4).
|
||||
// Путь микрофона радио, CW и web этого не касается: там источник свой.
|
||||
if FTCIMicRequested and (FTCITxLeadFrames > 0) then
|
||||
FNetwork.PrimeDUCIQ(Max(6096, FTCITxLeadFrames * 4))
|
||||
else
|
||||
FNetwork.PrimeDUCIQ(6096);
|
||||
end;
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
begin
|
||||
@@ -6430,6 +6446,7 @@ begin
|
||||
// (оператор держит T/R руками) — вооружение обязано пойти за этой кнопкой.
|
||||
SyncCWKeyer;
|
||||
Changed(rfTransmitting);
|
||||
if TraceOn and WasTransmitting and (not On_) then TraceDump('tx');
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetTune(On_: Boolean);
|
||||
|
||||
Reference in New Issue
Block a user