mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:27:33 +00:00
fix(dsp): канал TXA жил на два потока без разделения
TX-DSP поток зовёт fexchange0(TXA_CHAN) через буферы FTXIn/FTXOut, и ровно туда же на каждом T/R лезет SetTXRun из потока контроллера. Гейт FTXActive их не разделял: поток проверяет его СНАРУЖИ, а внутрь блока входит позже. Три дефекта одной природы: * деструктор звал SetTXRun(False), который поток НЕ останавливает — он лишь опускает гейт и тут же сам сливает канал своими fexchange0, то есть лезет в TXA одновременно с ещё живым потоком, прямо перед сносом объекта. Теперь Destroy сразу зовёт Close: тот опускает гейт, будит поток, дожидается WaitFor и лишь потом закрывает каналы. Комментарий «останавливаем TX поток» врал; * слив на отпускании PTT шёл без всякой синхронизации — окно гонки примерно 1-1.5% на каждый T/R (блок ~150 мкс против периода 10.7 мс). Появился FTXLock по идиоме RX-стороны (FMainGated + FMainLock): поток берёт лок вокруг ProcessTXBlock и ПЕРЕЧИТЫВАЕТ гейт под ним, оба слива в SetTXRun идут под тем же локом. Ждать контроллеру не дольше одного блока TXA; * ветка ЗАПУСКА оставалась открытой: SetMOX не отсекает MOX=True поверх уже идущей передачи (прислать повтор вправе CAT, web и TCI), а SetTXRun(True) обнулял индексы mic-кольца и дёргал SetChannelState мимо лока. ★Худший исход не теоретический: TX-поток дописывает свой локальный tail поверх обнулённого, кольцо выглядит почти полным СТАРЫХ сэмплов, и они уходят в эфир пачкой. Теперь `if Run and FTXActive then Exit` плюс весь старт одной транзакцией под FTXLock, FTXActive публикуется последним. ★Перенастройку TXA в ChangeSampleRate под лок НЕ берём: там SetChannelState с dmode=1 ждёт фейда, а фейд прокручивают fexchange0 самого потока — под локом это клин на ~106 мс (тот самый старый фриз TX→RX). Синхронизация там своя: остановка канала. Второй известный кросс-поточный доступ к TXA — pscc из сетевого потока (PureSignal), так же устроено в Thetis. Попутно закрыт use-after-free: TX-поток создаёт SetTXRun, которому открытый движок не нужен, а гасил его только код ЗА гейтом `if not FInitialized then Exit` в Close — на неоткрытом движке поток переживал Free объекта. Ловилось не там, где сделано: следующий TThread.Create в процессе падал с AV (в стенде — на секции часов после пейсинга, раньше — на третьем TRadioController в FreeEngines). Остановка потока перенесена в начало Close, до гейта. Стенд: порядок секций теперь намеренный — часы идут ПОСЛЕ тяжёлых частей и служат сторожем починки потока (упадёт снова — увидим там). Новая проверка повторного MOX с ★негативным контролем: без гейта «было 3000, стало 0». Для неё в движке появилось диагностическое свойство TXMicFill. 276/276, test/cat 50/50, GUI (--ws=qt6) и демон собираются. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bkwwyj7xVRrqnSVEseTRfV
This commit is contained in:
+118
-57
@@ -329,6 +329,12 @@ type
|
|||||||
// установки гейта, гарантируя что fexchange0 на RXA_CHAN завершён.
|
// установки гейта, гарантируя что fexchange0 на RXA_CHAN завершён.
|
||||||
FMainGated: Boolean;
|
FMainGated: Boolean;
|
||||||
FMainLock: TCriticalSection;
|
FMainLock: TCriticalSection;
|
||||||
|
// ★Канал TXA принадлежит TX-потоку, но T/R трогает его же: SetTXRun сливает
|
||||||
|
// канал своими fexchange0 через ОБЩИЕ буферы FTXIn/FTXOut. Гейт FTXActive
|
||||||
|
// сам по себе не разделяет их — поток мог пройти проверку и войти в блок
|
||||||
|
// ровно тогда, когда T/R уже сливает. Идиома та же, что на RX (FMainGated +
|
||||||
|
// FMainLock): гейт + повторная проверка ПОД локом, а слив идёт под ним же.
|
||||||
|
FTXLock: TCriticalSection;
|
||||||
|
|
||||||
FSpectrumPixels: array[0..SPECTRUM_PIXELS - 1] of Single;
|
FSpectrumPixels: array[0..SPECTRUM_PIXELS - 1] of Single;
|
||||||
FWaterfallPixels: array[0..SPECTRUM_PIXELS - 1] of Single;
|
FWaterfallPixels: array[0..SPECTRUM_PIXELS - 1] of Single;
|
||||||
@@ -617,6 +623,7 @@ type
|
|||||||
procedure ApplySliceFMSQ(Idx: Integer); // squelch слайса с гейтом по режиму
|
procedure ApplySliceFMSQ(Idx: Integer); // squelch слайса с гейтом по режиму
|
||||||
procedure ApplyNoiseFilterState;
|
procedure ApplyNoiseFilterState;
|
||||||
procedure ProcessTXBlock; // вызывается из TTXDSPThread
|
procedure ProcessTXBlock; // вызывается из TTXDSPThread
|
||||||
|
function GetTXMicFill: Integer;
|
||||||
procedure FeedTXDisplaySample(const I, Q: Double);
|
procedure FeedTXDisplaySample(const I, Q: Double);
|
||||||
procedure ApplyTXChainSettings; // прокидывает все FTX* поля в WDSP
|
procedure ApplyTXChainSettings; // прокидывает все FTX* поля в WDSP
|
||||||
procedure PushTXEQProfile; // кривая EQ в WDSP (узлы + legacy 3 полосы)
|
procedure PushTXEQProfile; // кривая EQ в WDSP (узлы + legacy 3 полосы)
|
||||||
@@ -896,6 +903,9 @@ type
|
|||||||
property OnPanWaterfall: TOnPanPixels read FOnPanWaterfall write FOnPanWaterfall;
|
property OnPanWaterfall: TOnPanPixels read FOnPanWaterfall write FOnPanWaterfall;
|
||||||
property OnTXIQ: TOnTXIQReady read FOnTXIQ write FOnTXIQ;
|
property OnTXIQ: TOnTXIQReady read FOnTXIQ write FOnTXIQ;
|
||||||
property TXActive: Boolean read FTXActive;
|
property TXActive: Boolean read FTXActive;
|
||||||
|
// Сколько mic-сэмплов ждёт обработки. Диагностическое: по нему видно, что
|
||||||
|
// повторный MOX=True не сбрасывает кольцо на ходу (см. SetTXRun).
|
||||||
|
property TXMicFill: Integer read GetTXMicFill;
|
||||||
// Включает full-duplex поведение RX-тракта: RXA не останавливается на TX,
|
// Включает full-duplex поведение RX-тракта: RXA не останавливается на TX,
|
||||||
// RX-анализатор продолжает получать IQ. Должен ставиться UI ДО SetTXRun.
|
// RX-анализатор продолжает получать IQ. Должен ставиться UI ДО SetTXRun.
|
||||||
property KeepRXDuringTX: Boolean read FKeepRXDuringTX
|
property KeepRXDuringTX: Boolean read FKeepRXDuringTX
|
||||||
@@ -1038,12 +1048,20 @@ begin
|
|||||||
// Обрабатываем накопившиеся блоки с реальными mic-данными (строго
|
// Обрабатываем накопившиеся блоки с реальными mic-данными (строго
|
||||||
// mic-гейтед: сколько полных mic-блоков в ring — столько TX-блоков).
|
// mic-гейтед: сколько полных mic-блоков в ring — столько TX-блоков).
|
||||||
repeat
|
repeat
|
||||||
|
if Terminated then Break;
|
||||||
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 then Break;
|
||||||
FEngine.ProcessTXBlock
|
FEngine.FTXLock.Enter;
|
||||||
else
|
try
|
||||||
Break;
|
// ★Повторная проверка гейта ПОД локом — как на RX (FMainGated).
|
||||||
|
// Снаружи она ничего не гарантирует: между ней и входом в блок T/R
|
||||||
|
// успевает опустить FTXActive и начать слив канала.
|
||||||
|
if not FEngine.FTXActive then Break;
|
||||||
|
FEngine.ProcessTXBlock;
|
||||||
|
finally
|
||||||
|
FEngine.FTXLock.Leave;
|
||||||
|
end;
|
||||||
until False;
|
until False;
|
||||||
|
|
||||||
if Terminated then Break;
|
if Terminated then Break;
|
||||||
@@ -1512,6 +1530,7 @@ begin
|
|||||||
// Гейт главного тракта (хирургическая смена rate)
|
// Гейт главного тракта (хирургическая смена rate)
|
||||||
FMainGated := False;
|
FMainGated := False;
|
||||||
FMainLock := TCriticalSection.Create;
|
FMainLock := TCriticalSection.Create;
|
||||||
|
FTXLock := TCriticalSection.Create;
|
||||||
|
|
||||||
// Лупа маяка — analyzer создаётся по требованию UI (SetBeaconZoom)
|
// Лупа маяка — analyzer создаётся по требованию UI (SetBeaconZoom)
|
||||||
FBcnLock := TCriticalSection.Create;
|
FBcnLock := TCriticalSection.Create;
|
||||||
@@ -1620,12 +1639,17 @@ end;
|
|||||||
|
|
||||||
destructor TWDSPEngine.Destroy;
|
destructor TWDSPEngine.Destroy;
|
||||||
begin
|
begin
|
||||||
SetTXRun(False); // останавливаем TX поток если запущен
|
// ★Сразу Close, без SetTXRun(False): тот поток НЕ останавливает — он лишь
|
||||||
|
// опускает FTXActive и тут же сам сливает канал TXA своими fexchange0, то
|
||||||
|
// есть лезет в канал одновременно с ещё живым потоком, прямо перед сносом
|
||||||
|
// объекта. Close опускает гейт, будит поток, дожидается его WaitFor и лишь
|
||||||
|
// потом закрывает каналы — это и есть правильный порядок.
|
||||||
Close;
|
Close;
|
||||||
RTLEventDestroy(FQueueSem);
|
RTLEventDestroy(FQueueSem);
|
||||||
RTLEventDestroy(FTXMicSem);
|
RTLEventDestroy(FTXMicSem);
|
||||||
FSliceLock.Free;
|
FSliceLock.Free;
|
||||||
FMainLock.Free;
|
FMainLock.Free;
|
||||||
|
FTXLock.Free;
|
||||||
FBcnLock.Free;
|
FBcnLock.Free;
|
||||||
FIQTapLock.Free;
|
FIQTapLock.Free;
|
||||||
inherited;
|
inherited;
|
||||||
@@ -2252,6 +2276,24 @@ procedure TWDSPEngine.Close;
|
|||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
|
// ★TX-DSP поток живёт весь сеанс (не пересоздаётся на T/R) и гасится ЗДЕСЬ —
|
||||||
|
// ДО гейта FInitialized, а не после. Создаёт его SetTXRun, которому открытый
|
||||||
|
// движок не нужен, поэтому на неоткрытом движке гейт ниже выпускал нас
|
||||||
|
// раньше, чем поток останавливался: он переживал Free самого движка и
|
||||||
|
// продолжал ходить по освобождённой памяти. Ловится это не там, где сделано:
|
||||||
|
// следующий TThread.Create в процессе падал с AV (в стенде — на секции часов
|
||||||
|
// после части «пейсинг», а раньше на третьем TRadioController в FreeEngines).
|
||||||
|
// Остановка всё так же идёт прежде закрытия WDSP-каналов — поток зовёт
|
||||||
|
// fexchange0 на TXA_CHAN.
|
||||||
|
FTXActive := False;
|
||||||
|
if Assigned(FTXThread) then
|
||||||
|
begin
|
||||||
|
FTXThread.Terminate;
|
||||||
|
RTLEventSetEvent(FTXMicSem);
|
||||||
|
FTXThread.WaitFor;
|
||||||
|
FreeAndNil(FTXThread);
|
||||||
|
end;
|
||||||
|
|
||||||
if not FInitialized then Exit;
|
if not FInitialized then Exit;
|
||||||
|
|
||||||
// PureSignal: снять подачу feedback ДО закрытия TXA — pscc зовётся из
|
// PureSignal: снять подачу feedback ДО закрытия TXA — pscc зовётся из
|
||||||
@@ -2267,17 +2309,6 @@ begin
|
|||||||
FreeAndNil(FDSPThread);
|
FreeAndNil(FDSPThread);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// TX-DSP поток живёт весь сеанс (не пересоздаётся на T/R) — гасим его здесь,
|
|
||||||
// до закрытия WDSP-каналов (он зовёт fexchange0 на TXA_CHAN).
|
|
||||||
FTXActive := False;
|
|
||||||
if Assigned(FTXThread) then
|
|
||||||
begin
|
|
||||||
FTXThread.Terminate;
|
|
||||||
RTLEventSetEvent(FTXMicSem);
|
|
||||||
FTXThread.WaitFor;
|
|
||||||
FreeAndNil(FTXThread);
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Закрываем WDSP-каналы слайсов (записи FSlices сохраняем — переоткроются в Open).
|
// Закрываем WDSP-каналы слайсов (записи FSlices сохраняем — переоткроются в Open).
|
||||||
FSliceLock.Enter;
|
FSliceLock.Enter;
|
||||||
try
|
try
|
||||||
@@ -3807,48 +3838,64 @@ procedure TWDSPEngine.SetTXRun(Run: Boolean);
|
|||||||
var
|
var
|
||||||
k, Err: Integer;
|
k, Err: Integer;
|
||||||
begin
|
begin
|
||||||
|
// ★Повторный старт при уже идущей передаче — не событие, а шум: SetMOX не
|
||||||
|
// отсекает MOX=True поверх MOX=True (RadioController.SetMOX), а прислать его
|
||||||
|
// снова вправе и CAT, и web, и TCI. Перезапускать тракт по такому вызову
|
||||||
|
// НЕЛЬЗЯ: сброс индексов mic-кольца гонялся бы с чтением из TX-потока
|
||||||
|
// (правило «tail пишет только TX-поток» ломается), а SetChannelState — с его
|
||||||
|
// fexchange0. Худший исход не теоретический: поток дописывает свой локальный
|
||||||
|
// tail поверх обнулённого, кольцо выглядит почти полным СТАРЫХ данных, и они
|
||||||
|
// уходят в эфир пачкой — то есть всплеск.
|
||||||
|
if Run and FTXActive then Exit;
|
||||||
FDispPos := 0;
|
FDispPos := 0;
|
||||||
FTXDispPos := 0;
|
FTXDispPos := 0;
|
||||||
if Run then
|
if Run then
|
||||||
begin
|
begin
|
||||||
FActiveDisplayID := TX_DISP_ID;
|
// ★Весь старт — одной транзакцией под FTXLock, флаг публикуется последним:
|
||||||
// Сброс mic ring ДО активации, чтобы простаивавший поток не прочитал старое.
|
// до него поток, взявший лок, перечитает гейт и уйдёт в простой, а после —
|
||||||
FTXMicHead := 0;
|
// канал уже поднят, кольцо пусто, буферы продуты.
|
||||||
FTXMicTail := 0;
|
FTXLock.Enter;
|
||||||
if FInitialized then
|
try
|
||||||
begin
|
FActiveDisplayID := TX_DISP_ID;
|
||||||
SetChannelState(TXA_CHAN, 1, 0); // run, no delay
|
// Сброс mic ring ДО активации, чтобы простаивавший поток не прочитал старое.
|
||||||
// Продувка канала: страховка от остатка прошлой TX-сессии в буферах/
|
FTXMicHead := 0;
|
||||||
// фильтрах TXA (без неё огрызок — после TUN это полноразмахный тон —
|
FTXMicTail := 0;
|
||||||
// выплёвывался в эфир первым же блоком нового TX). Основной слив теперь
|
if FInitialized then
|
||||||
// на стопе (ниже), но нештатные пути останова канал не чистят. 4 нулевых
|
|
||||||
// блока через fexchange0, выход в мусор (не в FOnTXIQ/display); поток
|
|
||||||
// ещё idle (FTXActive=False) — гонки нет.
|
|
||||||
FillChar(FTXIn[0], FAudioBufSize * 2 * SizeOf(Double), 0);
|
|
||||||
for k := 1 to 4 do
|
|
||||||
begin
|
begin
|
||||||
Err := 0;
|
SetChannelState(TXA_CHAN, 1, 0); // run, no delay
|
||||||
fexchange0(TXA_CHAN, @FTXIn[0], @FTXOut[0], @Err);
|
// Продувка канала: страховка от остатка прошлой TX-сессии в буферах/
|
||||||
|
// фильтрах TXA (без неё огрызок — после TUN это полноразмахный тон —
|
||||||
|
// выплёвывался в эфир первым же блоком нового TX). Основной слив теперь
|
||||||
|
// на стопе (ниже), но нештатные пути останова канал не чистят. 4 нулевых
|
||||||
|
// блока через fexchange0, выход в мусор (не в FOnTXIQ/display).
|
||||||
|
FillChar(FTXIn[0], FAudioBufSize * 2 * SizeOf(Double), 0);
|
||||||
|
for k := 1 to 4 do
|
||||||
|
begin
|
||||||
|
Err := 0;
|
||||||
|
fexchange0(TXA_CHAN, @FTXIn[0], @FTXOut[0], @Err);
|
||||||
|
end;
|
||||||
|
// Сброс TX-анализатора: его усредняющая история хранит спектр ПРОШЛОЙ
|
||||||
|
// сессии (после TUN — тон), и при переключении на TX-дисплей первые
|
||||||
|
// кадры показывали «всплеск» старого тона (виден в non-DUP, где на
|
||||||
|
// экране TX-спектр; эфир при этом чист — в DUP старт чистый).
|
||||||
|
// ResetPixelBuffers — лёгкое зануление pixel/average буферов (MW0LGE);
|
||||||
|
// НЕ ApplyTXAnalyzerSettings: тот зовёт SetAnalyzer (перепланирование
|
||||||
|
// FFT в UI-потоке) — давал видимую задержку отрисовки на старте TX.
|
||||||
|
if FAnalyzerOpen then
|
||||||
|
ResetPixelBuffers(TX_DISP_ID);
|
||||||
end;
|
end;
|
||||||
// Сброс TX-анализатора: его усредняющая история хранит спектр ПРОШЛОЙ
|
// TX-DSP поток создаётся ОДИН раз и живёт весь сеанс. НЕ пересоздаём на каждом
|
||||||
// сессии (после TUN — тон), и при переключении на TX-дисплей первые
|
// T/R: join (WaitFor) давал фиксированные ~100 мс фриза GUI на отпускании PTT.
|
||||||
// кадры показывали «всплеск» старого тона (виден в non-DUP, где на
|
// При FTXActive=False поток простаивает (см. TTXDSPThread.Execute: Continue).
|
||||||
// экране TX-спектр; эфир при этом чист — в DUP старт чистый).
|
if not Assigned(FTXThread) then
|
||||||
// ResetPixelBuffers — лёгкое зануление pixel/average буферов (MW0LGE);
|
begin
|
||||||
// НЕ ApplyTXAnalyzerSettings: тот зовёт SetAnalyzer (перепланирование
|
FTXThread := TTXDSPThread.Create(Self);
|
||||||
// FFT в UI-потоке) — давал видимую задержку отрисовки на старте TX.
|
FTXThread.Priority := tpHighest;
|
||||||
if FAnalyzerOpen then
|
end;
|
||||||
ResetPixelBuffers(TX_DISP_ID);
|
FTXActive := True; // активируем последним
|
||||||
|
finally
|
||||||
|
FTXLock.Leave;
|
||||||
end;
|
end;
|
||||||
// TX-DSP поток создаётся ОДИН раз и живёт весь сеанс. НЕ пересоздаём на каждом
|
|
||||||
// T/R: join (WaitFor) давал фиксированные ~100 мс фриза GUI на отпускании PTT.
|
|
||||||
// При FTXActive=False поток простаивает (см. TTXDSPThread.Execute: Continue).
|
|
||||||
if not Assigned(FTXThread) then
|
|
||||||
begin
|
|
||||||
FTXThread := TTXDSPThread.Create(Self);
|
|
||||||
FTXThread.Priority := tpHighest;
|
|
||||||
end;
|
|
||||||
FTXActive := True; // активируем последним
|
|
||||||
end else begin
|
end else begin
|
||||||
FTXActive := False; // поток уходит в простой (не трогает канал/ring)
|
FTXActive := False; // поток уходит в простой (не трогает канал/ring)
|
||||||
FActiveDisplayID := RX_DISP_ID;
|
FActiveDisplayID := RX_DISP_ID;
|
||||||
@@ -3863,12 +3910,21 @@ begin
|
|||||||
// 10мс) и штатный flush буферов исполняют наши же fexchange0 с нулевым
|
// 10мс) и штатный flush буферов исполняют наши же fexchange0 с нулевым
|
||||||
// входом — суммарно <1мс, выход в мусор (в эфир не идёт: DUC-очередь
|
// входом — суммарно <1мс, выход в мусор (в эфир не идёт: DUC-очередь
|
||||||
// чистится контроллером на T/R).
|
// чистится контроллером на T/R).
|
||||||
SetChannelState(TXA_CHAN, 0, 0); // stop, без ожидания
|
// ★Под локом: FTXActive уже опущен, но поток мог войти в блок раньше —
|
||||||
FillChar(FTXIn[0], FAudioBufSize * 2 * SizeOf(Double), 0);
|
// лок дожидается его конца и не пускает в канал следующий (гейт он
|
||||||
for k := 1 to 4 do
|
// перечитывает под этим же локом). Ждать приходится не дольше одного
|
||||||
begin
|
// блока TXA — сотня-другая микросекунд.
|
||||||
Err := 0;
|
FTXLock.Enter;
|
||||||
fexchange0(TXA_CHAN, @FTXIn[0], @FTXOut[0], @Err);
|
try
|
||||||
|
SetChannelState(TXA_CHAN, 0, 0); // stop, без ожидания
|
||||||
|
FillChar(FTXIn[0], FAudioBufSize * 2 * SizeOf(Double), 0);
|
||||||
|
for k := 1 to 4 do
|
||||||
|
begin
|
||||||
|
Err := 0;
|
||||||
|
fexchange0(TXA_CHAN, @FTXIn[0], @FTXOut[0], @Err);
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
FTXLock.Leave;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
FTXMicHead := 0;
|
FTXMicHead := 0;
|
||||||
@@ -4027,6 +4083,11 @@ begin
|
|||||||
RTLEventSetEvent(FTXMicSem); // будим TX поток
|
RTLEventSetEvent(FTXMicSem); // будим TX поток
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TWDSPEngine.GetTXMicFill: Integer;
|
||||||
|
begin
|
||||||
|
Result := (FTXMicHead - FTXMicTail + TX_MIC_RING) and (TX_MIC_RING - 1);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.PushTXMicSampleD(const S: Double);
|
procedure TWDSPEngine.PushTXMicSampleD(const S: Double);
|
||||||
var newHead: Integer;
|
var newHead: Integer;
|
||||||
begin
|
begin
|
||||||
|
|||||||
+15
-1
@@ -2002,7 +2002,7 @@ var
|
|||||||
Cfg: TTCISettings;
|
Cfg: TTCISettings;
|
||||||
C: TRawClient;
|
C: TRawClient;
|
||||||
Markers, Answered, Quantum, LongGaps, MaxGapMs, MinReserve: Integer;
|
Markers, Answered, Quantum, LongGaps, MaxGapMs, MinReserve: Integer;
|
||||||
N, MinStart, SumStart, StaleRuns: Integer;
|
N, MinStart, SumStart, StaleRuns, i, FillBefore: Integer;
|
||||||
DbgOwed, DbgFlight: Double;
|
DbgOwed, DbgFlight: Double;
|
||||||
DbgWin, DbgQ, DbgLead, SeedLead: Integer;
|
DbgWin, DbgQ, DbgLead, SeedLead: Integer;
|
||||||
begin
|
begin
|
||||||
@@ -2048,6 +2048,20 @@ begin
|
|||||||
C.WaitText('trx:', 1500);
|
C.WaitText('trx:', 1500);
|
||||||
Check('пейсинг: модуляция из TCI взята', Ctrl.TCIMicActive);
|
Check('пейсинг: модуляция из TCI взята', Ctrl.TCIMicActive);
|
||||||
|
|
||||||
|
// ── Повторный MOX поверх идущей передачи ──
|
||||||
|
// ★Прислать MOX=True второй раз вправе и CAT, и web, и TCI, а SetMOX этого
|
||||||
|
// не отсекает. Перезапускать по нему TX-тракт нельзя: сброс индексов
|
||||||
|
// mic-кольца гоняется с чтением из TX-потока, и в худшем случае поток
|
||||||
|
// дописывает свой tail поверх обнулённого — кольцо выглядит почти полным
|
||||||
|
// СТАРЫХ данных, которые уходят в эфир пачкой.
|
||||||
|
for i := 1 to 3000 do Ctrl.FDSPEngine.PushTXMicSampleD(0.25);
|
||||||
|
FillBefore := Ctrl.FDSPEngine.TXMicFill;
|
||||||
|
Ctrl.SetMOX(True);
|
||||||
|
Check('повтор MOX: передача не перезапускается', Ctrl.FDSPEngine.TXActive);
|
||||||
|
Check('повтор MOX: mic-кольцо не обнуляется',
|
||||||
|
Ctrl.FDSPEngine.TXMicFill >= FillBefore - 512,
|
||||||
|
Format('было %d, стало %d', [FillBefore, Ctrl.FDSPEngine.TXMicFill]));
|
||||||
|
|
||||||
// ── Здоровый клиент ──
|
// ── Здоровый клиент ──
|
||||||
// Прогрев: пока шёл разбор команды и проверки выше, клиент не отвечал, и
|
// Прогрев: пока шёл разбор команды и проверки выше, клиент не отвечал, и
|
||||||
// окно в полёте успело набиться маркерами. Плюс за это время адаптер выдаёт
|
// окно в полёте успело набиться маркерами. Плюс за это время адаптер выдаёт
|
||||||
|
|||||||
Reference in New Issue
Block a user