Serial Studio 中的 KISS FFT:轻量混合基 FFT 库的 API、构建与源码级解析
2026/9/18 1:29:38 网站建设 项目流程

Serial Studio 中的 KISS FFT:轻量混合基 FFT 库的 API、构建与源码级解析

【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio

KISS FFT("Keep It Simple, Stupid")是一个以"简单"为首要目标的混合基快速傅里叶变换库,核心代码仅约 500 行,支持 float、double、int16_t、int32_t 与 SIMD 多种数据类型,并以宽松的 BSD 许可证可被快速嵌入任意 C 程序。在本仓库中,它作为第三方依赖被集成进 lib/KissFFT,并实际承担 Serial Studio FFT 频谱绘图(FFTPlot)与瀑布图(Waterfall)两个核心可视化组件的实数 FFT 运算。读完本文,你将掌握 KISS FFT 的核心 API 用法、两种构建系统的全部配置项、数据类型切换机制,以及它在本项目中的真实调用链路与底层算法原理。

KISS FFT 是什么:设计哲学与背景

KISS FFT 的诞生源于作者 Mark Borgerding 的一个具体痛点:当时找不到一个不使用汇编代码的定点 FFT 实现。于是他先用浮点数把理论理清,再处理定点问题,最终得到一份可通过简单重编译就支持 short、float、double 等类型的精简代码(lib/KissFFT/kiss_fft.c)。

README 中作者明确表示:"已经有很多优秀的 FFT 库了,KISS FFT 并不试图比它们更好,它只希望做一个足够高效、足够实用的 FFT,支持定点或浮点数据类型,并且能在几分钟内以极宽松的许可证嵌入别人的 C 程序。" 换句话说,它的定位是可移植、易集成、许可证友好,而非追求极致性能。

作者在 BACKGROUND 一节给出了与某个高度优化的商业 FFT 库(文中匿名称为 FFT_BRANDX)的对比,这些数字客观反映了该库的设计取舍:

  • FFT_BRANDX 超过 10 万行代码,而 KISS FFT 的复数 1-D 核心仅约 500 行;
  • 使用 FFT_BRANDX 的简单程序体积为 522KB,而同等功能的 KISS FFT 程序仅 18KB(未做体积优化);
  • FFT_BRANDX 在默认模式下大约比 KISS FFT 快 2 倍。

作者的结论是:"那些高度优化的库为了榨取每一分性能背负了巨大的复杂度负担。有时候更简单反而更好,即使它并不'更好'。" 这句话是理解 KISS FFT 一切设计决策的总纲。

快速上手:1-D 复数 FFT 最小用法

在项目中引入 KISS FFT 只需包含头文件并调用三个函数。README 给出的最小编程范式如下:

#include "kiss_fft.h" kiss_fft_cfg cfg = kiss_fft_alloc( nfft ,is_inverse_fft ,0,0 ); while ... ... // put kth sample in cx_in[k].r and cx_in[k].i kiss_fft( cfg , cx_in , cx_out ); ... // transformed. DC is in cx_out[0].r and cx_out[0].i kiss_fft_free(cfg);

要点说明:

  • kiss_fft_alloc(nfft, is_inverse_fft, mem, lenmem)负责为指定长度nfft的 FFT 分配并初始化配置/状态缓冲区(内部包含旋转因子表与因子分解结果),最后一个参数传 0 表示由库内部用malloc分配;
  • 每次变换调用kiss_fft(cfg, cx_in, cx_out)完成;输入输出均为复数,通过.r/.i访问实部与虚部;
  • 用完后必须kiss_fft_free(cfg)释放,避免内存泄漏。

频率域布局(极易踩坑):输出频率数据从 DC(0 频)一直排到 2π。因此cx_out[0]是 FFT 的直流分量(DC bin),而cx_out[nfft/2](若存在)是奈奎斯特频率对应的 bin。

