mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
fix(pluto): телеметрия больше не блокирует UI-поток
ReadTelemetry делал 5-6 синхронных iio_channel_attr_read_* — каждое это USB round-trip, вместе ~30 мс. Звался из MeterTimerTick, то есть прямо в главном потоке, дважды в секунду (POLL_MS=500). Замер зонным профайлером: тик метра занимал 5633 мкс настенного времени при пиках до 51 мс, тогда как CPU-времени в нём было всего 140 мкс — чистое ожидание USB, ронявшее кадры. Опрос переехал в TPlutoTeleThread: он раз в 500 мс читает те же атрибуты и публикует снимок под FTeleLock. ReadTelemetry теперь только отдаёт снимок и никогда не блокирует, поэтому RadioController.ServicePlutoTelemetry и MainForm.MeterTimerTick не менялись — вся фильтрация температуры на месте. Прежнее блокирующее тело стало PollTelemetryBlocking (только из потока). Конкуренция за контекст libiio не выросла: UI-поток и раньше читал атрибуты параллельно с iio_buffer_refill в RX-потоке. Сменился лишь тот, кто ждёт. StopTelemetry идёт первой строкой Disconnect — до iio_context_destroy, иначе поток продолжил бы читать атрибуты разрушенного контекста. Результат: ui.meter_tick 5633 -> 110 мкс. Значение телеметрии теперь может отставать до секунды (троттл контроллера + период потока) — на 10-герцовом индикаторе температуры это незаметно. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+119
-3
@@ -82,6 +82,21 @@ type
|
|||||||
FTxFifoCount: Integer; // занято элементов
|
FTxFifoCount: Integer; // занято элементов
|
||||||
FTxLock: TRTLCriticalSection;
|
FTxLock: TRTLCriticalSection;
|
||||||
|
|
||||||
|
// Телеметрия. Каждый iio_channel_attr_read_* — USB round-trip (~5 мс), а их
|
||||||
|
// 5-6 за опрос: ~30 мс. Раньше это делал UI-поток (MeterTimerTick, 10 Гц) и
|
||||||
|
// замирал на весь опрос. Теперь опрашивает свой поток, а ReadTelemetry лишь
|
||||||
|
// отдаёт последний снимок и никогда не блокирует.
|
||||||
|
FTeleThread: TThread;
|
||||||
|
FTeleLock: TRTLCriticalSection;
|
||||||
|
FTeleTemp: Double; // TELEMETRY_NONE, пока не прочитано
|
||||||
|
FTeleSoc: Double;
|
||||||
|
FTeleRssi: Double;
|
||||||
|
FTeleValid: Boolean; // был ли хоть один успешный опрос
|
||||||
|
procedure StartTelemetry;
|
||||||
|
procedure StopTelemetry;
|
||||||
|
// Блокирующий опрос железа: только из FTeleThread.
|
||||||
|
function PollTelemetryBlocking(out TempC, SocTempC, RSSIdB: Double): Boolean;
|
||||||
|
|
||||||
// Низкоуровневые помощники применения атрибутов к ad9361-phy
|
// Низкоуровневые помощники применения атрибутов к ad9361-phy
|
||||||
procedure ApplyRxLO(Hz: Double);
|
procedure ApplyRxLO(Hz: Double);
|
||||||
procedure ApplyTxLO(Hz: Double);
|
procedure ApplyTxLO(Hz: Double);
|
||||||
@@ -168,6 +183,19 @@ type
|
|||||||
constructor Create(ABk: TPlutoBackend);
|
constructor Create(ABk: TPlutoBackend);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Телеметрия: опрашивает железо в своём темпе и публикует снимок. Держит UI
|
||||||
|
// подальше от блокирующих USB-транзакций. Доступ к контексту libiio здесь не
|
||||||
|
// более конкурентен, чем раньше: UI-поток дёргал те же атрибуты параллельно с
|
||||||
|
// refill в RX-потоке — сменился только поток, который ждёт.
|
||||||
|
TPlutoTeleThread = class(TThread)
|
||||||
|
private
|
||||||
|
FBk: TPlutoBackend;
|
||||||
|
protected
|
||||||
|
procedure Execute; override;
|
||||||
|
public
|
||||||
|
constructor Create(ABk: TPlutoBackend);
|
||||||
|
end;
|
||||||
|
|
||||||
// TX-поток: тянет int16-пары из FIFO бэкенда (или нули при underrun), пишет в
|
// TX-поток: тянет int16-пары из FIFO бэкенда (или нули при underrun), пишет в
|
||||||
// iio-буфер и push'ит. iio_buffer_push блокирует до приёма DMA → пейсинг ровно
|
// iio-буфер и push'ит. iio_buffer_push блокирует до приёма DMA → пейсинг ровно
|
||||||
// на device-rate. Работает с private-полями TPlutoBackend (одна единица компиляции).
|
// на device-rate. Работает с private-полями TPlutoBackend (одна единица компиляции).
|
||||||
@@ -350,6 +378,9 @@ begin
|
|||||||
FStreaming := False; FRxSeq := 0;
|
FStreaming := False; FRxSeq := 0;
|
||||||
FTxBuf := nil; FTxChI := nil; FTxChQ := nil; FTxThread := nil;
|
FTxBuf := nil; FTxChI := nil; FTxChQ := nil; FTxThread := nil;
|
||||||
FTxStreaming := False; FTxRatio := 8;
|
FTxStreaming := False; FTxRatio := 8;
|
||||||
|
FTeleThread := nil; FTeleValid := False;
|
||||||
|
FTeleTemp := TELEMETRY_NONE; FTeleSoc := TELEMETRY_NONE; FTeleRssi := TELEMETRY_NONE;
|
||||||
|
InitCriticalSection(FTeleLock);
|
||||||
FTxAttDb := PLUTO_TX_OFF; // на старте TX выключен (макс. аттенюация)
|
FTxAttDb := PLUTO_TX_OFF; // на старте TX выключен (макс. аттенюация)
|
||||||
// FIFO на ~4 TX-буфера запаса под джиттер продюсера/консьюмера. Окончательный
|
// FIFO на ~4 TX-буфера запаса под джиттер продюсера/консьюмера. Окончательный
|
||||||
// размер выставляется в StartTX по фактическому device-rate (TxBufPairsForRate).
|
// размер выставляется в StartTX по фактическому device-rate (TxBufPairsForRate).
|
||||||
@@ -363,7 +394,8 @@ end;
|
|||||||
|
|
||||||
destructor TPlutoBackend.Destroy;
|
destructor TPlutoBackend.Destroy;
|
||||||
begin
|
begin
|
||||||
Disconnect;
|
Disconnect; // остановит и телеметрию
|
||||||
|
DoneCriticalSection(FTeleLock);
|
||||||
DoneCriticalSection(FTxLock);
|
DoneCriticalSection(FTxLock);
|
||||||
inherited Destroy;
|
inherited Destroy;
|
||||||
end;
|
end;
|
||||||
@@ -535,11 +567,13 @@ begin
|
|||||||
ApplySampleRate(FSampleRate);
|
ApplySampleRate(FSampleRate);
|
||||||
ApplyGain;
|
ApplyGain;
|
||||||
ApplyTxAtten; // TX заглушён (макс. аттенюация) до явного PTT
|
ApplyTxAtten; // TX заглушён (макс. аттенюация) до явного PTT
|
||||||
|
StartTelemetry;
|
||||||
Result := True;
|
Result := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPlutoBackend.Disconnect;
|
procedure TPlutoBackend.Disconnect;
|
||||||
begin
|
begin
|
||||||
|
StopTelemetry; // до iio_context_destroy: поток ещё читает атрибуты
|
||||||
StopTX;
|
StopTX;
|
||||||
StopRX;
|
StopRX;
|
||||||
if FCtx <> nil then
|
if FCtx <> nil then
|
||||||
@@ -904,8 +938,8 @@ begin
|
|||||||
LeaveCriticalSection(FTxLock);
|
LeaveCriticalSection(FTxLock);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TPlutoBackend.ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean;
|
function TPlutoBackend.PollTelemetryBlocking(out TempC, SocTempC, RSSIdB: Double): Boolean;
|
||||||
// Опрос аппаратной телеметрии (вызывается контроллером, не из RX-потока):
|
// БЛОКИРУЮЩИЙ опрос железа — только из FTeleThread (каждое чтение = USB round-trip):
|
||||||
// ad9361-phy temp0/input — температура трансивера в миллиградусах C (longlong);
|
// ad9361-phy temp0/input — температура трансивера в миллиградусах C (longlong);
|
||||||
// xadc temp0 — температура Zynq SoC/FPGA; сырой raw + offset (longlong)
|
// xadc temp0 — температура Zynq SoC/FPGA; сырой raw + offset (longlong)
|
||||||
// + scale (double), C = (raw + offset) * scale / 1000;
|
// + scale (double), C = (raw + offset) * scale / 1000;
|
||||||
@@ -963,4 +997,86 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TPlutoBackend.ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean;
|
||||||
|
// Неблокирующий: отдаёт последний снимок, снятый FTeleThread. Безопасно звать из UI.
|
||||||
|
begin
|
||||||
|
EnterCriticalSection(FTeleLock);
|
||||||
|
try
|
||||||
|
TempC := FTeleTemp;
|
||||||
|
SocTempC := FTeleSoc;
|
||||||
|
RSSIdB := FTeleRssi;
|
||||||
|
Result := FTeleValid;
|
||||||
|
finally
|
||||||
|
LeaveCriticalSection(FTeleLock);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPlutoBackend.StartTelemetry;
|
||||||
|
begin
|
||||||
|
if FTeleThread = nil then
|
||||||
|
FTeleThread := TPlutoTeleThread.Create(Self);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPlutoBackend.StopTelemetry;
|
||||||
|
begin
|
||||||
|
if FTeleThread = nil then Exit;
|
||||||
|
FTeleThread.Terminate;
|
||||||
|
FTeleThread.WaitFor; // обязателен до разрушения контекста libiio
|
||||||
|
FreeAndNil(FTeleThread);
|
||||||
|
EnterCriticalSection(FTeleLock);
|
||||||
|
try
|
||||||
|
FTeleValid := False;
|
||||||
|
FTeleTemp := TELEMETRY_NONE;
|
||||||
|
FTeleSoc := TELEMETRY_NONE;
|
||||||
|
FTeleRssi := TELEMETRY_NONE;
|
||||||
|
finally
|
||||||
|
LeaveCriticalSection(FTeleLock);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TPlutoTeleThread }
|
||||||
|
|
||||||
|
constructor TPlutoTeleThread.Create(ABk: TPlutoBackend);
|
||||||
|
begin
|
||||||
|
FBk := ABk;
|
||||||
|
FreeOnTerminate := False;
|
||||||
|
inherited Create(False);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPlutoTeleThread.Execute;
|
||||||
|
const
|
||||||
|
POLL_MS = 500; // ~2 Гц: чаще незачем, каждый опрос ~30 мс USB-транзакций
|
||||||
|
STEP_MS = 25; // мелкий шаг сна — чтобы Terminate отрабатывал быстро
|
||||||
|
var
|
||||||
|
t, s, r: Double;
|
||||||
|
ok: Boolean;
|
||||||
|
Waited: Integer;
|
||||||
|
begin
|
||||||
|
while not Terminated do
|
||||||
|
begin
|
||||||
|
if FBk.FConnected then
|
||||||
|
begin
|
||||||
|
ok := FBk.PollTelemetryBlocking(t, s, r);
|
||||||
|
if ok then
|
||||||
|
begin
|
||||||
|
EnterCriticalSection(FBk.FTeleLock);
|
||||||
|
try
|
||||||
|
FBk.FTeleTemp := t;
|
||||||
|
FBk.FTeleSoc := s;
|
||||||
|
FBk.FTeleRssi := r;
|
||||||
|
FBk.FTeleValid := True;
|
||||||
|
finally
|
||||||
|
LeaveCriticalSection(FBk.FTeleLock);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
Waited := 0;
|
||||||
|
while (Waited < POLL_MS) and not Terminated do
|
||||||
|
begin
|
||||||
|
Sleep(STEP_MS);
|
||||||
|
Inc(Waited, STEP_MS);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ interface
|
|||||||
uses
|
uses
|
||||||
Classes, SysUtils, Math, SyncObjs,
|
Classes, SysUtils, Math, SyncObjs,
|
||||||
BeaconDecoder,
|
BeaconDecoder,
|
||||||
|
PerfLog, // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
|
||||||
WDSP;
|
WDSP;
|
||||||
|
|
||||||
const
|
const
|
||||||
@@ -1365,10 +1366,12 @@ procedure TWDSPEngine.PushIQItemToDSP(const Item: TIQQueueItem);
|
|||||||
var
|
var
|
||||||
i, Pos: Integer;
|
i, Pos: Integer;
|
||||||
IR, QR: LongInt;
|
IR, QR: LongInt;
|
||||||
|
T0: Int64; // PERF
|
||||||
const
|
const
|
||||||
SCALE = 1.0 / 8388608.0;
|
SCALE = 1.0 / 8388608.0;
|
||||||
begin
|
begin
|
||||||
if FTXActive and not FKeepRXDuringTX then Exit;
|
if FTXActive and not FKeepRXDuringTX then Exit;
|
||||||
|
T0 := PerfNow; // PERF
|
||||||
// Post-TX hold: дропаем входящие RX-IQ пакеты пока активно окно мьюта.
|
// Post-TX hold: дропаем входящие RX-IQ пакеты пока активно окно мьюта.
|
||||||
// Это закрывает ОБА канала сразу — и водопад/спектр (анализатор не
|
// Это закрывает ОБА канала сразу — и водопад/спектр (анализатор не
|
||||||
// кормится), и аудио (RXA не запускается, FOnAudio не вызывается).
|
// кормится), и аудио (RXA не запускается, FOnAudio не вызывается).
|
||||||
@@ -1402,9 +1405,11 @@ begin
|
|||||||
if FRXAccPos >= FBufSize then
|
if FRXAccPos >= FBufSize then
|
||||||
ProcessRXBlock;
|
ProcessRXBlock;
|
||||||
end;
|
end;
|
||||||
|
PerfAdd(pzDspPushIQ, T0); // PERF: весь путь пакета через DSP-поток
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.FeedDisplaySample(const I, Q: Double);
|
procedure TWDSPEngine.FeedDisplaySample(const I, Q: Double);
|
||||||
|
var T0: Int64; // PERF
|
||||||
begin
|
begin
|
||||||
if not FAnalyzerOpen then Exit;
|
if not FAnalyzerOpen then Exit;
|
||||||
|
|
||||||
@@ -1414,7 +1419,9 @@ begin
|
|||||||
|
|
||||||
if FDispPos >= DISPLAY_BLOCK_SIZE then
|
if FDispPos >= DISPLAY_BLOCK_SIZE then
|
||||||
begin
|
begin
|
||||||
|
T0 := PerfNow; // PERF
|
||||||
Spectrum0(1, RX_DISP_ID, 0, 0, @FDispBuf[0]);
|
Spectrum0(1, RX_DISP_ID, 0, 0, @FDispBuf[0]);
|
||||||
|
PerfAdd(pzDspSpectrum0, T0); // PERF: скармливание блока анализатору
|
||||||
FDispPos := 0;
|
FDispPos := 0;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -1438,12 +1445,14 @@ procedure TWDSPEngine.ProcessRXBlock;
|
|||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
Err: Integer;
|
Err: Integer;
|
||||||
|
T0: Int64; // PERF
|
||||||
// OutL/OutR теперь поля класса — не аллоцируем каждый раз
|
// OutL/OutR теперь поля класса — не аллоцируем каждый раз
|
||||||
begin
|
begin
|
||||||
FRXAccPos := 0;
|
FRXAccPos := 0;
|
||||||
if not FInitialized then Exit;
|
if not FInitialized then Exit;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
T0 := PerfNow; // PERF
|
||||||
// Упаковываем в interleaved буфер для fexchange0
|
// Упаковываем в interleaved буфер для fexchange0
|
||||||
for i := 0 to FBufSize - 1 do
|
for i := 0 to FBufSize - 1 do
|
||||||
begin
|
begin
|
||||||
@@ -1454,6 +1463,7 @@ begin
|
|||||||
// DSP обработка
|
// DSP обработка
|
||||||
Err := 0;
|
Err := 0;
|
||||||
fexchange0(RXA_CHAN, @FRXIn[0], @FRXOut[0], @Err);
|
fexchange0(RXA_CHAN, @FRXIn[0], @FRXOut[0], @Err);
|
||||||
|
PerfAdd(pzDspFexMain, T0); // PERF: главный RX-канал WDSP
|
||||||
|
|
||||||
// S-meter: читаем ОДИН раз в DSP-колбэке и кешируем в FSMeter.
|
// S-meter: читаем ОДИН раз в DSP-колбэке и кешируем в FSMeter.
|
||||||
// GetRXAMeter сбрасывает аккумулятор после вызова, поэтому второй
|
// GetRXAMeter сбрасывает аккумулятор после вызова, поэтому второй
|
||||||
@@ -1475,12 +1485,14 @@ begin
|
|||||||
and (not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX))
|
and (not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX))
|
||||||
and (FPostTXMuteUntil = 0) then
|
and (FPostTXMuteUntil = 0) then
|
||||||
begin
|
begin
|
||||||
|
T0 := PerfNow; // PERF
|
||||||
for i := 0 to FAudioBufSize - 1 do
|
for i := 0 to FAudioBufSize - 1 do
|
||||||
begin
|
begin
|
||||||
FOutL[i] := FRXOut[i * 2] * FVolume;
|
FOutL[i] := FRXOut[i * 2] * FVolume;
|
||||||
FOutR[i] := FRXOut[i * 2 + 1] * FVolume;
|
FOutR[i] := FRXOut[i * 2 + 1] * FVolume;
|
||||||
end;
|
end;
|
||||||
FOnAudio(FOutL, FOutR, FAudioBufSize);
|
FOnAudio(FOutL, FOutR, FAudioBufSize);
|
||||||
|
PerfAdd(pzDspAudioCb, T0); // PERF: маршрут RX-аудио (web/радио/звуковуха)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Софтовые слайсы: fan-out того же IQ-блока на доп. WDSP-каналы.
|
// Софтовые слайсы: fan-out того же IQ-блока на доп. WDSP-каналы.
|
||||||
@@ -1588,8 +1600,10 @@ procedure TWDSPEngine.ProcessSlices;
|
|||||||
var
|
var
|
||||||
s, i, k, Err: Integer;
|
s, i, k, Err: Integer;
|
||||||
V: Double;
|
V: Double;
|
||||||
|
T0, TS: Int64; // PERF
|
||||||
begin
|
begin
|
||||||
if not Assigned(FOnSliceAudio) then Exit;
|
if not Assigned(FOnSliceAudio) then Exit;
|
||||||
|
T0 := PerfNow; // PERF
|
||||||
FSliceLock.Enter;
|
FSliceLock.Enter;
|
||||||
try
|
try
|
||||||
for s := 0 to MAX_SLICES - 1 do
|
for s := 0 to MAX_SLICES - 1 do
|
||||||
@@ -1603,7 +1617,9 @@ begin
|
|||||||
FSliceIn[i * 2 + 1] := FRXAccQ[i];
|
FSliceIn[i * 2 + 1] := FRXAccQ[i];
|
||||||
end;
|
end;
|
||||||
Err := 0;
|
Err := 0;
|
||||||
|
TS := PerfNow; // PERF
|
||||||
fexchange0(FSlices[s].Chan, @FSliceIn[0], @FSliceOut[0], @Err);
|
fexchange0(FSlices[s].Chan, @FSliceIn[0], @FSliceOut[0], @Err);
|
||||||
|
PerfAdd(pzDspSliceFex, TS); // PERF: fexchange0 одного слайса
|
||||||
// S-метр канала читаем здесь (DSP-поток) — GetRXAMeter сбрасывает аккумулятор.
|
// S-метр канала читаем здесь (DSP-поток) — GetRXAMeter сбрасывает аккумулятор.
|
||||||
FSlices[s].SMeter := GetRXAMeter(FSlices[s].Chan, RXA_S_AV);
|
FSlices[s].SMeter := GetRXAMeter(FSlices[s].Chan, RXA_S_AV);
|
||||||
V := FSlices[s].Volume;
|
V := FSlices[s].Volume;
|
||||||
@@ -1617,6 +1633,7 @@ begin
|
|||||||
finally
|
finally
|
||||||
FSliceLock.Leave;
|
FSliceLock.Leave;
|
||||||
end;
|
end;
|
||||||
|
PerfAdd(pzDspSlices, T0); // PERF: fan-out всех слайсов целиком
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TWDSPEngine.AddSlice(Id, Mode, FilterLo, FilterHi: Integer;
|
function TWDSPEngine.AddSlice(Id, Mode, FilterLo, FilterHi: Integer;
|
||||||
@@ -2273,8 +2290,10 @@ var
|
|||||||
i, tail: Integer;
|
i, tail: Integer;
|
||||||
Err: Integer;
|
Err: Integer;
|
||||||
Avail: Integer;
|
Avail: Integer;
|
||||||
|
T0: Int64; // PERF
|
||||||
begin
|
begin
|
||||||
if not FInitialized then Exit;
|
if not FInitialized then Exit;
|
||||||
|
T0 := PerfNow; // PERF
|
||||||
|
|
||||||
Avail := (FTXMicHead - FTXMicTail + TX_MIC_RING) and (TX_MIC_RING - 1);
|
Avail := (FTXMicHead - FTXMicTail + TX_MIC_RING) and (TX_MIC_RING - 1);
|
||||||
|
|
||||||
@@ -2314,6 +2333,7 @@ begin
|
|||||||
// FTXOut содержит FTXOutBufSize IQ пар @ FTXSampleRate (interleaved double)
|
// FTXOut содержит FTXOutBufSize IQ пар @ FTXSampleRate (interleaved double)
|
||||||
if Assigned(FOnTXIQ) then
|
if Assigned(FOnTXIQ) then
|
||||||
FOnTXIQ(FTXOut, FTXOutBufSize);
|
FOnTXIQ(FTXOut, FTXOutBufSize);
|
||||||
|
PerfAdd(pzDspTxBlock, T0); // PERF: TX-блок целиком (fexchange0 + analyzer + IQ out)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.PushTXMicSamples16(const Src: array of SmallInt; N: Integer);
|
procedure TWDSPEngine.PushTXMicSamples16(const Src: array of SmallInt; N: Integer);
|
||||||
@@ -2611,8 +2631,10 @@ var
|
|||||||
Flag: Integer;
|
Flag: Integer;
|
||||||
i, N: Integer;
|
i, N: Integer;
|
||||||
DispID: Integer;
|
DispID: Integer;
|
||||||
|
T0: Int64; // PERF
|
||||||
begin
|
begin
|
||||||
if not FInitialized or not FAnalyzerOpen then Exit;
|
if not FInitialized or not FAnalyzerOpen then Exit;
|
||||||
|
T0 := PerfNow; // PERF
|
||||||
N := EnsureRange(FDisplayPixelCount, 1, SPECTRUM_PIXELS);
|
N := EnsureRange(FDisplayPixelCount, 1, SPECTRUM_PIXELS);
|
||||||
DispID := FActiveDisplayID; // RX_DISP_ID или TX_DISP_ID, зависит от FTXActive
|
DispID := FActiveDisplayID; // RX_DISP_ID или TX_DISP_ID, зависит от FTXActive
|
||||||
|
|
||||||
@@ -2635,6 +2657,7 @@ begin
|
|||||||
if Assigned(FOnWaterfall) then
|
if Assigned(FOnWaterfall) then
|
||||||
FOnWaterfall(FWaterfallPixels, N);
|
FOnWaterfall(FWaterfallPixels, N);
|
||||||
end;
|
end;
|
||||||
|
PerfAdd(pzDispGetPixels, T0); // PERF: GetPixels + колбэки спектр/водопад
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.GetSpectrumData(var Pixels: array of Single;
|
procedure TWDSPEngine.GetSpectrumData(var Pixels: array of Single;
|
||||||
|
|||||||
Reference in New Issue
Block a user