FT8CN/ft8CN/app/src/main/cpp/monitor_opr.c

258 wiersze
13 KiB
C
Czysty Wina Historia

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

//
// Created by jmsmf on 2022/4/22.
//
#include "monitor_opr.h"
#include "ft8/constants.h"
//#define LOG_LEVEL LOG_DEBUG
#define LOG_LEVEL LOG_FATAL
// FFT转换采用加窗函数减少频谱泄露的问题。
// Hanning窗汉宁窗适用于95%的情况。
static float hann_i(int i, int N) {
float x = sinf((float) M_PI * i / N);
return x * x;
}
static float hamming_i(int i, int N) {
const float a0 = (float) 25 / 46;
const float a1 = 1 - a0;
float x1 = cosf(2 * (float) M_PI * i / N);
return a0 - a1 * x1;
}
static float blackman_i(int i, int N) {
const float alpha = 0.16f; // or 2860/18608
const float a0 = (1 - alpha) / 2;
const float a1 = 1.0f / 2;
const float a2 = alpha / 2;
float x1 = cosf(2 * (float) M_PI * i / N);
float x2 = 2 * x1 * x1 - 1; // Use double angle formula
return a0 - a1 * x1 + a2 * x2;
}
static void
waterfall_init(waterfall_t *me, int max_blocks, int num_bins, int time_osr, int freq_osr) {
//mag_size信号量数组的大小。最大块数93*时间过采样率2*频率过采样率2*分析块960*sizeOf(U_int8)
size_t mag_size = max_blocks * time_osr * freq_osr * num_bins * sizeof(me->mag[0]);
me->max_blocks = max_blocks;
me->num_blocks = 0;
me->num_bins = num_bins;//num_bins = 12000 * 0.16 / 2 = 960
me->time_osr = time_osr;
me->freq_osr = freq_osr;
me->block_stride = (time_osr * freq_osr * num_bins);
me->mag = (uint8_t *) malloc(mag_size);//申请信号量数组用于计算得分357120个
me->mag2 = (float *) malloc(mag_size * sizeof(float));//申请信号量数组,用于计算信噪比
LOG(LOG_DEBUG, "Waterfall size = %zu\n", mag_size);
}
static void waterfall_free(waterfall_t *me) {
free(me->mag);
free(me->mag2);
}
void monitor_init(monitor_t *me, const monitor_config_t *cfg) {
LOG(LOG_DEBUG, "Monitor is initializing...");
//协议的时长FT8_SLOT_TIME=15.0fFT4_SLOT_TIME=7.5f
float slot_time = (cfg->protocol == PROTO_FT4) ? FT4_SLOT_TIME : FT8_SLOT_TIME;
//协议每个符号的时长FT8_SYMBOL_PERIOD=0.160fFT4_SYMBOL_PERIOD=0.048f
float symbol_period = (cfg->protocol == PROTO_FT4) ? FT4_SYMBOL_PERIOD : FT8_SYMBOL_PERIOD;
//**************************************************
// Compute DSP parameters that depend on the sample rate
// 根据采样率计算DSP参数
// block_size每一个FSK符号占用的样本数FT8:12000*0.16=1920个
// subblock_size分析移动的大小样本数,每符号样本数/时间过采样率。FT8:1920/2=960
// nfftfft size。fft大小=每个FSK符号占用的样本数*频率过采样率=1920*2
// fft_normFFT归一化因子。2/fft size。
me->block_size = (int) (cfg->sample_rate *
symbol_period); //12000*0.16 对应于一个FSK符号的样本=1920
me->subblock_size = me->block_size / cfg->time_osr;//移动的样本数。一个FSK符号的样本数/过采样率
//nfft是傅里叶变换前时域的实数序列的数量。目前是一个FSK符号的样本数*频率过采样率
me->nfft = me->block_size * cfg->freq_osr;//nfft=一个FSK符号的样本数*频率过采样率
me->fft_norm = 2.0f / me->nfft;//< FFT归一化因子。FFT normalization factor
// const int len_window = 1.8f * me->block_size; // hand-picked and optimized
//**************************************************
// 申请窗空间大小是fft块大小*me->windows[0]的大小
// 采集周期如果是实际信号的非整数时,端点是不连续的。
// 这些不连续片段在FFT中显示为高频成分。这些高频成分不存在于原信号中。
// 这些频率可能远高于奈奎斯特频率在0 采样率的一半的频率区间内产生混叠。
// 使用FFT获得的频率不是原信号的实际频率而是一个改变过的频率。
// 类似于某个频率的能量泄漏至其他频率。 这种现象叫做频谱泄漏。
// 频率泄漏使好的频谱线扩散到更宽的信号范围中。这些不连续片段在FFT中显示为高频成分。
// 通过加窗来尽可能减少在非整数个周期上进行FFT产生的误差。
// 加窗可减少这些不连续部分的幅值。
// 加窗包括将时间记录乘以有限长度的窗窗的幅值逐渐变小在边沿处为0。
// 加窗的结果是尽可能呈现出一个连续的波形,减少剧烈的变化。 这种方法也叫应用一个加窗。
// 窗函数分很多种常见的的如Hamming窗、Hanning窗、Blackman-Harris窗、Kaiser-Bessel窗、Flat top窗。
// Hanning窗适用于95%的情况。
me->window = (float *) malloc(
me->nfft * sizeof(me->window[0]));// 申请窗空间大小是fft块大小*sizeof(me->windows[0])
//此处是窗函数的设置使用常用的hanning窗
for (int i = 0; i < me->nfft; ++i) {
// window[i] = 1;
me->window[i] = hann_i(i, me->nfft);// 使用Hanning窗
// me->window[i] = blackman_i(i, me->nfft);// Blackman-Harris窗
// me->window[i] = hamming_i(i, me->nfft);// Hamming窗
// me->window[i] = (i < len_window) ? hann_i(i, len_window) : 0;
}
// 申请当前STFT(短时傅氏变换)分析框架nfft样本的空间。
//last_frame:申请傅里叶变换分析框架用的nfft样本
// 空间大小=时域数据量*数据类型占用的空间=一个FSK符号占用的采样数据量*频率过采样率=12000*0.16*2*SizeOf(float)
me->last_frame = (float *) malloc(me->nfft * sizeof(me->last_frame[0]));
// size_t 类型定义在cstddef头文件中该文件是C标准库的头文件stddef.h的C++版。
// 它是一个与机器相关的unsigned类型其大小足以保证存储内存中对象的大小。
size_t fft_work_size;
// 第一步获取可以用的FFT工作区域的大小到fft_work_size
//nfft=一个FSK符号的样本数*频率过采样率=0.16*12000*2=3840
kiss_fftr_alloc(me->nfft, 0, 0, &fft_work_size);
//fft_work_size
// 申请FFT工作区域的内存38676个
me->fft_work = malloc(fft_work_size);
//第二步返回fft的设置信息
me->fft_cfg = kiss_fftr_alloc(me->nfft, 0, me->fft_work, &fft_work_size);
// 最大块数FT8的周期时长/每个符号时长FT8:15/0.16 =93
const int max_blocks = (int) (slot_time / symbol_period);
// num_bins如果以6.25 Hz为单位的FFT箱数量。6.25每秒6.25个FSK符号0.16*6.25=1。
// num_bins = 12000 * 0.16 / 2 = 960。2是啥是生成的频域数据量是采集数据量的一半
const int num_bins = (int) (cfg->sample_rate * symbol_period / 2);
//初始化瀑布图。
waterfall_init(&me->wf, max_blocks, num_bins, cfg->time_osr, cfg->freq_osr);
me->wf.protocol = cfg->protocol;
me->symbol_period = symbol_period;
me->max_mag = -120.0f;
LOG(LOG_INFO, "Block size = %d\n", me->block_size);
LOG(LOG_INFO, "Subblock size = %d\n", me->subblock_size);
LOG(LOG_INFO, "N_FFT = %d\n", me->nfft);
LOG(LOG_DEBUG, "FFT work area = %zu\n", fft_work_size);
// 瀑布图中最多能申请max_blocks个块数
LOG(LOG_DEBUG, "Waterfall allocated %d symbols\n", me->wf.max_blocks);
}
void monitor_free(monitor_t *me) {
waterfall_free(&me->wf);
free(me->fft_work);
free(me->last_frame);
free(me->window);
LOG(LOG_DEBUG, "Monitor is free .");
}
// Compute FFT magnitudes (log wf) for a frame in the signal and update waterfall data
// 计算信号中一帧的FFT幅度log wf并更新瀑布数据
void monitor_process(monitor_t *me, const float *frame) {
// Check if we can still store more waterfall data
//防止溢出
//mag阵列中存储的块符号编号
if (me->wf.num_blocks >= me->wf.max_blocks)
return;
//num_bins 的值是 12000 * 0.16 / 2 = 960
//wf.block_strid= (time_osr * freq_osr * num_bins)=2*2*960
//offset是在mag数组中的偏移量。wf.num_blocks是当前符号的块编号以num_bins(数据片段)*时间过采样*频率过采样为单位。
//mag的数组大小实际上是时间过采样*频率过采样*符号的最大量93*每符号真实采样数据过采样的数据960.
int offset = me->wf.num_blocks * me->wf.block_stride;
int frame_pos = 0;
// Loop over block subdivisions
//循环块细分wf.time_osr=2 时间过采样率
for (int time_sub = 0; time_sub < me->wf.time_osr; ++time_sub) {
kiss_fft_scalar timedata[me->nfft];
kiss_fft_cpx freqdata[me->nfft / 2 + 1];
// Shift the new data into analysis frame
//将新数据转移到分析框架中。
// subblock_size分析移动的大小样本数blocksize/2 每秒块数/时间过采样率=FT8:12000*0.16/2=1920/2=960个
//last_frame的空间已经申请好了。空间大小=时域数据量*数据类型占用的空间=一个FSK符号占用的采样数据量*频率过采样率=12000*0.16*2*SizeOf(float)=3840
//nfft=一个FSK符号的样本数*频率过采样率=1920*2=3840
//subblock_size。移动的样本数。一个FSK符号的样本数/过采样率,1920/2=960
//第一个循环把过采样的后半段数据向前移960个数据
//第二个循环把新的声音数据导入到last_frame的后半部分新的声音数据960个。外面的时间过采样率循环2遍正好960*2=1920,一个符号
//这样就可以对一个符号周期的时域数据做傅里叶变换了
for (int pos = 0; pos < me->nfft - me->subblock_size; ++pos) {
me->last_frame[pos] = me->last_frame[pos + me->subblock_size];
}
for (int pos = me->nfft - me->subblock_size; pos < me->nfft; ++pos) {
me->last_frame[pos] = frame[frame_pos];
++frame_pos;
}
// Compute windowed analysis frame
//用窗函数做一次转换,汉宁窗。
for (int pos = 0; pos < me->nfft; ++pos) {
//把last_frame中的数据赋值到timedata中来timedata是时域数据要做一次归一化、窗函数处理
timedata[pos] = me->fft_norm * me->window[pos] * me->last_frame[pos];
//timedata[pos] =me->window[pos] * me->last_frame[pos];
}
//傅里叶变换把timedata的时域数据长度是nfft转换到频域数据上来。频域数据是复数数组数组长度是nfft/2+1
//nfft=一个FSK符号的样本数*频率过采样率=12000*0.16*2=3840
kiss_fftr(me->fft_cfg, timedata, freqdata);
// Loop over two possible frequency bin offsets (for averaging)
//在两个可能的频率单元偏移上循环(用于平均)
//两个循环的意义是在一个符号采样数据的范围内12000*0.16对freqdata的能量做计算
for (int freq_sub = 0; freq_sub < me->wf.freq_osr; ++freq_sub) {
for (int bin = 0; bin < me->wf.num_bins; ++bin) { //num_bins 的值是 12000 * 0.16 / 2 = 960
//循环次数=2*960=1920
//信号量位置src_bin,
int src_bin = (bin * me->wf.freq_osr) + freq_sub;
////此位置可能是计算信噪比的位置
//求各频率点上的信号强度是傅里叶之后的平方少了开方mag2应当是信号量的平方
float mag2 = (freqdata[src_bin].i * freqdata[src_bin].i) +
(freqdata[src_bin].r * freqdata[src_bin].r);
float db = 10.0f * log10f(1E-12f + mag2);
//把信号量保存下来
//offset=me->wf.num_blocks * me->wf.block_stride;
//wf.block_strid= (time_osr * freq_osr * num_bins)=2*2*960
//偏移量就是当前块编号*每个符号ftt数据量
me->wf.mag2[offset] = mag2;
//每循环一次偏移量移一位。共移动time_osr*freq_osr*num_bins=2*2*960=3840
// Scale decibels to unsigned 8-bit range and clamp the value
//将分贝缩放到无符号8位范围并钳制该值
// Range 0-240 covers -120..0 dB in 0.5 dB steps
int scaled = (int) (2 * db + 240);
//0~255之间
me->wf.mag[offset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled);
++offset;
if (db > me->max_mag)
me->max_mag = db;
}
}
}
++me->wf.num_blocks;//mag阵列中存储的块符号编号块大小2*2*960
}
void monitor_reset(monitor_t *me) {
LOG(LOG_DEBUG, "Monitor is resetting...");
me->wf.num_blocks = 0;
me->max_mag = -120.0f;
}