完整声明与各函数的行为说明位于 lib/KissFFT/kiss_fft.h(如 kiss_fft.h 的 kiss_fft_alloc 注释),具体实现则在 lib/KissFFT/kiss_fft.c 中。

核心 API 详解

从 kiss_fft.h 可以看到该库对外暴露的全部接口,均以extern "C"包裹以便 C++ 程序直接调用:

函数签名作用
kiss_fft_allockiss_fft_cfg kiss_fft_alloc(int nfft, int inverse_fft, void *mem, size_t *lenmem)初始化 FFT/IFFT 的 cfg/state 缓冲区,返回 NULL 表示失败
kiss_fftvoid kiss_fft(kiss_fft_cfg cfg, const kiss_fft_cpx *fin, kiss_fft_cpx *fout)对复数输入执行一次 FFT
kiss_fft_stridevoid kiss_fft_stride(kiss_fft_cfg cfg, const kiss_fft_cpx *fin, kiss_fft_cpx *fout, int fin_stride)通用版本:每隔fin_stride个样本读取一次输入
kiss_fft_free宏(映射到KISS_FFT_FREE释放kiss_fft_alloc分配的缓冲区
kiss_fft_cleanupvoid kiss_fft_cleanup(void)清理内部管理的内存,退出前调用可让编译器输出更干净,非必需
kiss_fft_next_fast_sizeint kiss_fft_next_fast_size(int n)返回不小于 n、且只含"快速因子"(2、3、5)的最小整数

关于kiss_fft_allocmem/lenmem参数,头文件注释给出了三种用法:

  • lenmem == NULL:库内部用malloc分配 cfg 缓冲区,返回指针需用kiss_fft_free释放;
  • memlenmem均非 NULL 且*lenmem足够大:cfg 状态被放入用户提供的mem中,实际占用大小写回*lenmem,返回mem
  • mem为 NULL 或*lenmem不够大:返回 NULL,并将所需最小 cfg 缓冲区大小写入*lenmem——这是典型的"先查询大小、再自备内存"两段式用法,适合嵌入式等对动态分配敏感的场景。

kiss_fft_next_fast_size的实现(kiss_fft.c 第 408-420 行)非常直白:不断检查 n 能否被 2、3、5 整除,不能则 n 自增,直到找到完全由 2/3/5 分解的数。这是因为核心实现对因子 2、3、4、5 做了专门的蝶形优化(见下文"内部原理")。

实数优化 FFT(kiss_fftr):频谱分析的实际选择

README 特别提到tools/目录下的实数优化 FFT——它"返回正半频谱:(nfft/2+1) 个复数频率 bin"。对于采集自真实传感器、音频或串口的数据(纯实数序列),实数 FFT 相比复数 FFT 可节省约 45% 的 CPU 时间(此数字来自 kiss_fftr.h 的注释)。

实数 FFT 的接口定义在 lib/KissFFT/kiss_fftr.h:

  • kiss_fftr_alloc(nfft, inverse_fft, mem, lenmem)要求 nfft 必须为偶数;不关心分配细节时 mem 和 lenmem 直接传 NULL;
  • kiss_fftr(cfg, timedata, freqdata):输入timedata为 nfft 个实标量点,输出freqdatanfft/2+1个复数点(从 DC 到 Nyquist);
  • kiss_fftri(cfg, freqdata, timedata):逆变换,输入 nfft/2+1 个复数点,输出 nfft 个实标量点;
  • kiss_fftr_free:对应的释放宏。

这也正是 Serial Studio 中 FFT 绘图与瀑布图所使用的接口,下一节详细展开。

扩展工具集:多维 FFT 与快速卷积等

README 列出了tools/目录提供的其他能力:

  • 多维 FFTkiss_fftnd.c/kiss_fftnd.h):任意维度的 FFT;
  • 实数优化 FFT:见上一节(kiss_fftr.c);
  • 快速卷积 FIR 滤波kiss_fastfir.c):基于 FFT 实现快速卷积,注意不适用于定点版本
  • 频谱图像生成psdpng工具):把频谱渲染成 PNG 图片。

