mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +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:
@@ -27,6 +27,11 @@ function GetScreenScale: Integer;
|
||||
{$IFNDEF HEADLESS}
|
||||
function GetControlScale(AControl: TControl): Integer;
|
||||
{$ENDIF}
|
||||
// Монотонные микросекунды одного источника для всех потоков. Нужны там, где
|
||||
// интервалы считает не человек, а код: пейсинг запросов TX-аудио по TCI ведёт
|
||||
// абсолютные дедлайны, и часы, способные прыгнуть от NTP, там не годятся.
|
||||
function MonotonicUs: Int64;
|
||||
|
||||
// Возвращает каталог для хранения конфигурации (с завершающим разделителем).
|
||||
// macOS: ~/Library/Application Support/ewsdr/
|
||||
// Linux: ~/.config/ewsdr/
|
||||
@@ -37,8 +42,44 @@ implementation
|
||||
|
||||
uses
|
||||
SysUtils {$IFNDEF HEADLESS}, Forms{$ENDIF}
|
||||
{$IFDEF WINDOWS}, Windows{$ENDIF}
|
||||
{$IFDEF LINUX}, Linux, UnixType{$ENDIF}
|
||||
{$IF DEFINED(UNIX) AND NOT DEFINED(LINUX)}, BaseUnix{$ENDIF}
|
||||
{$IF DEFINED(DARWIN) AND NOT DEFINED(HEADLESS)}, MacScale{$ENDIF};
|
||||
|
||||
{$IFDEF WINDOWS}
|
||||
var
|
||||
QPCFreq: Int64 = 0;
|
||||
{$ENDIF}
|
||||
|
||||
function MonotonicUs: Int64;
|
||||
{$IFDEF WINDOWS}
|
||||
var
|
||||
V: Int64;
|
||||
begin
|
||||
if QPCFreq <= 0 then
|
||||
if not QueryPerformanceFrequency(QPCFreq) then Exit(0);
|
||||
QueryPerformanceCounter(V);
|
||||
Result := (V * 1000000) div QPCFreq;
|
||||
end;
|
||||
{$ELSE}
|
||||
{$IFDEF LINUX}
|
||||
var
|
||||
TS: TTimeSpec;
|
||||
begin
|
||||
if clock_gettime(CLOCK_MONOTONIC, @TS) <> 0 then Exit(0);
|
||||
Result := Int64(TS.tv_sec) * 1000000 + TS.tv_nsec div 1000;
|
||||
end;
|
||||
{$ELSE}
|
||||
var
|
||||
TV: TTimeVal;
|
||||
begin
|
||||
fpgettimeofday(@TV, nil);
|
||||
Result := Int64(TV.tv_sec) * 1000000 + TV.tv_usec;
|
||||
end;
|
||||
{$ENDIF}
|
||||
{$ENDIF}
|
||||
|
||||
function HasOpenGLSpectrumSwitch: Boolean;
|
||||
var
|
||||
I: Integer;
|
||||
|
||||
Reference in New Issue
Block a user