Qt —— 将系统麦克风、扬声器音频录制为本地文件,可播放(附:源码)

本文介绍了如何使用Qt库进行立体声混音,记录麦克风和扬声器的声音。通过QAudioRecorder和QAudioEncoderSettings类,详细阐述了录制过程,并附带了效果视频链接和相关代码示例。
Qt5 中实现扬声器(或麦克风)的音频频谱图,通常涉及以下几个核心技术点: 1. 使用 `QAudioInput` 或 `QAudioOutput` 获取音频数据(本例中我们以采集麦克风输入为例,用于分析频谱)。 2. 对采集到的时域音频信号进行 **快速傅里叶变换(FFT)**,转换为频域数据,得到频谱。 3. 使用 `QPainter`、`QWidget` 或 `QGraphicsView` 绘制频谱图,推荐使用 `QCustomPlot` 或自定义 `QWidget` 实现高效绘图。 下面是一个完整的 C++ 示例代码,展示如何在 Qt5 中实现一个实时音频频谱图(基于麦克风输入): --- ### ✅ 功能说明: - 使用默认音频输入设备采集 PCM 数据。 - 使用 FFT(通过 KissFFT 库)将时域数据转为频域。 - 在自定义 `SpectrumWidget` 中实时绘制频谱柱状图。 > 注意:KissFFT 是一个轻量级的 FFT 库,需提前集成。也可以用 FFTW,但 KissFFT 更适合嵌入式和 Qt 项目。 --- ### 步骤 1:添加依赖(KissFFT) 请下载 [KissFFT](https://github.com/mborgerding/kissfft) 并将其加入项目,或使用以下简化版本的复数结构与 FFT 调用方式(此处使用静态链接方式示意)。 --- ### 主要代码如下(C++ / Qt5): ```cpp // spectrumwidget.h #ifndef SPECTRUMWIDGET_H #define SPECTRUMWIDGET_H #include <QWidget> #include <QVector> class SpectrumWidget : public QWidget { Q_OBJECT public: explicit SpectrumWidget(QWidget *parent = nullptr); void setSpectrumData(const QVector<double> &spectrum); protected: void paintEvent(QPaintEvent *event) override; private: QVector<double> m_spectrum; }; #endif // SPECTRUMWIDGET_H ``` ```cpp // spectrumwidget.cpp #include "spectrumwidget.h" #include <QPainter> #include <QColor> SpectrumWidget::SpectrumWidget(QWidget *parent) : QWidget(parent), m_spectrum(256, 0.0) { setMinimumHeight(200); setAutoFillBackground(false); } void SpectrumWidget::setSpectrumData(const QVector<double> &spectrum) { m_spectrum = spectrum; update(); // 触发重绘 } void SpectrumWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); painter.fillRect(rect(), Qt::black); if (m_spectrum.isEmpty()) return; const int nBars = m_spectrum.size(); const int barWidth = width() / nBars + 1; const double maxLevel = 100.0; // 假设能量最大值归一化为100 painter.setPen(Qt::NoPen); for (int i = 0; i < nBars; ++i) { double value = m_spectrum[i]; int height = qBound(0, static_cast<int>(height() * value / maxLevel), height()); QColor color(0, 255 * (value / maxLevel), 255); // 颜色随强度变化 painter.fillRect(i * barWidth, height() - height(), barWidth - 1, height, color); } } ``` ```cpp // audioinputhandler.h #ifndef AUDIOINPUTHANDLER_H #define AUDIOINPUTHANDLER_H #include <QAudioInput> #include <QByteArray> #include <QIODevice> #include <QObject> #include <QTimer> #include "kiss_fft.h" class AudioInputHandler : public QObject, public QIODevice { Q_OBJECT public: explicit AudioInputHandler(QObject *parent = nullptr); void start(); QVector<double> getSpectrum() const { return m_spectrum; } signals: void spectrumReady(); private slots: void processAudio(); protected: qint64 readData(char *data, qint64 maxlen) override; qint64 writeData(const char *data, qint64 len) override; private: QAudioInput *m_audioInput; QByteArray m_buffer; QVector<double> m_spectrum; kiss_fft_cfg m_fftCfg; int m_sampleRate; int m_bufferSize; void computeFFT(const QVector<qint16> &samples); }; ``` ```cpp // audioinputhandler.cpp #include "audioinputhandler.h" #include <QAudioFormat> #include <QAudioDeviceInfo> #include <QDebug> #include <algorithm> #include <cmath> // 简化版 KissFFT 接口封装(确保已链接 kissfft) extern "C" { #include "kiss_fft.h" } AudioInputHandler::AudioInputHandler(QObject *parent) : QObject(parent), QIODevice(parent), m_sampleRate(44100), m_bufferSize(1024) { QAudioFormat format; format.setSampleRate(m_sampleRate); format.setChannelCount(1); format.setSampleSize(16); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice(); if (!info.isFormatSupported(format)) { qWarning() << "Warning: Desired format not supported, using nearest."; format = info.nearestFormat(format); } m_audioInput = new QAudioInput(format, this); open(QIODevice::WriteOnly); m_fftCfg = kiss_fft_alloc(m_bufferSize, 0, nullptr, nullptr); // 0=forward FFT m_spectrum.resize(m_bufferSize / 2); // 只取正频率部分 } void AudioInputHandler::start() { m_buffer.clear(); m_audioInput->start(this); QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &AudioInputHandler::processAudio); timer->start(30); // 每30ms处理一次频谱 } void AudioInputHandler::processAudio() { if (m_buffer.size() < m_bufferSize * 2) // 16位 = 2字节每样本 return; QVector<qint16> samples(m_bufferSize); const qint16 *rawData = reinterpret_cast<const qint16*>(m_buffer.constData()); for (int i = 0; i < m_bufferSize; ++i) { samples[i] = rawData[i]; // 单通道直接读取 } computeFFT(samples); m_buffer.remove(0, m_bufferSize * 2); // 移动窗口 emit spectrumReady(); } qint64 AudioInputHandler::readData(char *data, qint64 maxlen) { Q_UNUSED(data) Q_UNUSED(maxlen) return 0; // 我们不从中读取,只写入 } qint64 AudioInputHandler::writeData(const char *data, qint64 len) { m_buffer.append(data, len); return len; } void AudioInputHandler::computeFFT(const QVector<qint16> &samples) { // 创建输入输出缓冲区 kiss_fft_cpx *in = new kiss_fft_cpx[m_bufferSize]; kiss_fft_cpx *out = new kiss_fft_cpx[m_bufferSize]; for (int i = 0; i < m_bufferSize; ++i) { in[i].r = static_cast<qint16>(samples[i]) / 32768.0f; // 归一化到 [-1,1] in[i].i = 0.0f; } // 执行 FFT kiss_fft(m_fftCfg, in, out); // 计算幅值并填充频谱(仅前 N/2 项有意义) for (int i = 0; i < m_bufferSize / 2; ++i) { float re = out[i].r; float im = out[i].i; float mag = std::sqrt(re * re + im * im); // 对数缩放增强视觉效果 m_spectrum[i] = 20.0f * std::log10(mag + 1e-5f) + 100; // 偏移显示 if (m_spectrum[i] < 0) m_spectrum[i] = 0; } delete[] in; delete[] out; } ``` ```cpp // mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include "spectrumwidget.h" #include "audioinputhandler.h" class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); private slots: void updateSpectrum(); private: SpectrumWidget *m_spectrumWidget; AudioInputHandler *m_audioInputHandler; }; ``` ```cpp // mainwindow.cpp #include "mainwindow.h" #include <QVBoxLayout> #include <QWidget> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { m_spectrumWidget = new SpectrumWidget(this); setCentralWidget(m_spectrumWidget); m_audioInputHandler = new AudioInputHandler(this); connect(m_audioInputHandler, &AudioInputHandler::spectrumReady, this, &MainWindow::updateSpectrum); m_audioInputHandler->start(); } void MainWindow::updateSpectrum() { m_spectrumWidget->setSpectrumData(m_audioInputHandler->getSpectrum()); } ``` ```cpp // main.cpp #include <QApplication> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow w; w.show(); return app.exec(); } ``` --- ### 🔧 编译说明: 1. 将 `kissfft` 源码加入项目(`kiss_fft.c`, `kiss_fft.h` 等),并在 `.pro` 文件中包含: ```pro SOURCES += \ main.cpp \ spectrumwidget.cpp \ audioinputhandler.cpp \ mainwindow.cpp \ kiss_fft.c HEADERS += \ spectrumwidget.h \ audioinputhandler.h \ mainwindow.h \ kiss_fft.h ``` 2. 设置编译器支持 C++11: ```pro CONFIG += c++11 ``` --- ### 📌 解释: - **QAudioInput**:从麦克风捕获原始 PCM 数据。 - **QIODevice 子类**:作为音频数据的目标设备,接收实时音频流。 - **FFT 分析**:将时域波形转换为频率成分,体现各频率的能量强度。 - **SpectrumWidget**:自定义绘图部件,动态显示柱状频谱图。 - **定时刷新**:每 30ms 更新一次画面,保证流畅性。 --- ### ⚠️ 注意事项: - 若想用于“扬声器播放内容”的频谱(即回放监控),需使用 `QAudioOutput` 的反馈机制或 WASAPI Loopback(Windows 特有),Qt 原生不支持系统声音捕捉,需平台特定 API。 - 当前示例是 **麦克风输入频谱**,若要监听扬声器输出,必须使用操作系统提供的 loopback 录音功能(如 Windows 的 `eRender` 设备)。 - 性能优化可考虑双缓冲、降采样、使用 OpenGL 绘图(Qt Quick + Shader)等。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信必诺

嗨,支持下哥们呗。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值