当前位置: 首页 > news >正文

VS2022+QT6.7+Multimedia(捕获Windows音频数据,生成实时频谱)

目录

一、前言

二、代码详解

三、gitee完整项目下载


一、前言

实现的功能:

1、捕获Windows系统音频,从扬声器播放的声音(网页、应用)。

2、FFT傅里叶将频域转为分贝。

3、在QChart中用QBarSet生成20段实时频谱图,0Hz-20kHz。

(主线程只负责更新UI,子线程处理音频数据,做FFT变换)

(代码本身并不复杂,只是涉及的东西比较多,多梳理几遍就能理解)

准备工作:

1、您需要在Qt Maintenance Tool中安装Qt Multimedia模块,并且在VS项目属性中引用该模块

2、您需要正确安装  #include <fftw3.h>   // FFT库

FFTW3在VS环境下的安装(亲测)_vs2022 fftw-CSDN博客

实现图片效果如下:

实现视频效果如下:

QT windows频谱分析器

二、代码详解

程序执行流程图:

1、用到的头文件:

#include <QAudioInput>
#include <QAudioFormat>
#include <QMediaDevices>
#include <QAudioDevice>
#include <QAudioSource>
#include <QIODevice>
#include <QAudioBuffer>  //音频帧
#include <cmath> #include <QTimer>
#include <QThread>
#include <QHBoxLayout>
#include <QChart>
#include <QChartView>
#include <QBarSeries>
#include <QBarSet>
#include <QBarCategoryAxis>
#include <QValueAxis>#include <fftw3.h>   // FFT库

2、用到的两个类:QtWidgetsApplication4 主线程类 、 AudioQThread 子线程类

class QtWidgetsApplication4 : public QWidget
{Q_OBJECTpublic:QtWidgetsApplication4(QWidget* parent = nullptr);~QtWidgetsApplication4();QBarSeries* m_series;        // 频谱柱状图数据集QChart* m_chart;             // 频谱图表QStringList bandCategories;  // 存储20个频段的X轴标签QBarCategoryAxis* m_axisX;   // X轴QValueAxis* m_axisY;         // Y轴QChartView* m_chartView;     // 频谱图表视图QBarSet* m_barSet;           // 频谱柱状图QString getBandLabel(int);   // 获取20个频段的标签void paintEvent_init();      // 频谱图初始化public slots:void update_chart(QVector<double>); // 更新图表 30ms/次private:Ui::QtWidgetsApplication4Class ui;
};class AudioQThread : public QObject
{Q_OBJECTpublic:AudioQThread();~AudioQThread();void work();    // 线程处理函数void readAll(); // 读取音频数据QIODevice* audioIODevice = nullptr; // 录音源QAudioFormat formatAudio; // 音频格式void updateFFT();         // 计算FFT,将频域转为分贝 signals:void updatePlot(QVector<double>); // 更新频谱图private:// FFT相关变量// FFT核心参数(确保覆盖20kHz:采样率≥44.1kHz,FFT点数≥2048)static const int FFT_SIZE = 2048;  // FFT点数(2^11,44.1kHz采样率下频率分辨率≈21.5Hz)fftw_complex* fftIn;        // FFT输入(复数数组)fftw_complex* fftOut;       // FFT输出(复数数组)fftw_plan fftPlan;          // FFT计划QVector<double> fftFreqs;   // FFT对应的实际频率(0Hz~奈奎斯特频率)QVector<double> fftDb;      // FFT结果(分贝值,对应fftFreqs)// 20频段配置(对数刻度,覆盖20Hz-20kHz)static const int BAND_COUNT = 20;           // 频段数量QVector<QPair<double, double>> bandRanges;  // 每个频段的[起始频率, 结束频率]QVector<double> bandEnergy; // 每个频段的平均能量(分贝值)// 辅助变量QTimer* updateTimer;        // 刷新定时器(30ms/次,≈33fps)QVector<qint16> audioBuffer;// 音频缓冲区(存储待FFT的时域数据)double nyquistFreq=NULL;    // 奈奎斯特频率(采样率/2,确保≥20kHz)
};

