mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
fix cpu
This commit is contained in:
+458
-428
File diff suppressed because it is too large
Load Diff
+53
-86
@@ -145,6 +145,7 @@ type
|
||||
FANFEnabled: Boolean;
|
||||
FMuted: Boolean;
|
||||
FVolume: Double;
|
||||
FLastError: string;
|
||||
|
||||
function ModeToWDSP(Mode: Integer): Integer;
|
||||
procedure ApplyDefaultFilter;
|
||||
@@ -211,6 +212,7 @@ type
|
||||
property Mode: Integer read FMode;
|
||||
property FilterLow: Integer read FFilterLow;
|
||||
property FilterHigh: Integer read FFilterHigh;
|
||||
property LastError: string read FLastError;
|
||||
property SMeter: Double read FSMeter;
|
||||
property OnAudio: TOnAudioReady read FOnAudio write FOnAudio;
|
||||
property OnSpectrum: TOnSpectrumReady read FOnSpectrum write FOnSpectrum;
|
||||
@@ -349,69 +351,31 @@ end;
|
||||
|
||||
procedure TWDSPEngine.OpenAnalyzer;
|
||||
var
|
||||
Success: Integer;
|
||||
MaxW: Integer;
|
||||
Ovrlp: Integer;
|
||||
Success: Integer;
|
||||
MaxW: Integer;
|
||||
Ovrlp: Integer;
|
||||
FRAME_RATE: Integer;
|
||||
begin
|
||||
if FAnalyzerOpen then Exit;
|
||||
|
||||
// ---- Шаг 1: создать анализатор ----
|
||||
// success = 0 означает УСПЕХ (WDSP Guide p.119)
|
||||
// m_size = максимальный FFT размер (должен быть степенью 2, ≤ 262144)
|
||||
// ОПТИМИЗАЦИЯ: 262144 → 16384. WDSP аллоцирует и инициализирует twiddle-таблицы
|
||||
// для ВСЕХ степеней 2 вплоть до m_size при каждом вызове XCreateAnalyzer.
|
||||
// Реальный рабочий FFT = 4096, запас ×4 = 16384 достаточен для будущих апгрейдов.
|
||||
// Эффект: время открытия WDSP ~10 сек → ~1 сек.
|
||||
// m_LO = 1 (нет spur elimination)
|
||||
// m_stitch = 1 (один sub-span)
|
||||
Success := -1;
|
||||
XCreateAnalyzer(DISP_ID, @Success, 16384, 1, 1, nil);
|
||||
if Success <> 0 then
|
||||
begin
|
||||
// Анализатор не создан — работаем без спектра, краша не будет
|
||||
Exit;
|
||||
end;
|
||||
if Success <> 0 then Exit;
|
||||
|
||||
// ---- Шаг 2: SetAnalyzer ----
|
||||
// Рассчитываем параметры по документации (WDSP Guide p.120-121)
|
||||
FRAME_RATE := 20; // 20 fps
|
||||
// max_w = fft_size + min(KEEP_TIME*sample_rate, KEEP_TIME*fft_size*frame_rate)
|
||||
// KEEP_TIME = 0.1
|
||||
// min(0.1*192000, 0.1*4096*20) = min(19200, 8192) = 8192
|
||||
MaxW := 4096 + 8192; // = 12288
|
||||
|
||||
// ovrlp = max(0, ceil(fft_size - sample_rate/frame_rate))
|
||||
// = max(0, ceil(4096 - 192000/20)) = max(0, 4096-9600) = 0
|
||||
FRAME_RATE := 20;
|
||||
// max_w: WDSP аллоцирует ring-buffer и FFT планы на max_w сэмплов.
|
||||
// Формула: fft_size + min(0.1*samplerate, 0.1*fft_size*fps)
|
||||
// = 4096 + min(19200, 8192) = 12288 → 1700ms инициализации!
|
||||
// Реально нам нужен только один FFT-кадр без усреднения по времени.
|
||||
// MaxW = sz = 4096 — минимально допустимое значение (должно быть ≥ sz).
|
||||
// piHPSDR использует именно sz без добавки keep_time.
|
||||
MaxW := 4096;
|
||||
Ovrlp := 0;
|
||||
|
||||
FFlp[0] := 0; // LO на нижней стороне, нет spur elimination
|
||||
SetAnalyzer(
|
||||
DISP_ID,
|
||||
1, // n_pixout: 1 (один выход — панадаптер и водопад одинаковые)
|
||||
1, // n_fft: 1 (без spur elimination)
|
||||
1, // typ: 1 = COMPLEX (I+Q)
|
||||
@FFlp[0], // flp: [0] — LO ниже сигнала
|
||||
4096, // sz: размер FFT
|
||||
FAudioBufSize, // bf_sz: блок данных @ AudioRate = dsp_size (1024 @ 48kHz)
|
||||
1, // win_type: 1 = 4-term Blackman-Harris
|
||||
14.0, // pi: Kaiser beta (не используется при BH)
|
||||
Ovrlp, // ovrlp: перекрытие = 0
|
||||
0, // clp: обрезка краёв = 0
|
||||
0.0, // fscLin: pan left = 0 (полный диапазон)
|
||||
0.0, // fscHin: pan right = 0 (полный диапазон)
|
||||
SPECTRUM_PIXELS, // n_pix: пикселей на выходе GetPixels
|
||||
1, // n_stch: 1 sub-span (нет stitching)
|
||||
0, // calset: нет калибровки
|
||||
0.0, // fmin
|
||||
0.0, // fmax
|
||||
MaxW // max_w: рассчитан выше = 12288
|
||||
);
|
||||
FFlp[0] := 0;
|
||||
SetAnalyzer(DISP_ID, 1, 1, 1, @FFlp[0], 4096, FAudioBufSize, 1, 14.0,
|
||||
Ovrlp, 0, 0.0, 0.0, SPECTRUM_PIXELS, 1, 0, 0.0, 0.0, MaxW);
|
||||
|
||||
// ---- Шаг 3: настройка детектора и усреднения ----
|
||||
// DETECTOR_MODE_AVERAGE + LOG_RECURSIVE — плавный, как в piHPSDR/Thetis
|
||||
// backmult ~0.45 = инерция ~2.2 кадра → плавно, без смазывания быстрых сигналов
|
||||
// NumAverage = FRAME_RATE * 0.12 ≈ 2..3 при 20fps — достаточно для LOG_RECURSIVE
|
||||
SetDisplayDetectorMode(DISP_ID, 0, DETECTOR_MODE_AVERAGE);
|
||||
SetDisplayAverageMode(DISP_ID, 0, AVERAGE_MODE_LOG_RECURSIVE);
|
||||
SetDisplayNumAverage(DISP_ID, 0, Max(2, Trunc(FRAME_RATE * 0.12)));
|
||||
@@ -434,66 +398,65 @@ end;
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function TWDSPEngine.Open: Boolean;
|
||||
var
|
||||
DllPath: string;
|
||||
begin
|
||||
Result := False;
|
||||
FLastError := '';
|
||||
if FInitialized then Exit;
|
||||
|
||||
// Проверяем что libwdsp загружена (WEAKEXTERNALSYMBOLS: nil если нет DLL)
|
||||
if not Assigned(@OpenChannel) then
|
||||
begin
|
||||
// wdsp.dll / libwdsp.so не найдена — демо-режим
|
||||
FInitialized := False;
|
||||
DllPath := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0))) + WDSP_LIB;
|
||||
if FileExists(DllPath) then
|
||||
FLastError := Format('Symbols from %s are not resolved. Check architecture (x64/x86) and dependencies.', [DllPath])
|
||||
else
|
||||
FLastError := Format('WDSP library not found: %s', [DllPath]);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
try
|
||||
// ОПТИМИЗАЦИЯ: Анализатор создаём ПЕРВЫМ — до OpenChannel.
|
||||
// Это соответствует порядку piHPSDR и гарантирует что DISP_ID готов
|
||||
// до того как DSP поток начнёт вызывать Spectrum0.
|
||||
// Анализатор ПЕРВЫМ — до OpenChannel (порядок как в piHPSDR)
|
||||
OpenAnalyzer;
|
||||
// НЕ используем SetRXASpectrum — кормим анализатор вручную
|
||||
// через Spectrum0 с Double буфером, чтобы избежать SIGSEGV
|
||||
// (fexchange0 работает с Single, а внутри WDSP Spectrum2 ждёт Double)
|
||||
if not FAnalyzerOpen then
|
||||
begin
|
||||
FLastError := 'XCreateAnalyzer failed (WDSP init failed).';
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// ---- RXA channel 0 ----
|
||||
// ОПТИМИЗАЦИЯ: state=0 (не запускать сразу) — настраиваем все параметры
|
||||
// вхолостую, только потом стартуем через SetChannelState.
|
||||
// Канал не тратит CPU на обработку "мусорных" данных в процессе настройки.
|
||||
OpenChannel(
|
||||
RXA_CHAN,
|
||||
FBufSize, // in_size @ FSampleRate (4096 @ 192kHz)
|
||||
FAudioBufSize, // dsp_size @ AudioRate (1024 @ 48kHz) — как piHPSDR buffer_size
|
||||
FBufSize,
|
||||
FAudioBufSize,
|
||||
FSampleRate,
|
||||
FAudioRate, // dsp_rate = 48kHz
|
||||
FAudioRate, // out_rate = 48kHz
|
||||
0, // atype: 0=RX
|
||||
0, // state: 0=hold (запустим после настройки всех параметров)
|
||||
FAudioRate,
|
||||
FAudioRate,
|
||||
0,
|
||||
0, // state=0: создать но не запускать
|
||||
0.010, 0.010, 0.010, 0.010,
|
||||
0
|
||||
);
|
||||
|
||||
SetRXAMode(RXA_CHAN, ModeToWDSP(FMode));
|
||||
SetRXAShiftRun(RXA_CHAN, 1); // включаем NCO шифтер (нужен для CTUN)
|
||||
SetRXAShiftFreq(RXA_CHAN, 0.0); // по умолчанию сдвига нет
|
||||
SetRXAShiftRun(RXA_CHAN, 1);
|
||||
SetRXAShiftFreq(RXA_CHAN, 0.0);
|
||||
RXASetPassband(RXA_CHAN, FFilterLow, FFilterHigh);
|
||||
SetAGC(FAGCMode, 50.0); // настраиваем AGC с полными параметрами piHPSDR
|
||||
SetAGC(FAGCMode, 50.0);
|
||||
SetRXAPanelGain1(RXA_CHAN, FVolume);
|
||||
SetRXAPanelSelect(RXA_CHAN, 3);
|
||||
SetRXAPanelRun(RXA_CHAN, 1);
|
||||
SetChannelState(RXA_CHAN, 1, 0);
|
||||
|
||||
// Запускаем RXA канал только после полной настройки
|
||||
SetChannelState(RXA_CHAN, 1, 0); // run, no delay-wait
|
||||
|
||||
// ---- TXA channel 1 ----
|
||||
OpenChannel(
|
||||
TXA_CHAN,
|
||||
FBufSize,
|
||||
FBufSize,
|
||||
FAudioBufSize,
|
||||
FAudioBufSize,
|
||||
FAudioRate,
|
||||
48000,
|
||||
FSampleRate,
|
||||
1, // atype: 1=TX
|
||||
0, // state: 0=hold
|
||||
FAudioRate,
|
||||
FAudioRate,
|
||||
1,
|
||||
0,
|
||||
0.010, 0.010, 0.010, 0.010,
|
||||
0
|
||||
);
|
||||
@@ -519,8 +482,12 @@ begin
|
||||
FDSPThread.Priority := tpHighest; // DSP критический поток
|
||||
|
||||
except
|
||||
FInitialized := False;
|
||||
Result := False;
|
||||
on E: Exception do
|
||||
begin
|
||||
FLastError := E.ClassName + ': ' + E.Message;
|
||||
FInitialized := False;
|
||||
Result := False;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
+5
-8
@@ -1046,13 +1046,13 @@ var
|
||||
i, n, Enc: Integer;
|
||||
Pkt: array of Byte;
|
||||
begin
|
||||
if not FRunning then begin AudioLog('EXIT: FRunning=false'); Exit; end;
|
||||
if not FRunning then Exit;
|
||||
FClientLock.Enter;
|
||||
HasWs := FClientCount > 0;
|
||||
FClientLock.Leave;
|
||||
if not HasWs then begin AudioLog('EXIT: FClientCount=0'); Exit; end;
|
||||
if (Samples = nil) or (Count <= 0) then begin AudioLog('EXIT: Samples nil/Count=0'); Exit; end;
|
||||
if not FOpusReady then begin AudioLog('EXIT: FOpusReady=false'); Exit; end;
|
||||
if not HasWs then Exit;
|
||||
if (Samples = nil) or (Count <= 0) then Exit;
|
||||
if not FOpusReady then Exit;
|
||||
|
||||
i := 0;
|
||||
while i < Count do
|
||||
@@ -1073,10 +1073,7 @@ begin
|
||||
Pkt[0] := WS_MSG_AUDIO;
|
||||
Move(FOpusOut[0], Pkt[1], Enc);
|
||||
BroadcastBinary(Pkt[0], Length(Pkt));
|
||||
AudioLog('SENT opus bytes=' + IntToStr(Enc) + ' clients=' + IntToStr(FClientCount));
|
||||
end
|
||||
else
|
||||
AudioLog('ENCODE FAILED Enc=' + IntToStr(Enc));
|
||||
end;
|
||||
FOpusBufPos := 0;
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -6,7 +6,6 @@ unit WebUtils;
|
||||
- SHA-1 (минимальная реализация для WebSocket handshake)
|
||||
- Base64 (кодирование)
|
||||
- JSON helpers (JsonGetStr, JsonGetFloat, JsonGetInt, JsonGetBool)
|
||||
- AudioLog (отладочное логирование аудио)
|
||||
|
||||
ИСПРАВЛЕНИЯ:
|
||||
- (Windows build fix) Порядок uses: стандартные RTL-юниты первыми,
|
||||
@@ -76,10 +75,6 @@ function JsonGetFloat(const Json, Key: string; Def: Double): Double;
|
||||
function JsonGetInt(const Json, Key: string; Def: Integer): Integer;
|
||||
function JsonGetBool(const Json, Key: string; Def: Boolean): Boolean;
|
||||
|
||||
{ ── Отладочное логирование аудио ─────────────────────────────────────────── }
|
||||
|
||||
procedure AudioLog(const S: string);
|
||||
|
||||
implementation
|
||||
|
||||
{ ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -318,20 +313,4 @@ begin
|
||||
else Result := (S = 'true') or (S = '1');
|
||||
end;
|
||||
|
||||
{ ═══════════════════════════════════════════════════════════════════════════
|
||||
Отладочное логирование аудио
|
||||
═══════════════════════════════════════════════════════════════════════════ }
|
||||
|
||||
procedure AudioLog(const S: string);
|
||||
var F: TextFile;
|
||||
begin
|
||||
try
|
||||
AssignFile(F, 'audio_debug.log');
|
||||
if FileExists('audio_debug.log') then Append(F) else Rewrite(F);
|
||||
WriteLn(F, FormatDateTime('hh:nn:ss.zzz', Now) + ' ' + S);
|
||||
CloseFile(F);
|
||||
except
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+3
-3
@@ -285,7 +285,7 @@
|
||||
"span_hz" : 1.9200000000000000E+005
|
||||
},
|
||||
"1" : {
|
||||
"vfo_a" : 3.6210000000000000E+006,
|
||||
"vfo_a" : 3.6870000000000000E+006,
|
||||
"vfo_b" : 3.7500000000000000E+006,
|
||||
"mode" : 0,
|
||||
"filter_idx" : 5,
|
||||
@@ -307,7 +307,7 @@
|
||||
"span_hz" : 1.9200000000000000E+005
|
||||
},
|
||||
"3" : {
|
||||
"vfo_a" : 7.1620000000000000E+006,
|
||||
"vfo_a" : 7.1410000000000000E+006,
|
||||
"vfo_b" : 7.1000000000000000E+006,
|
||||
"mode" : 0,
|
||||
"filter_idx" : 5,
|
||||
@@ -329,7 +329,7 @@
|
||||
"span_hz" : 1.9200000000000000E+005
|
||||
},
|
||||
"5" : {
|
||||
"vfo_a" : 1.4163000000000000E+007,
|
||||
"vfo_a" : 1.4189000000000000E+007,
|
||||
"vfo_b" : 1.4200000000000000E+007,
|
||||
"mode" : 1,
|
||||
"filter_idx" : 5,
|
||||
|
||||
Reference in New Issue
Block a user