核心 FFT 与大部分tools/代码均可编译为使用 float、double、Q15 short 或 Q31 样本(对应 int16_t / int32_t),默认数据类型为 float。这些工具对应的可执行文件在 lib/KissFFT/tools/CMakeLists.txt 中定义,包括fastconvfastconvrfftpsdpng等,均可通过KISSFFT_TOOLS选项控制是否构建。

构建 KISS FFT:Make 与 CMake 双构建系统

KISS FFT 提供两套功能等价的构建系统:传统 Makefile(Unix/Linux)与 CMake(≥ 3.6,由 Kitware 开发)。README 给出的支持环境为:

  • GNU 构建环境:GCC、Clang + GNU Make 或 CMake(≥ 3.6);
  • Microsoft Visual C++(MSVC)+ CMake(≥ 3.6)。

构建与测试的可选依赖库:

  • libpng:用于psdpng工具;
  • libfftw3:用于将 KISS FFT 结果与 FFTW 对比验证;
  • Python 2/3 + Numpy:用于将结果与 Numpy 对比验证;
  • OpenMP(GCC / Clang / MSVC 支持):用于多核 FFT 变换。

Cygwin 与 MinGW 环境大概率可用于面向 Windows 平台构建,但截至文档写作时未做过测试。仓库中 lib/KissFFT/Makefile 的版本号为 131.1.0(KFVER_MAJOR=131即 ABI 版本,KFVER_MINOR=1KFVER_PATCH=0),CMake 工程会从 Makefile 中正则提取这三个数字作为project(kissfft ...)的版本号(见 lib/KissFFT/CMakeLists.txt)。

全部构建配置项

