我们将创建一个简单的Qt应用程序,用于采集麦克风的音频数据。我们将使用QAudioInput来捕获音频,并将捕获的原始PCM数据保存到文件中。同时,我们也会在界面上显示一些基本的控制按钮(开始、停止)和状态信息。
步骤:
-
创建Qt项目(例如使用Qt Widgets Application)。
-
在.pro文件中添加multimedia模块:QT += multimedia
-
设计一个简单的界面,包含两个按钮(开始和停止)和一个标签显示状态。
-
在后台代码中,设置音频输入设备,并当点击开始按钮时,开始捕获音频数据;点击停止按钮时,停止捕获。
注意:我们这里将捕获的原始PCM数据保存为文件,但请注意原始PCM数据没有文件头(如WAV头),所以不能直接播放。如果需要可播放的文件,可以添加WAV头或者使用其他格式。
但是,为了简单起见,我们只保存原始PCM数据。如果你需要播放,可以使用Audacity等工具导入原始数据(设置正确的采样率、样本大小和通道数)。
我们将使用以下音频格式:
-
采样率:44100 Hz
-
样本大小:16位
-
通道数:2(立体声)
如果默认设备不支持这些参数,我们将使用最接近的设置。
代码结构:
-
使用QAudioInput进行音频输入。
-
使用QFile来存储捕获的音频数据。
界面设计:
-
一个QPushButton用于开始捕获。
-
一个QPushButton用于停止捕获(初始不可用)。
-
一个QLabel用于显示状态。
逻辑:
-
点击开始按钮后,初始化音频输入设备并开始捕获,同时将开始按钮禁用,停止按钮启用。
-
点击停止按钮后,停止捕获,关闭文件,并将按钮状态重置。
我们将创建一个类,比如叫AudioCapture,来管理音频捕获。
由于Qt6中对多媒体模块有一些变化,请注意:如果你使用的是Qt6,需要确保包括正确的模块。在Qt6中,QAudioInput位于QtMultimedia模块中。
Qt 麦克风音频数据采集应用
下面是一个完整的 Qt 应用程序,用于采集麦克风音频数据并实时显示波形。
1. 项目文件(AudioCaptureApp.pro)
QT += core gui multimedia widgetsCONFIG += c++17TARGET = AudioCaptureApp
TEMPLATE = appSOURCES += \main.cpp \mainwindow.cpp \audiocapture.cppHEADERS += \mainwindow.h \audiocapture.hFORMS += \widget.ui
2. 主程序入口 (main.cpp)
#include <QApplication>
#include "mainwindow.h"int main(int argc, char *argv[])
{QApplication app(argc, argv);// 设置应用程序信息app.setApplicationName("音频采集应用");app.setApplicationVersion("1.0");app.setOrganizationName("MyCompany");MainWindow window;window.show();return app.exec();
}
3. 音频采集类头文件 (audiocapture.h)
#ifndef AUDIOCAPTURE_H
#define AUDIOCAPTURE_H#include <QObject>
#include <QAudioInput>
#include <QAudioDeviceInfo>
#include <QByteArray>
#include <QTimer>
#include <QDebug>class AudioCapture : public QObject
{Q_OBJECTpublic:explicit AudioCapture(QObject *parent = nullptr);~AudioCapture();// 音频设备操作bool initializeAudio();void startCapture();void stopCapture();void pauseCapture();void resumeCapture();// 设备信息QList<QAudioDeviceInfo> getAvailableDevices() const;QString getCurrentDeviceName() const;// 音频参数设置void setSampleRate(int rate);void setChannelCount(int channels);void setSampleSize(int size);// 状态查询bool isCapturing() const { return m_isCapturing; }qint64 getBytesCaptured() const { return m_bytesCaptured; }signals:// 音频数据可用信号void audioDataAvailable(const QByteArray &data);// 音频电平信号(用于显示波形)void audioLevelChanged(qreal level);// 状态变化信号void captureStateChanged(bool capturing);// 错误信号void errorOccurred(const QString &errorMessage);public slots:void setAudioDevice(const QAudioDeviceInfo &device);private slots:void handleAudioDataReady();void updateAudioLevel();private:void setupAudioFormat();void calculateAudioLevel(const QByteArray &data);private:QAudioInput *m_audioInput;QIODevice *m_audioDevice;QAudioFormat m_audioFormat;QAudioDeviceInfo m_currentDevice;QTimer *m_levelTimer;bool m_isCapturing;bool m_isPaused;qint64 m_bytesCaptured;qreal m_currentLevel;
};#endif // AUDIOCAPTURE_H
4. 音频采集类实现 (audiocapture.cpp)
#include "audiocapture.h"AudioCapture::AudioCapture(QObject *parent) : QObject(parent), m_audioInput(nullptr), m_audioDevice(nullptr), m_isCapturing(false), m_isPaused(false), m_bytesCaptured(0), m_currentLevel(0.0)
{m_levelTimer = new QTimer(this);connect(m_levelTimer, &QTimer::timeout, this, &AudioCapture::updateAudioLevel);
}AudioCapture::~AudioCapture()
{stopCapture();
}bool AudioCapture::initializeAudio()
{// 获取默认输入设备m_currentDevice = QAudioDeviceInfo::defaultInputDevice();if (m_currentDevice.isNull()) {emit errorOccurred("未找到可用的音频输入设备");return false;}// 设置音频格式setupAudioFormat();// 检查格式支持if (!m_currentDevice.isFormatSupported(m_audioFormat)) {m_audioFormat = m_currentDevice.nearestFormat(m_audioFormat);qDebug() << "使用最接近的音频格式:" << m_audioFormat.sampleRate() << "Hz," << m_audioFormat.channelCount() << "channels,"<< m_audioFormat.sampleSize() << "bits";}// 创建音频输入对象m_audioInput = new QAudioInput(m_currentDevice, m_audioFormat, this);// 设置缓冲区大小m_audioInput->setBufferSize(4096);return true;
}void AudioCapture::setupAudioFormat()
{m_audioFormat.setSampleRate(44100); // 44.1 kHzm_audioFormat.setChannelCount(1); // 单声道m_audioFormat.setSampleSize(16); // 16位m_audioFormat.setCodec("audio/pcm"); // PCM编码m_audioFormat.setByteOrder(QAudioFormat::LittleEndian);m_audioFormat.setSampleType(QAudioFormat::SignedInt);
}void AudioCapture::startCapture()
{if (!m_audioInput) {if (!initializeAudio()) {return;}}if (m_isCapturing && !m_isPaused) {return;}if (m_isPaused) {resumeCapture();return;}// 开始音频采集m_audioDevice = m_audioInput->start();if (!m_audioDevice) {emit errorOccurred("无法启动音频设备");return;}// 连接数据可用信号connect(m_audioDevice, &QIODevice::readyRead, this, &AudioCapture::handleAudioDataReady);m_isCapturing = true;m_isPaused = false;m_bytesCaptured = 0;// 启动电平计时器(用于实时显示)m_levelTimer->start(50); // 每50ms更新一次电平emit captureStateChanged(true);qDebug() << "开始音频采集,设备:" << m_currentDevice.deviceName();
}void AudioCapture::stopCapture()
{if (m_audioInput) {m_audioInput->stop();if (m_audioDevice) {m_audioDevice->disconnect();m_audioDevice = nullptr;}}m_levelTimer->stop();m_isCapturing = false;m_isPaused = false;m_currentLevel = 0.0;emit captureStateChanged(false);emit audioLevelChanged(0.0);qDebug() << "停止音频采集,总采集数据:" << m_bytesCaptured << "字节";
}void AudioCapture::pauseCapture()
{if (m_isCapturing && !m_isPaused) {m_audioInput->suspend();m_isPaused = true;m_levelTimer->stop();emit audioLevelChanged(0.0);}
}void AudioCapture::resumeCapture()
{if (m_isCapturing && m_isPaused) {m_audioInput->resume();m_isPaused = false;m_levelTimer->start(50);}
}void AudioCapture::handleAudioDataReady()
{if (!m_audioDevice) return;// 读取音频数据QByteArray audioData = m_audioDevice->readAll();if (audioData.isEmpty()) return;m_bytesCaptured += audioData.size();// 计算音频电平(用于波形显示)calculateAudioLevel(audioData);// 发送数据可用信号emit audioDataAvailable(audioData);
}void AudioCapture::calculateAudioLevel(const QByteArray &data)
{const qint16 *samples = reinterpret_cast<const qint16*>(data.constData());int sampleCount = data.size() / 2; // 16位样本,每个样本2字节qreal maxValue = 0;for (int i = 0; i < sampleCount; ++i) {qreal value = qAbs(samples[i]) / 32768.0; // 16位有符号整数范围:-32768到32767if (value > maxValue) {maxValue = value;}}m_currentLevel = maxValue;
}void AudioCapture::updateAudioLevel()
{emit audioLevelChanged(m_currentLevel);m_currentLevel = 0.0; // 重置为下次计算
}QList<QAudioDeviceInfo> AudioCapture::getAvailableDevices() const
{return QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
}QString AudioCapture::getCurrentDeviceName() const
{return m_currentDevice.deviceName();
}void AudioCapture::setAudioDevice(const QAudioDeviceInfo &device)
{if (m_isCapturing) {stopCapture();}m_currentDevice = device;// 重新初始化音频输入if (m_audioInput) {delete m_audioInput;m_audioInput = nullptr;}initializeAudio();
}void AudioCapture::setSampleRate(int rate)
{m_audioFormat.setSampleRate(rate);if (m_isCapturing) {// 需要重新启动采集stopCapture();startCapture();}
}void AudioCapture::setChannelCount(int channels)
{m_audioFormat.setChannelCount(channels);if (m_isCapturing) {stopCapture();startCapture();}
}void AudioCapture::setSampleSize(int size)
{m_audioFormat.setSampleSize(size);if (m_isCapturing) {stopCapture();startCapture();}
}
5. 主窗口头文件 (mainwindow.h)
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QPushButton>
#include <QLabel>
#include <QComboBox>
#include <QProgressBar>
#include <QCheckBox>
#include <QSpinBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QFileDialog>
#include <QMessageBox>
#include "audiocapture.h"class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void onStartStopClicked();void onPauseResumeClicked();void onSaveDataClicked();void onDeviceChanged(int index);void handleAudioDataAvailable(const QByteArray &data);void handleAudioLevelChanged(qreal level);void handleCaptureStateChanged(bool capturing);void handleErrorOccurred(const QString &errorMessage);private:void setupUI();void setupConnections();void updateDeviceList();void updateUIState();private:// UI 组件QWidget *m_centralWidget;QVBoxLayout *m_mainLayout;QGroupBox *m_deviceGroup;QComboBox *m_deviceCombo;QLabel *m_statusLabel;QProgressBar *m_levelBar;QGroupBox *m_controlGroup;QPushButton *m_startStopButton;QPushButton *m_pauseResumeButton;QPushButton *m_saveButton;QGroupBox *m_settingsGroup;QCheckBox *m_autoSaveCheck;QSpinBox *m_sampleRateSpin;QSpinBox *m_channelsSpin;QLabel *m_statsLabel;// 音频采集AudioCapture *m_audioCapture;// 数据存储QByteArray m_capturedData;bool m_isCapturing;bool m_isPaused;
};#endif // MAINWINDOW_H
6. 主窗口实现 (mainwindow.cpp)
#include "mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), m_audioCapture(new AudioCapture(this)), m_isCapturing(false), m_isPaused(false)
{setWindowTitle("Qt 音频采集应用");setMinimumSize(600, 400);setupUI();setupConnections();updateDeviceList();updateUIState();
}MainWindow::~MainWindow()
{if (m_isCapturing) {m_audioCapture->stopCapture();}
}void MainWindow::setupUI()
{m_centralWidget = new QWidget(this);setCentralWidget(m_centralWidget);m_mainLayout = new QVBoxLayout(m_centralWidget);// 设备选择组m_deviceGroup = new QGroupBox("音频设备", this);QVBoxLayout *deviceLayout = new QVBoxLayout(m_deviceGroup);m_deviceCombo = new QComboBox(this);deviceLayout->addWidget(m_deviceCombo);m_statusLabel = new QLabel("状态: 未启动", this);deviceLayout->addWidget(m_statusLabel);m_levelBar = new QProgressBar(this);m_levelBar->setRange(0, 100);m_levelBar->setValue(0);m_levelBar->setFormat("音频电平: %p%");deviceLayout->addWidget(m_levelBar);m_mainLayout->addWidget(m_deviceGroup);// 控制按钮组m_controlGroup = new QGroupBox("控制", this);QHBoxLayout *controlLayout = new QHBoxLayout(m_controlGroup);m_startStopButton = new QPushButton("开始采集", this);m_pauseResumeButton = new QPushButton("暂停", this);m_saveButton = new QPushButton("保存数据", this);m_pauseResumeButton->setEnabled(false);m_saveButton->setEnabled(false);controlLayout->addWidget(m_startStopButton);controlLayout->addWidget(m_pauseResumeButton);controlLayout->addWidget(m_saveButton);m_mainLayout->addWidget(m_controlGroup);// 设置组m_settingsGroup = new QGroupBox("设置", this);QHBoxLayout *settingsLayout = new QHBoxLayout(m_settingsGroup);m_autoSaveCheck = new QCheckBox("自动保存", this);m_sampleRateSpin = new QSpinBox(this);m_sampleRateSpin->setRange(8000, 192000);m_sampleRateSpin->setValue(44100);m_sampleRateSpin->setSuffix(" Hz");m_channelsSpin = new QSpinBox(this);m_channelsSpin->setRange(1, 2);m_channelsSpin->setValue(1);m_channelsSpin->setSuffix(" 声道");settingsLayout->addWidget(new QLabel("采样率:", this));settingsLayout->addWidget(m_sampleRateSpin);settingsLayout->addWidget(new QLabel("声道数:", this));settingsLayout->addWidget(m_channelsSpin);settingsLayout->addWidget(m_autoSaveCheck);settingsLayout->addStretch();m_mainLayout->addWidget(m_settingsGroup);// 统计信息m_statsLabel = new QLabel("采集数据: 0 字节", this);m_mainLayout->addWidget(m_statsLabel);m_mainLayout->addStretch();
}void MainWindow::setupConnections()
{connect(m_startStopButton, &QPushButton::clicked, this, &MainWindow::onStartStopClicked);connect(m_pauseResumeButton, &QPushButton::clicked, this, &MainWindow::onPauseResumeClicked);connect(m_saveButton, &QPushButton::clicked, this, &MainWindow::onSaveDataClicked);connect(m_deviceCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),this, &MainWindow::onDeviceChanged);connect(m_audioCapture, &AudioCapture::audioDataAvailable,this, &MainWindow::handleAudioDataAvailable);connect(m_audioCapture, &AudioCapture::audioLevelChanged,this, &MainWindow::handleAudioLevelChanged);connect(m_audioCapture, &AudioCapture::captureStateChanged,this, &MainWindow::handleCaptureStateChanged);connect(m_audioCapture, &AudioCapture::errorOccurred,this, &MainWindow::handleErrorOccurred);
}void MainWindow::updateDeviceList()
{m_deviceCombo->clear();QList<QAudioDeviceInfo> devices = m_audioCapture->getAvailableDevices();for (const QAudioDeviceInfo &device : devices) {m_deviceCombo->addItem(device.deviceName(), QVariant::fromValue(device));}if (devices.isEmpty()) {m_deviceCombo->addItem("未找到音频设备");m_startStopButton->setEnabled(false);}
}void MainWindow::updateUIState()
{if (m_isCapturing) {m_startStopButton->setText("停止采集");m_pauseResumeButton->setEnabled(true);m_saveButton->setEnabled(false); // 采集时不能保存m_deviceCombo->setEnabled(false);m_sampleRateSpin->setEnabled(false);m_channelsSpin->setEnabled(false);if (m_isPaused) {m_pauseResumeButton->setText("继续");m_statusLabel->setText("状态: 已暂停");} else {m_pauseResumeButton->setText("暂停");m_statusLabel->setText("状态: 采集中...");}} else {m_startStopButton->setText("开始采集");m_pauseResumeButton->setEnabled(false);m_pauseResumeButton->setText("暂停");m_saveButton->setEnabled(!m_capturedData.isEmpty());m_deviceCombo->setEnabled(true);m_sampleRateSpin->setEnabled(true);m_channelsSpin->setEnabled(true);m_statusLabel->setText("状态: 未启动");}m_statsLabel->setText(QString("采集数据: %1 字节").arg(m_capturedData.size()));
}void MainWindow::onStartStopClicked()
{if (m_isCapturing) {m_audioCapture->stopCapture();} else {// 应用设置m_audioCapture->setSampleRate(m_sampleRateSpin->value());m_audioCapture->setChannelCount(m_channelsSpin->value());// 开始采集m_audioCapture->startCapture();m_capturedData.clear(); // 清空之前的数据}
}void MainWindow::onPauseResumeClicked()
{if (m_isPaused) {m_audioCapture->resumeCapture();m_isPaused = false;} else {m_audioCapture->pauseCapture();m_isPaused = true;}updateUIState();
}void MainWindow::onSaveDataClicked()
{if (m_capturedData.isEmpty()) {QMessageBox::information(this, "提示", "没有可保存的音频数据");return;}QString fileName = QFileDialog::getSaveFileName(this, "保存音频数据", "", "原始音频数据 (*.raw);;所有文件 (*)");if (!fileName.isEmpty()) {QFile file(fileName);if (file.open(QIODevice::WriteOnly)) {file.write(m_capturedData);file.close();QMessageBox::information(this, "成功", "音频数据已保存");} else {QMessageBox::warning(this, "错误", "无法保存文件");}}
}void MainWindow::onDeviceChanged(int index)
{if (index >= 0) {QAudioDeviceInfo device = m_deviceCombo->itemData(index).value<QAudioDeviceInfo>();m_audioCapture->setAudioDevice(device);}
}void MainWindow::handleAudioDataAvailable(const QByteArray &data)
{m_capturedData.append(data);updateUIState();// 如果启用了自动保存,可以在这里实现自动保存逻辑if (m_autoSaveCheck->isChecked()) {// 自动保存实现(可选)}
}void MainWindow::handleAudioLevelChanged(qreal level)
{int levelPercent = qMin(100, static_cast<int>(level * 100));m_levelBar->setValue(levelPercent);
}void MainWindow::handleCaptureStateChanged(bool capturing)
{m_isCapturing = capturing;m_isPaused = false;updateUIState();
}void MainWindow::handleErrorOccurred(const QString &errorMessage)
{QMessageBox::critical(this, "音频采集错误", errorMessage);m_isCapturing = false;m_isPaused = false;updateUIState();
}
功能特点
-
设备选择:自动检测并列出所有可用的音频输入设备
-
实时监控:显示音频电平,可视化音频输入强度
-
灵活控制:开始/停止、暂停/继续采集
-
参数设置:可调节采样率、声道数等音频参数
-
数据保存:将采集的原始PCM数据保存为文件
-
状态显示:实时显示采集状态和统计数据
编译和运行
-
使用 Qt Creator 打开项目文件
AudioCaptureApp.pro
-
配置正确的 Qt 版本(确保包含 multimedia 模块)
-
编译并运行项目
使用说明
-
启动应用程序后,选择要使用的音频输入设备
-
根据需要调整采样率和声道数设置
-
点击"开始采集"按钮开始录音
-
观察音频电平显示,确认设备正常工作
-
点击"暂停"可以临时停止采集
-
点击"停止采集"结束录音
-
点击"保存数据"将采集的音频保存为文件