You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求FFTW与Qt QAudioProbe结合的实时音频FFT可视化示例

简化Qt实时音频FFT柱状图实现方案

我完全懂你的感受——Qt自带的Spectrum示例为了覆盖各种场景做了太多封装,对只需要核心实时FFT可视化的需求来说确实太臃肿了。咱们直接把它拆解成最小可行模块,结合你正在写的QThread Worker类来实现,步骤清晰,代码简洁。

第一步:修正音频捕获初始化(替换你现有的process()开头)

你之前用的QAudioEncoderSettings是针对音频编码输出的,实时处理原始PCM的话,用QAudioInput更直接。下面是Worker类里正确的初始化+核心处理代码:

#include <QAudioInput>
#include <QIODevice>
#include <QAudioFormat>
#include <QFFT> // Qt6 自带,Qt5的话可以用FFTW或轻量自定义FFT
#include <QVector>
#include <cmath>

class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr) : QObject(parent) {}

signals:
    // 发送FFT处理后的频谱数据给UI线程更新柱状图
    void spectrumReady(const QVector<double>& spectrumBars);
    void errorOccurred(const QString& errorMsg);

public slots:
    void process() {
        // 1. 配置通用音频格式:PCM 16位、单声道、44100Hz采样率
        QAudioFormat format;
        format.setSampleRate(44100);
        format.setChannelCount(1);
        format.setSampleFormat(QAudioFormat::Int16); // 16位整数PCM,最常用的格式

        // 检查设备兼容性,自动 fallback 到支持的格式
        QAudioDeviceInfo info(QAudioDeviceInfo::defaultInputDevice());
        if (!info.isFormatSupported(format)) {
            emit errorOccurred("音频格式不支持,将使用最接近的兼容格式");
            format = info.nearestFormat(format);
        }

        // 2. 创建音频输入对象,启动录制并获取数据流设备
        QAudioInput* audioInput = new QAudioInput(info, format, this);
        QIODevice* audioDevice = audioInput->start();

        // 3. 配置FFT参数:缓冲区大小必须是2的幂(FFT效率最高),这里选1024
        const int fftSize = 1024;
        QFFT fft(fftSize);
        QVector<qreal> inputBuffer(fftSize);

        // 4. 循环读取音频数据并实时处理
        while (!audioDevice->atEnd()) {
            // 读取16位PCM数据:每个采样占2字节,所以要读 fftSize*2 字节
            QByteArray rawData = audioDevice->read(fftSize * 2);
            if (rawData.size() != fftSize * 2) continue; // 数据不足时跳过,避免崩溃

            // 5. 转换PCM数据为FFT可用的浮点数,同时加汉宁窗减少频谱泄漏
            const qint16* pcmData = reinterpret_cast<const qint16*>(rawData.constData());
            for (int i = 0; i < fftSize; ++i) {
                // 汉宁窗公式:抑制频谱泄漏的关键
                double window = 0.5 * (1 - cos(2 * M_PI * i / (fftSize - 1)));
                inputBuffer[i] = pcmData[i] * window / 32768.0; // 归一化到[-1,1]区间
            }

            // 6. 执行FFT并计算频谱幅度(转分贝更适合可视化)
            QVector<qcomplex> fftResult = fft.transform(inputBuffer);
            QVector<double> spectrumBars;

            // FFT结果是对称的,只取前半部分即可
            for (int i = 0; i < fftSize / 2; ++i) {
                double magnitude = qAbs(fftResult[i]);
                double db = 20 * log10(magnitude / fftSize); // 归一化后转分贝
                spectrumBars.append(db > -60 ? db + 60 : 0); // 过滤-60dB以下的噪音,映射到0~60范围
            }

            // 7. 把频谱数据发送给UI线程更新
            emit spectrumReady(spectrumBars);
        }

        // 清理资源
        audioInput->stop();
        delete audioInput;
    }
};

第二步:UI线程接收数据绘制柱状图

用Qt的QChart实现轻量柱状图,也可以用自定义QWidget手动绘制(更省资源):

#include <QMainWindow>
#include <QChart>
#include <QBarSeries>
#include <QBarSet>
#include <QChartView>
#include <QThread>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {
        // 初始化图表组件
        QBarSeries* series = new QBarSeries();
        QBarSet* barSet = new QBarSet("实时频谱");
        *barSet << 0; // 初始占位值
        series->append(barSet);

        QChart* chart = new QChart();
        chart->addSeries(series);
        chart->setTitle("音频FFT柱状图");
        chart->createDefaultAxes();
        chart->axisY()->setRange(0, 60); // 对应Worker输出的0~60dB范围

        QChartView* chartView = new QChartView(chart);
        setCentralWidget(chartView);

        // 启动Worker子线程
        QThread* workerThread = new QThread(this);
        Worker* worker = new Worker();
        worker->moveToThread(workerThread);

        // 连接信号槽:线程启动、数据更新、错误处理、资源清理
        connect(workerThread, &QThread::started, worker, &Worker::process);
        connect(worker, &Worker::spectrumReady, this, [=](const QVector<double>& bars) {
            // 只显示前64个柱子(太多会导致UI卡顿)
            const int displayCount = 64;
            barSet->remove(0, barSet->count());
            for (int i = 0; i < qMin(displayCount, bars.size()); ++i) {
                *barSet << bars[i];
            }
        });
        connect(worker, &Worker::errorOccurred, this, [=](const QString& msg) {
            qDebug() << "音频处理错误:" << msg;
        });
        connect(workerThread, &QThread::finished, worker, &Worker::deleteLater);
        connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);

        workerThread->start();
    }
};

关键注意事项

  • FFT缓冲区大小:必须是2的幂(512/1024/2048),这是FFT算法的硬性要求,能最大化处理效率。
  • 窗口函数:一定要加汉宁窗(或布莱克曼窗),否则会出现严重的频谱泄漏,导致可视化结果失真。
  • 线程安全:Worker在子线程处理数据,必须通过信号槽传递结果到UI线程,绝对不能在子线程直接操作UI控件。
  • 性能优化:如果UI卡顿,可以减少柱状图数量(比如从64减到32),或者降低采样率(比如22050Hz)。

内容的提问来源于stack exchange,提问作者tim quzerah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:27:46