3、在主类构造中创建子线程,并绑定updatePlot信号和update_chart槽来更新频谱图

	paintEvent_init();                     //频谱图初始化QThread* thread = new QThread();       //创建线程AudioQThread* st = new AudioQThread(); //创建对象st->moveToThread(thread);              //将对象移动到线程中connect(thread, &QThread::started, st, &AudioQThread::work);connect(st, &AudioQThread::updatePlot, this, &QtWidgetsApplication4::update_chart);connect(ui.pushButton, &QPushButton::clicked, this, [=]() {thread->start(); //启动线程});

4、在子线程构造中初始化定时器、FFT资源、音频缓冲区

// 子线程的构造中初始化音频处理线程
AudioQThread::AudioQThread()
{// 1、 初始化20频段范围(对数刻度,覆盖20Hz-20kHz,符合人耳听觉)const double bandStarts[] = { 20, 28, 40, 56, 80, 112, 160, 224, 320, 448,640, 896, 1280, 1792, 2560, 3584, 5120, 7168, 10240, 14336 };const double bandEnds[] = { 28, 40, 56, 80, 112, 160, 224, 320, 448, 640,896, 1280, 1792, 2560, 3584, 5120, 7168, 10240, 14336, 20000 };for (int i = 0; i < BAND_COUNT; ++i) {bandRanges.append({ bandStarts[i], bandEnds[i] });}bandEnergy.resize(BAND_COUNT, -60.0);  // 初始化能量为-60dB(噪声阈值)// 2、 初始化FFT资源(FFT_SIZE=2048,确保频率分辨率足够)fftIn = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * FFT_SIZE);fftOut = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * FFT_SIZE);fftPlan = fftw_plan_dft_1d(FFT_SIZE, fftIn, fftOut, FFTW_FORWARD, FFTW_ESTIMATE);// 3、 初始化刷新定时器(控制可视化帧率)updateTimer = new QTimer(this);updateTimer->setInterval(30);connect(updateTimer, &QTimer::timeout, this, &AudioQThread::updateFFT);// 4、 初始化音频缓冲区audioBuffer.reserve(FFT_SIZE);
}

5、在子线程中设置音频格式、音频输入设备、创建音源,绑定readAll函数,获取设备的音频数据

// 子线程,获取音频数据并执行FFT
void AudioQThread::work()
{// 1、使用默认音频格式formatAudio.setSampleRate(44100);formatAudio.setChannelCount(2);formatAudio.setSampleFormat(QAudioFormat::Int16);nyquistFreq = formatAudio.sampleRate() / 2.0;qInfo() << "采样率:" << formatAudio.sampleRate() << "Hz,奈奎斯特频率:" << nyquistFreq << "Hz";// 2、初始化FFT频率数组(映射FFT索引到实际频率)fftFreqs.resize(FFT_SIZE / 2);double freqStep = formatAudio.sampleRate() / (double)FFT_SIZE;  // FFT频率分辨率for (int i = 0; i < FFT_SIZE / 2; ++i) {fftFreqs[i] = i * freqStep;  // 第i个FFT点对应的实际频率}// 3、获取默认音频输入设备QAudioDevice inputDevice = QMediaDevices::defaultAudioInput();// 4、创建音频输入QAudioInput* audioInput = new QAudioInput(inputDevice, this);// 5、创建音频源QAudioSource* audioSource = new QAudioSource(inputDevice, formatAudio, this);// 6、开始录音audioIODevice = audioSource->start();// 7、连接readyRead信号connect(audioIODevice, &QIODevice::readyRead, this, &AudioQThread::readAll);// 8、启动定时器(FFT和频段计算)updateTimer->start();
}

6、readAll缓存数据处理,将数据存到audioBuffer缓冲区中

// 转换为16位整数采样点,存入缓冲区(仅保留最新FFT_SIZE个点)
void AudioQThread::readAll()
{QByteArray data = audioIODevice->readAll();if (!data.isEmpty()) {const qint16* samples = reinterpret_cast<const qint16*>(data.constData());int sampleCount = data.size() / sizeof(qint16);for (int i = 0; i < sampleCount; ++i) {audioBuffer.append(samples[i]);if (audioBuffer.size() > FFT_SIZE) {audioBuffer.remove(0, audioBuffer.size() - FFT_SIZE);}}}
}