配置项(Make / CMake)可选值默认值说明
KISSFFT_DATATYPE/-DKISSFFT_DATATYPE=floatdoubleint16_tint32_tsimdfloat库使用的主要数据类型;simd要求目标 CPU 支持 SSE 指令集
KISSFFT_OPENMP=1/-DKISSFFT_OPENMP=ON0 / 1关闭启用 OpenMP 多核支持,需编译器支持
KISSFFT_STATIC=1/-DKISSFFT_STATIC=ON0 / 1Make 关闭;CMake 默认 ON生成静态库(Windows.lib,Unix/Linux.a);关闭则生成共享库(Windows.dll,Linux/Unix.so,macOS.dylib
-DKISSFFT_TEST=OFFON/OFF关闭关闭测试构建(仅 CMake);Make 下测试通过make testall/make testsingle单独进行
KISSFFT_TOOLS=0/-DKISSFFT_TOOLS=OFF0 / 1默认构建工具是否构建fastconv等命令行工具
KISSFFT_USE_ALLOCA=1/-DKISSFFT_USE_ALLOCA=ON0 / 1关闭使用alloca代替malloc/free
PREFIX=/-DCMAKE_INSTALL_PREFIX=路径Make 为/usr/local指定安装前缀目录

注意 CMake 中KISSFFT_STATIC默认值为 ON(CMakeLists.txt 第 50 行),这与 Make 的默认动态库行为不同;同时如果同时指定-DBUILD_SHARED_LIBS=ON-DKISSFFT_STATIC=ON,CMake 会直接报错退出(第 78-80 行)。CMake 还会校验KISSFFT_DATATYPE的取值,非法值会触发FATAL_ERROR(第 59-65 行)。

示例:以 int16_t 定点类型构建静态 OpenMP 库

README 给出的 Make 命令(在 kissfft 源码树内执行):

make KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 all

等价 CMake 流程:

mkdir build && cd build cmake -DKISSFFT_DATATYPE=int16_t -DKISSFFT_STATIC=ON -DKISSFFT_OPENMP=ON .. make all

示例:指定安装前缀

make PREFIX=/tmp/1234 KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 install

或:

mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/tmp/1234 -DKISSFFT_DATATYPE=int16_t -DKISSFFT_STATIC=ON -DKISSFFT_OPENMP=ON .. make all make install

从 Makefile 可以看到:库产物名随配置变化(如libkissfft-int16_t-openmp.a),头文件(kiss_fft.hkissfft.hhkiss_fftnd.hkiss_fftndr.hkiss_fftr.h)统一安装到include/kissfft目录,同时生成对应的.pcpkg-config 文件(Linux/macOS 下)。CMake 构建还额外导出kissfft::kissfftkissfft::kissfft-<datatype>两个 target 别名,并支持安装 CMake package 配置(CMakeLists.txt 第 294-319 行)。

数据类型切换机制:FIXED_POINT 与 kiss_fft_scalar

KISS FFT 通过编译期宏实现类型可重编译。从 kiss_fft.h 第 73-85 行 可以看到:

  • 定义了FIXED_POINT时:FIXED_POINT == 32kiss_fft_scalarint32_t,否则为int16_t
  • 未定义时:默认kiss_fft_scalarfloat
  • 定义了USE_SIMD时:kiss_fft_scalar__m128,且自动使用 16 字节对齐的_mm_malloc/_mm_free(kiss_fft.h 第 50-60 行)。

复数类型kiss_fft_cpx仅包含ri两个kiss_fft_scalar字段(第 87-90 行)。在 CMake 中,各类型对应的编译宏由 lib/KissFFT/CMakeLists.txt 自动生成:float/double定义kiss_fft_scalar=<type>int16_t/int32_t分别定义FIXED_POINT=16/FIXED_POINT=32simd定义USE_SIMD并为非 MSVC 编译器附加-msse(MSVC 下为/arch:SSE)。Make 构建在 Makefile 中的TYPEFLAGS变量里做了完全一致的映射。

重要警告(来自 FAQ):所有参与编译的代码必须使用相同的预处理器定义FIXED_POINTkiss_fft_scalar),混合构建环境是"输出不符合预期"的两大常见原因之一。另一个常见原因是缩放因子:浮点版本不做缩放,因此输出可能存在一个与期望值之间的常数倍关系。

测试:从单配置到全矩阵

针对上面的 int16_t 静态 OpenMP 配置,README 给出的验证命令:

make KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 testsingle

使用 CMake 时则执行:

make test

若要覆盖所有可能的构建配置组合,运行扩展测试套件(在 kissfft 源码树内):

sh test/kissfft-testsuite.sh

注意该扩展套件耗时约 20-40 分钟(取决于运行设备),主要用于上报 bug 或验证 pull request 前做完整回归。从 Makefile 的 testall 目标 可以看到完整的测试矩阵:shared/static × double/float/int16_t/int32_t/simd × 是否启用 OpenMP,共 15 种组合(其中 simd 与 int32_t 在部分机器上可能不工作,脚本对此有注明)。测试目标定义在 lib/KissFFT/test/CMakeLists.txt,包括bm_kiss(基准测试)、ffr/tr(实数 FFT 测试)、testcpp(C++ 头文件kissfft.hh测试)与tsimd(SIMD 测试)等。

性能:作者给出的参考数据

README 的 PERFORMANCE 一节给出了作者的实测(环境为 Athlon XP 2100+、gcc 2.96、float 类型):

  • 执行 10000 次 1024 点复数 FFT 耗时 0.63 秒 CPU 时间;
  • 作为对照,md5sum 处理同样数据量的耗时是它的两倍;
  • 变换 5 分钟 CD 音质音频不到 1 秒(nfft=1024)。

这份数据来自文档原始记录(硬件与编译器已过时),仅作量级参考,不宜与当代库做直接比较。作者同时给出两条忠告:

  • 不要在使用 KISS FFT 的场合追求"世界上最快的傅里叶变换";
  • 不要要求作者添加会让代码膨胀的功能。

