12dc701604
Two pieces of pure overhead, neither of them doing any DSP.
The POSIX QueueUserWorkItem() shim spawned a thread and immediately joined
it. That is a synchronous call -- the parallelism the Windows thread pool
provides is absent here either way -- so it only bought a pthread_create()
and its stack mmap, ~15 us, for every FFT frame. Call the function directly.
(The Windows build is untouched and still gets its thread pool; a real pool
for POSIX would be a separate change, and only pays off for num_stitch > 1.)
The dispatcher thread ran `for (each ss, LO) {...} Sleep(1);`, and Sleep(1)
is usleep(1000), so it woke 1000 times a second to re-read the same flags.
That burned 0.63% of a core even with no samples arriving. Give it a
semaphore instead, signalled by the four Spectrum*() entry points when new
samples land, and by SetAnalyzer/DestroyAnalyzer after they raise
end_dispatcher so the blocking wait always has a way out. SetAnalyzer holds
SetAnalyzerSection while it waits for the dispatcher to quit, and the
dispatcher never takes that section, so signalling from under it is safe.
Measured on an Apple M1 Pro; 16384-point complex FFT, 1024-sample buffers,
~40 pixel frames/s, CPU of all threads via getrusage:
idle, no samples at all 0.63% of a core -> 0.00%
under load 3.54% -> 2.65%
Output pixels are bit-identical. Stressed with 3 create/destroy cycles and
24 on-the-fly SetAnalyzer reconfigurations while a second thread fed samples:
no deadlock.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
219 lines
10 KiB
C
219 lines
10 KiB
C
/* analyzer.h
|
|
|
|
This file is part of a program that implements a Spectrum Analyzer
|
|
used in conjunction with software-defined-radio hardware.
|
|
|
|
Copyright (C) 2012, 2013, 2014, 2016, 2023, 2025 Warren Pratt, NR0V
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
The author can be reached by email at
|
|
|
|
warren@wpratt.com
|
|
|
|
*/
|
|
#ifndef _analyzer_h
|
|
#define _analyzer_h
|
|
#include "comm.h"
|
|
|
|
typedef struct _dp
|
|
{
|
|
int max_size; // maximum fft size to be used
|
|
int max_num_fft; // maximum number of LO positions per sub-span to be used
|
|
int max_stitch; // maximum number of sub-spans to be concatenated
|
|
// NOTE: max_size, max_num_fft, and max_stitch MUST BE <= THE
|
|
// CORRESPONDING VALUES IN <analyzer.h>!!
|
|
int num_fft; // current number of ffts in use
|
|
int num_pixout; // current number of detector/averages/pixel value outputs
|
|
int size; // current size of fft input sample vector
|
|
int out_size; // current size of fft output vector
|
|
int window_type; // type of the window function to be applied
|
|
int overlap; // number of samples re-used per fft, range 0 to size-1
|
|
int flip[dMAX_NUM_FFT]; // 0 for low-side LO => do NOT flip; 1 for high-side LO => FLIP
|
|
int clip; // number of bins to clip off on EACH end of the sub-span fft
|
|
// ASSUMES size/2 IS AN EVEN NUMBER!!!
|
|
double fsclipL; // number of intervals to clip off the lower end of the TOTAL SPAN
|
|
double fsclipH; // number of intervals to clip off the upper end of the TOTAL SPAN
|
|
int fscL; // fsclipL modulo (out_size - 2 * clip)
|
|
int fscH; // fsclipH modulo (out_size - 2 * clip)
|
|
int begin_ss; // number of first sub-span that is NOT completely clipped off
|
|
int end_ss; // number of last sub-span that is NOT completely clipped off
|
|
int ss_bins[dMAX_STITCH]; // number of bins delivered by eliminate()/Celiminate in each sub-span
|
|
volatile LONG input_busy[dMAX_STITCH][dMAX_NUM_FFT];
|
|
int num_pixels; // number of pixels requested
|
|
int num_stitch; // number of results to be stitched together to generate the pixel frame
|
|
unsigned long long stitch_flag;
|
|
int spec_flag[dMAX_STITCH]; // flags showing if all ffts for a sub-span are done so elimination can proceed
|
|
double pix_per_bin; // number of pixels per fft bin, note that this is fractional, not integral
|
|
double det_offset; // offset needed in detector
|
|
double bin_per_pix; // number of fft bins per pixel, this is fractional and != 1.0/pix_per_bin
|
|
double scale; // output amplitude scale factor
|
|
double PiAlpha; // parameter for Kaiser window function
|
|
|
|
int cal_set; // specifies which set of calibration data to use
|
|
double f_min; // frequency at first pixel (for calibration)
|
|
double f_max; // frequency at last pixel (for calibration)
|
|
int cal_changed; // flag to indicate that the calibration data has changed
|
|
|
|
double *window; // pointer to buffer to hold window coefficients
|
|
double *result[dMAX_STITCH]; // pointers to buffer to hold elimination results for each sub-span
|
|
dOUTREAL *pixels[dMAX_PIXOUTS][dNUM_PIXEL_BUFFS]; // pointers pixel output buffers
|
|
double *t_pixels[dMAX_PIXOUTS]; // pointer to temporary pixel buffer //pointer to temporary pixel buffer for non-averaged data
|
|
int w_pix_buff[dMAX_PIXOUTS]; // number of pixel buffer owned by writing process
|
|
int r_pix_buff[dMAX_PIXOUTS]; // number of pixel buffer owned by reading process
|
|
int last_pix_buff[dMAX_PIXOUTS]; // number of the last pixel buffer written
|
|
volatile LONG pb_ready[dMAX_PIXOUTS][dNUM_PIXEL_BUFFS]; // if value is 0, this data has already been read; 1 = fresh data to read
|
|
int num_average[dMAX_PIXOUTS]; // number of spans to average to create the pixels
|
|
int avail_frames[dMAX_PIXOUTS]; // number of pixel frames currently available to average
|
|
int av_in_idx[dMAX_PIXOUTS]; // input index in averaging pixel buffer ring
|
|
int av_out_idx[dMAX_PIXOUTS]; // output index in averaging pixel buffer ring
|
|
double *av_sum[dMAX_PIXOUTS]; // pointer to sum buffer for averaging
|
|
double *av_buff[dMAX_PIXOUTS][dMAX_AVERAGE]; // pointers to ring of buffers to hold pixel frames for averaging
|
|
double *pre_av_out;
|
|
int av_mode[dMAX_PIXOUTS];
|
|
double av_backmult[dMAX_PIXOUTS]; // back multiplier for weighted averaging
|
|
double *cd; // pointer to amplitude calibration buffer
|
|
int n_freqs[dMAX_CAL_SETS]; // number of frequencies in each calibration set
|
|
double *freqs[dMAX_CAL_SETS]; // pointers to vectors of calibration frequencies
|
|
double (*ac3[dMAX_CAL_SETS][dMAX_M]); // pointers to amplitude interpolant coefficients
|
|
double (*ac2[dMAX_CAL_SETS][dMAX_M]);
|
|
double (*ac1[dMAX_CAL_SETS][dMAX_M]);
|
|
double (*ac0[dMAX_CAL_SETS][dMAX_M]);
|
|
|
|
fftw_plan plan[dMAX_STITCH][dMAX_NUM_FFT]; // fftw plans
|
|
fftw_plan Cplan[dMAX_STITCH][dMAX_NUM_FFT];
|
|
double *fft_in[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to fftw real input vectors
|
|
fftw_complex *Cfft_in[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to fftw complex input vectors
|
|
fftw_complex *fft_out[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to fftw complex output vectors
|
|
volatile LONG *pnum_threads; // pointer to current number of active worker threads
|
|
int stop; // when set, fft threads will be returned to the pool
|
|
int end_dispatcher; // set this flag to one to destroy the dispatcher thread
|
|
volatile int dispatcher; // one if the dispatcher thread is alive & active
|
|
int ss; // sub-span being processed
|
|
int LO; // LO (within current sub-span) being processed
|
|
int flag;
|
|
int have_samples[dMAX_STITCH][dMAX_NUM_FFT]; // number of unused samples remaining in a buffer
|
|
int type; // 0 for REAL, 1 for COMPLEX
|
|
int incr; // size - overlap
|
|
int buff_size; // amount of data to be stored each time an input buffer is opened and closed = JanusAudio/BlockSize
|
|
dINREAL* I_samples[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to current input position in I/Q buffers
|
|
dINREAL* Q_samples[dMAX_STITCH][dMAX_NUM_FFT];
|
|
int bsize; // size of I_samples[][] and Q_samples[][] (number of samples they hold)
|
|
int IQout_index[dMAX_STITCH][dMAX_NUM_FFT]; // current output index for I_samples[ss][LO] and Q_samples[ss][LO]
|
|
int IQO_idx[dMAX_STITCH][dMAX_NUM_FFT];
|
|
int IQin_index[dMAX_STITCH][dMAX_NUM_FFT]; // current input index for I_samples[ss][LO] and Q_samples[ss][LO]
|
|
volatile LONG buff_ready[dMAX_STITCH][dMAX_NUM_FFT]; // 1 if buffer ready to read; 0 if needs to be filled
|
|
int max_writeahead; // max allowed input samples ahead of where reading output samples
|
|
|
|
volatile LONG snap[dMAX_STITCH][dMAX_NUM_FFT]; // set to 1 to allow a snap of raw spectrum data
|
|
HANDLE hSnapEvent[dMAX_STITCH][dMAX_NUM_FFT]; // mutex handles; mutexes will be used to signal a snap is complete
|
|
double *snap_buff[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to buffers for the snap
|
|
|
|
HANDLE Sem_BuffReady; // signalled when input samples arrive, so the
|
|
// dispatcher can block instead of polling
|
|
|
|
CRITICAL_SECTION PB_ControlsSection[dMAX_PIXOUTS];
|
|
CRITICAL_SECTION SetAnalyzerSection;
|
|
CRITICAL_SECTION BufferControlSection[dMAX_STITCH][dMAX_NUM_FFT];
|
|
CRITICAL_SECTION StitchSection;
|
|
CRITICAL_SECTION EliminateSection[dMAX_STITCH];
|
|
CRITICAL_SECTION ResampleSection;
|
|
|
|
int det_type[dMAX_PIXOUTS]; // detector type
|
|
double inv_coherent_gain;
|
|
double inherent_power_gain;
|
|
double inv_enb;
|
|
double norm_oneHz; // dB factor to normalize to one Hz bandwidth
|
|
int sample_rate; // sample rate; used for normalization calculations
|
|
int normalize[dMAX_PIXOUTS];
|
|
|
|
// BEGIN CODE TO GET MAX FFT_BIN WITHIN A FREQUENCY RANGE
|
|
int dmb_run;
|
|
int dmb_disp;
|
|
int dmb_ss;
|
|
int dmb_LO;
|
|
double dmb_rate;
|
|
double dmb_fLow;
|
|
double dmb_fHigh;
|
|
double dmb_tau;
|
|
int dmb_frame_rate;
|
|
int dmb_begin0;
|
|
int dmb_end0;
|
|
int dmb_begin1;
|
|
int dmb_end1;
|
|
double dmb_decay;
|
|
double dmb_max_dB;
|
|
CRITICAL_SECTION cs_dmb;
|
|
// END CODE TO GET MAX FFT_BIN WITHIN A FREQUENCY RANGE
|
|
|
|
} dp, *DP;
|
|
|
|
extern DP pdisp[];
|
|
|
|
extern __declspec( dllexport )
|
|
void CreateAnalyzer ( int disp,
|
|
int *success,
|
|
char *app_data_path);
|
|
|
|
extern __declspec( dllexport )
|
|
void XCreateAnalyzer ( int disp,
|
|
int *success, //writes '0' to success if all went well, <0 if mem alloc failed
|
|
int m_size, //maximum fft size to be used
|
|
int m_LO, //maximum number of LO positions per subspan
|
|
int m_stitch, //maximum number of subspans to be concatenated
|
|
char *app_data_path
|
|
);
|
|
|
|
extern __declspec( dllexport )
|
|
void DestroyAnalyzer(int disp);
|
|
|
|
extern __declspec( dllexport )
|
|
void SetCalibration ( int disp,
|
|
int set_num, //identifier for this calibration data set
|
|
int n_points, //number of calibration points in the set
|
|
double (*cal)[dMAX_M+1] //pointer to the calibration table, first
|
|
);
|
|
|
|
extern __declspec( dllexport )
|
|
void OpenBuffer(int disp, int ss, int LO, void **Ipointer, void **Qpointer);
|
|
|
|
extern __declspec( dllexport )
|
|
void CloseBuffer(int disp, int ss, int LO);
|
|
|
|
extern __declspec( dllexport )
|
|
void Spectrum(int disp, int ss, int LO, dINREAL* pI, dINREAL* pQ);
|
|
|
|
extern __declspec( dllexport )
|
|
void Spectrum2(int run, int disp, int ss, int LO, dINREAL* pbuff);
|
|
|
|
extern __declspec( dllexport )
|
|
void Spectrum0(int run, int disp, int ss, int LO, double* pbuff);
|
|
|
|
extern __declspec( dllexport )
|
|
void SnapSpectrum( int disp,
|
|
int ss,
|
|
int LO,
|
|
double *snap_buff);
|
|
|
|
extern __declspec( dllexport )
|
|
void SnapSpectrumTimeout (int disp,
|
|
int ss,
|
|
int LO,
|
|
double* snap_buff,
|
|
DWORD timeout,
|
|
int* flag);
|
|
|
|
#endif
|