7、FFT将时域->频域->分贝值->存到bandEnergy中->通过updatePlot(bandEnergy);发送到主线程

// 频谱处理、频段计算、通过updatePlot信号在UI中绘制频谱图
void AudioQThread::updateFFT()
{// 0、缓冲区数据不足,不执行FFTif (audioBuffer.size() < FFT_SIZE) return;// 1、 时域数据加汉宁窗(减少频谱泄漏)for (int i = 0; i < FFT_SIZE; ++i) {double hanning = 0.5 * (1 - cos(2 * M_PI * i / (FFT_SIZE - 1)));fftIn[i][0] = audioBuffer[i] * hanning;  // 实部(时域数据)fftIn[i][1] = 0.0;                       // 虚部(无)}// 2、 执行FFT,转换为频域数据fftw_execute(fftPlan);// 3、 FFT结果转换为分贝值(仅保留前半段,对应0Hz~奈奎斯特频率)fftDb.resize(FFT_SIZE / 2);for (int i = 0; i < FFT_SIZE / 2; ++i) {double magnitude = sqrt(fftOut[i][0] * fftOut[i][0] + fftOut[i][1] * fftOut[i][1]);fftDb[i] = (magnitude > 1e-8) ? 20 * log10(magnitude) : -120.0;  // 避免log(0)}// 4、 计算20个频段的平均能量(核心:按频段范围分组FFT数据)bandEnergy.fill(-60.0);  // 重置能量for (int bandIdx = 0; bandIdx < BAND_COUNT; ++bandIdx) {double bandStart = bandRanges[bandIdx].first;double bandEnd = bandRanges[bandIdx].second;double energySum = 0.0;int count = 0;// 遍历FFT频率点,找到当前频段内的所有点for (int fftIdx = 0; fftIdx < fftFreqs.size(); ++fftIdx) {double freq = fftFreqs[fftIdx];if (freq >= bandStart && freq < bandEnd) {energySum += fftDb[fftIdx];  // 累加该频段内的分贝值count++;}}// 计算该频段的平均能量(若有有效点)if (count > 0) {bandEnergy[bandIdx] = energySum / count;}}// 5、更新频谱图updatePlot(bandEnergy);

8、主线程中初始化频谱图,UI文件中只有一个按钮和widget。注意:paintEvent_init一开始就放在构造里面了,程序执行时频谱图就已经初始化了。

// 频谱图初始化
void QtWidgetsApplication4::paintEvent_init()
{// 1、创建图表布局QWidget* Widget = ui.widget;QVBoxLayout* layout = new QVBoxLayout(Widget);// 2、初始化柱状图数据集m_barSet = new QBarSet("FFT 分贝值");m_series = new QBarSeries();m_series->append(m_barSet);m_series->setBarWidth(1.0); // 相对宽度,0.0-1.0之间// 3、创建图表m_chart = new QChart();m_chart->addSeries(m_series);m_chart->setTitle("20频段音频频谱分析(20Hz-20kHz)");m_chart->setAnimationOptions(QChart::SeriesAnimations); // 添加动画效果m_chart->legend()->hide(); // 隐藏图例// 4、创建X轴标签for (int i = 1; i <= 20; ++i) {QString bandLabel = getBandLabel(i);  //获取x轴标签bandCategories << bandLabel;*m_barSet << 0.0;  //设置20个柱状图的默认值}// 5、创建X轴(频率点)m_axisX = new QBarCategoryAxis();m_chart->addAxis(m_axisX, Qt::AlignBottom);m_axisX->append(bandCategories); // 添加X轴标签m_series->attachAxis(m_axisX);m_axisX->setLabelsFont(QFont("Arial", 5)); // 设置字体和字号// 6、创建Y轴(分贝值)m_axisY = new QValueAxis();m_axisY->setTitleText("分贝 (dB)");m_axisY->setRange(0, 200); // 根据数据范围设置m_chart->addAxis(m_axisY, Qt::AlignLeft);m_series->attachAxis(m_axisY);// 7、创建图表视图m_chartView = new QChartView(m_chart);m_chartView->setRenderHint(QPainter::Antialiasing); // 抗锯齿// 8、将图表视图添加到布局中layout->setContentsMargins(0, 0, 0, 0);layout->addWidget(m_chartView);
}

9、主线程中直接更新柱状图

void QtWidgetsApplication4::update_chart(QVector<double> bandEnergy)
{qDebug() << bandEnergy;// 批量更新数据for (int i = 0; i < bandEnergy.size(); ++i) {// 更新柱状图数据m_barSet->replace(i, bandEnergy[i]);}
}

10、X轴的标签在这个函数中创建,使用m_axisX->append(bandCategories); // 添加X轴标签

// 自定义辅助函数:获取第i个频段的频率范围标签(1~20对应20Hz-20kHz的对数划分)
QString QtWidgetsApplication4::getBandLabel(int bandIndex)
{// 使用与AudioQThread中一致的频段范围定义const QVector<QString> labels = {"20-28Hz", "28-40Hz", "40-56Hz", "56-80Hz", "80-112Hz","112-160Hz", "160-224Hz", "224-320Hz", "320-448Hz", "448-640Hz","640-896Hz", "896-1.28kHz", "1.28-1.79kHz", "1.79-2.56kHz", "2.56-3.58kHz","3.58-5.12kHz", "5.12-7.17kHz", "7.17-10.24kHz", "10.24-14.3kHz", "14.3-20kHz"};if (bandIndex >= 1 && bandIndex <= 20) {return labels[bandIndex - 1];}return QString("柱 %1").arg(bandIndex);
}

三、gitee完整项目下载

https://gitee.com/zjq11223344/qt-widgets-application4https://gitee.com/zjq11223344/qt-widgets-application4

http://www.dtcms.com/a/355193.html

相关文章:

  • Day16_【机器学习建模流程】
  • Python备份实战专栏第2/6篇:30分钟搭建企业级API认证系统,安全性吊打90%的方案
  • R语言贝叶斯方法在生态环境领域中的高阶技术应用
  • Mac 开发环境与配置操作速查表
  • 基于Vue2+elementUi实现树形 横向 合并 table不规则表格
  • 华为S5720S重置密码
  • 前沿技术观察:从AI 时代到量子计算的下一站
  • 智能物联网(AIoT)核心技术落地路径与企业数字化转型适配方案
  • 如何通俗的理解操作系统的IO多路复用
  • H5 本地跨域设置
  • “帕萨特B5钳盘式制动器结构设计三维PROE模型7张CAD图纸PDF图“
  • UE5.5模型导入FBX强制x轴向前Force Front XAxis
  • 上线问题——Mac系统下如何获取鸿蒙APP证书公钥和MD5指纹
  • 密码管理中
  • 多线程 【详解】| Java 学习日志 | 第 14 天
  • Ansys Icepak AEDT 中的后处理脚本
  • 护网面经总结(三)
  • 三维细节呈现核心技术:法线、凹凸与置换贴图全解析与应用指南
  • 物业满意度调查数据分析——从 “数据杂乱” 到 “精准改进” 的落地经验(满意度调查问卷)
  • Linux系统资源分配算法在VPS云服务器调优-性能优化全指南
  • ​突破RAG知识库中的PDF解析瓶颈:从文本错乱到多模态处理的架构跃迁​
  • 【C++成长之旅】C++入门基础:从 Hello World 到命名空间与函数重载的系统学习
  • NV002NV003美光固态闪存NV026NV030
  • 数组替代map实现性能优化
  • Multimodal Transformer Training in Personalized Federated Learning
  • 配送算法17 AFramework for Multi-stage Bonus Allocation in meal delivery Platform
  • 替换数字(字符串算法)
  • 宋红康 JVM 笔记 Day08|堆
  • SMTPman,smtp协议是什么协议的核心功能!
  • 大数据毕业设计选题推荐-基于大数据的存量房网上签约月统计信息可视化分析系统-Hadoop-Spark-数据可视化-BigData