内部原理:时间抽取混合基 FFT

README 的 UNDER THE HOOD 一节与源码相互印证,可以总结出以下实现事实:

  1. 算法类型:时间抽取(decimation-in-time)、混合基(mixed-radix)out-of-place的 FFT。如果传入的输入输出缓冲区相同,内部会创建一个临时缓冲区来存放数据(kiss_fft_stridefin == fout分支即处理此情况,见 kiss_fft.c 第 371-395 行——注意注释明确说明"这并非真正的 in-place 算法")。

  2. 无静态数据、线程安全:核心例程不使用任何静态数据,因此是线程安全的(tools/目录下的工具不保证)。kiss_fft_cleanup在现代版本中已是空操作(kiss_fft.c 第 403-406 行)。

  3. 缩放策略:浮点版本不做任何缩放(为了速度);定点版本则在正反变换两个方向都做缩放(为了防止溢出)。源码中每个蝶形里的C_FIXDIV宏正是定点版本的除法缩放点(如kf_bfly2中的C_FIXDIV(*Fout,2))。

  4. 优化的蝶形:对因子 2、3、4、5 提供了手工优化的蝶形函数kf_bfly2kf_bfly3kf_bfly4kf_bfly5(kiss_fft.c 第 15-189 行),其余因子回退到通用蝶形kf_bfly_generic。调度函数kf_work递归分解,因子分解由kf_factor完成(先提 4、再提 2、3,最后遍历剩余素数,见 kiss_fft.c 第 302-328 行)。

  5. OpenMP 并行:启用 OpenMP 后,kf_work在顶层(fstride==1p<=5m!=1)将 p 个子任务用#pragma omp parallel for分发到多线程执行(kiss_fft.c 第 250-272 行)。

  6. 实数优化原理:实数 FFT 优化只对偶数长度的 FFT 生效。它将一个长度为 nfft 的实数序列打包成两个半长 FFT 并行计算(分别放入实部与虚部),再通过旋转因子(twiddling)合并,最终得到从 DC 到 Nyquist 的 nfft/2+1 个复数频率 bin。

  7. 快速卷积:使用 overlap-scrap 方法,且做了小修改——把 scrap 放在尾部(见kiss_fastfir.c)。

  8. 旋转因子与对齐kiss_fft_alloc一次性分配"状态结构 + (nfft-1) 个复数旋转因子"的连续内存(kiss_fft.c 第 337-368 行),SIMD 模式下会向上对齐到 16 字节边界。

在 Serial Studio 中的实际应用:FFT 绘图与瀑布图

KISS FFT 在本仓库中被作为内置第三方依赖集成。顶层 lib/CMakeLists.txt 中通过add_subdirectory(KissFFT)引入,并以静态库形式配置(见 lib/CMakeLists.txt 第 341-379 行 与 第 611 行 的configure_third_party_lib(kissfft)),同时注明其无额外依赖。UI 层在 core/Ui/CMakeLists.txt 中链接kissfft(注释:"kissfft is the real-FFT plan behind FFTPlot and the waterfall spectrogram")。

实际消费 KISS FFT 的是两个波形可视化组件:

  • FFTPlot(FFT 频谱绘图):core/Ui/UI/Widgets/FFTPlot.cpp 与 core/Ui/UI/Widgets/FFTPlot.h;
  • Waterfall(瀑布频谱图):core/Ui/UI/Widgets/Waterfall.cpp 与 core/Ui/UI/Widgets/Waterfall.h。

两者的用法完全一致且完全贴合上文介绍的实数 FFT 接口,可视为官方 README 用法在生产代码中的真实范本:

  1. 分配 FFT planm_plan = kiss_fftr_alloc(m_size, 0, nullptr, nullptr);(FFTPlot.cpp 第 104 行、Waterfall.cpp 第 496 行),第二个参数 0 表示正向 FFT,分配失败时记录警告日志;
  2. 缓冲区布局:时域样本m_samples大小为m_size个实标量(std::vector<kiss_fft_scalar>),频域输出m_fftOutput大小为m_size / 2 + 1个复数(std::vector<kiss_fft_cpx>)——正是文档所述"正半频谱 nfft/2+1 个 bin";
  3. 执行变换kiss_fftr(m_plan, m_samples.data(), m_fftOutput.data());(FFTPlot.cpp 第 832 行、Waterfall.cpp 第 787 行);
  4. 释放 plan:析构函数或重置路径中调用kiss_fftr_free(m_plan)(FFTPlot.cpp 第 159 行 与 第 539 行)。

此外,FFTPlot 在变换前会先应用加窗(Widgets::fillFftWindow填充窗函数系数,FFT 点大小在 FFTPlot.cpp 第 96-99 行 附近初始化),这也是工程化使用 FFT 时的标准做法。头文件里还包含两条编译期断言:kiss_fft_cpx必须恰好打包两个 float(static_assert(sizeof(kiss_fft_cpx) == 2 * sizeof(float)),FFTPlot.cpp 第 616 行),以及kiss_fft_scalar大小等于float(FFTPlot.cpp 第 818 行)——这从侧面印证了项目使用的是默认 float 数据类型的 KISS FFT 构建。

测试侧同样有对应的验证:应用测试目录 app/tests/CMakeLists.txt 中有一条"Real-versus-complex KissFFT equivalence"测试,专门验证 FFTPlot 与 Waterfall 所读取的 bin 上,实数 FFT 与复数 FFT 结果等价,并链接kissfft作为被测目标(见 app/tests/CMakeLists.txt 第 381-388 行)。

常见问题(FAQ)要点

README 的 FAQ 部分回答了三个高频问题:

  1. 能否在具有某类许可证的项目中使用 kissfft?可以,见下节许可证说明。
  2. 为什么输出不符合预期?两大常见原因:一是缩放——检查输出与期望值之间是否存在常数倍关系;二是混合构建环境——所有代码必须用相同的FIXED_POINTkiss_fft_scalar预处理器定义编译。
  3. 作者会帮忙写代码/调试吗?大概率不会(除非付费);作者乐于回答针对性强、切题的技术问题,但也可能指引读者去看书、论坛或其他资料。

许可证

KISS FFT 采用Revised BSD License(即 BSD 3-Clause),许可证全文见 lib/KissFFT/COPYING(仓库的许可证归档位于 LICENSES/BSD-3-Clause.txt)。按作者表述,其要点是"免费使用与修改,署名致谢,无任何担保"。该许可证一方面与 GPL 兼容,另一方面也允许闭源商业软件使用——这正是它被广泛嵌入各类开源与商业项目(包括 Serial Studio)的原因。源码头文件中的 SPDX 标识为SPDX-License-Identifier: BSD-3-Clause

结语:什么时候该选 KISS FFT

从 README 的 TODO 列表可以看出项目仍留有已知限制:奇数长度实数 FFT 的优化尚未实现、输入/输出缩放语义尚需进一步文档化、kiss_fastfir.c尚不兼容定点类型等。综合来看,KISS FFT 适合"需要一个简单、可移植、类型灵活、许可证友好且性能够用的 FFT"的场景——正如 Serial Studio 在 FFT 频谱绘图与瀑布图中所做的:用不到百行的集成代码(分配 plan → 填窗 →kiss_fftr→ 取 bin → 释放),就获得了稳定可靠的频域分析能力。如果你需要的是为每一个平台压榨到极限的最快 FFT,那么如作者所说,它并不适合你;但如果你认同"有时候更简单反而更好",KISS FFT 的 500 行核心与几分钟的集成时间,会让你体会到这份克制的价值。

【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询