Qt - 音频采集程序

Qt 麦克风音频数据采集(编码数据)

我们将使用Qt的多媒体模块来捕获音频,并编码保存到文件。同时,我们创建一个简单的界面,包含开始和停止按钮,以及状态显示。

步骤:

  1. 包含必要的头文件。

  2. 创建一个Qt应用程序,并设置界面。

  3. 使用QAudioRecorder来录制音频,并设置编码参数。

  4. 将录制的音频保存到文件,并在界面上显示状态。

注意:由于Qt6中一些多媒体类已被更改,我们这里使用Qt5的QAudioRecorder(因为Qt6中移除了QAudioRecorder,但Qt5中还有)。如果使用Qt6,可能需要使用QMediaRecorder和QAudioInput的方式。但根据问题要求,我们使用QAudioEncoderSettings,所以这里假设使用Qt5。

由于问题没有指定Qt版本,我们按照Qt5来实现。

我们将创建一个简单的窗口,包含两个按钮(开始和停止)和一个标签显示状态。

代码结构:

  • 包含头文件

  • 定义MainWindow类,私有成员:QAudioRecorder指针,按钮和标签。

  • 实现开始和停止槽函数。

注意:在.pro文件中需要添加 multimedia 模块。

下面开始编写代码:

 

我来为您实现一个使用Qt和C++的音频采集应用程序,可以编码并保存麦克风音频数据。

首先,在 .pro 文件中添加必要的模块:

1. AudioCaptureApp.pro

QT += core gui multimedia multimediawidgets

CONFIG += c++17

SOURCES = \
    main.cpp \
    mainwindow.cpp

HEADERS = \
    mainwindow.h

FORMS = \
    mainwindow.ui

2. mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QAudioRecorder>
#include <QAudioEncoderSettings>
#include <QAudioDeviceInfo>
#include <QMessageBox>
#include <QDateTime>
#include <QFileDialog>

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_startButton_clicked();
    void on_stopButton_clicked();
    void updateProgress(qint64 duration);
    void displayError();
    void updateStatus(QMediaRecorder::Status status);
    void updateAudioDevices();

private:
    Ui::MainWindow *ui;
    QAudioRecorder *audioRecorder;
    QAudioEncoderSettings audioSettings;
    QString outputFile;

    void setupAudioSettings();
    void setupUI();
    QString generateOutputFilename();
};
#endif // MAINWINDOW_H

3. mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , audioRecorder(new QAudioRecorder(this))
{
    ui->setupUi(this);
    
    setupUI();
    setupAudioSettings();
    updateAudioDevices();
    
    // 连接信号和槽
    connect(audioRecorder, &QAudioRecorder::durationChanged, this, &MainWindow::updateProgress);
    connect(audioRecorder, QOverload<QMediaRecorder::Error>::of(&QAudioRecorder::error), this, &MainWindow::displayError);
    connect(audioRecorder, &QAudioRecorder::statusChanged, this, &MainWindow::updateStatus);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::setupUI()
{
    // 设置按钮状态
    ui->stopButton->setEnabled(false);
    
    // 设置状态标签
    ui->statusLabel->setText("准备就绪");
    ui->progressLabel->setText("时长: 00:00");
    
    // 设置窗口标题和大小
    setWindowTitle("音频采集器");
    setMinimumSize(400, 300);
}

void MainWindow::setupAudioSettings()
{
    // 配置音频编码设置
    audioSettings.setCodec("audio/aac");
    audioSettings.setQuality(QMultimedia::HighQuality);
    audioSettings.setChannelCount(2);
    audioSettings.setSampleRate(44100);
    audioSettings.setBitRate(128000);
    audioSettings.setEncodingMode(QMultimedia::ConstantQualityEncoding);
    
    // 应用设置到录音器
    audioRecorder->setEncodingSettings(audioSettings);
    
    // 设置音频输入为默认麦克风
    QStringList audioInputs = audioRecorder->audioInputs();
    if (!audioInputs.isEmpty()) {
        audioRecorder->setAudioInput(audioInputs.first());
    }
    
    // 在UI上显示编码信息
    ui->codecLabel->setText(QString("编码器: %1").arg(audioSettings.codec()));
    ui->sampleRateLabel->setText(QString("采样率: %1 Hz").arg(audioSettings.sampleRate()));
    ui->bitrateLabel->setText(QString("比特率: %1 kbps").arg(audioSettings.bitRate() / 1000));
    ui->channelsLabel->setText(QString("声道: %1").arg(audioSettings.channelCount()));
}

void MainWindow::updateAudioDevices()
{
    ui->audioDeviceComboBox->clear();
    
    // 获取所有可用的音频输入设备
    QStringList audioInputs = audioRecorder->audioInputs();
    QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
    
    for (const QAudioDeviceInfo &device : devices) {
        ui->audioDeviceComboBox->addItem(device.deviceName());
    }
    
    if (audioInputs.isEmpty()) {
        ui->statusLabel->setText("错误: 未找到音频输入设备");
        ui->startButton->setEnabled(false);
    }
}

QString MainWindow::generateOutputFilename()
{
    QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
    return QString("audio_record_%1.m4a").arg(timestamp);
}

void MainWindow::on_startButton_clicked()
{
    // 选择保存文件的位置
    QString defaultFileName = generateOutputFilename();
    outputFile = QFileDialog::getSaveFileName(this, 
                                            "保存音频文件",
                                            QDir::homePath() + "/" + defaultFileName,
                                            "音频文件 (*.m4a *.aac *.mp3)");
    
    if (outputFile.isEmpty()) {
        return; // 用户取消了保存
    }
    
    // 设置输出文件
    audioRecorder->setOutputLocation(QUrl::fromLocalFile(outputFile));
    
    // 开始录音
    audioRecorder->record();
    
    // 更新UI状态
    ui->startButton->setEnabled(false);
    ui->stopButton->setEnabled(true);
    ui->statusLabel->setText("录音中...");
    ui->filenameLabel->setText(QString("文件: %1").arg(QFileInfo(outputFile).fileName()));
}

void MainWindow::on_stopButton_clicked()
{
    // 停止录音
    audioRecorder->stop();
    
    // 更新UI状态
    ui->startButton->setEnabled(true);
    ui->stopButton->setEnabled(false);
    ui->statusLabel->setText("录音已停止");
    
    QMessageBox::information(this, "完成", 
                           QString("音频已保存到:\n%1").arg(outputFile));
}

void MainWindow::updateProgress(qint64 duration)
{
    // 更新录音时长显示
    qint64 seconds = duration / 1000;
    qint64 minutes = seconds / 60;
    seconds = seconds % 60;
    
    ui->progressLabel->setText(QString("时长: %1:%2")
                              .arg(minutes, 2, 10, QLatin1Char('0'))
                              .arg(seconds, 2, 10, QLatin1Char('0')));
}

void MainWindow::displayError()
{
    QMessageBox::warning(this, "错误", 
                        QString("录音错误: %1").arg(audioRecorder->errorString()));
    
    ui->statusLabel->setText("错误发生");
    ui->startButton->setEnabled(true);
    ui->stopButton->setEnabled(false);
}

void MainWindow::updateStatus(QMediaRecorder::Status status)
{
    QString statusText;
    
    switch (status) {
    case QMediaRecorder::RecordingStatus:
        statusText = "录音中";
        break;
    case QMediaRecorder::PausedStatus:
        statusText = "已暂停";
        break;
    case QMediaRecorder::StoppedStatus:
        statusText = "已停止";
        break;
    default:
        statusText = "未知状态";
        break;
    }
    
    ui->statusLabel->setText(statusText);
}

4. mainwindow.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>500</width>
    <height>400</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>音频采集器</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QGroupBox" name="groupBox">
      <property name="title">
       <string>音频设备</string>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout_2">
       <item>
        <widget class="QComboBox" name="audioDeviceComboBox"/>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <widget class="QGroupBox" name="groupBox_2">
      <property name="title">
       <string>编码信息</string>
      </property>
      <layout class="QGridLayout" name="gridLayout">
       <item row="0" column="0">
        <widget class="QLabel" name="codecLabel">
         <property name="text">
          <string>编码器: </string>
         </property>
        </widget>
       </item>
       <item row="0" column="1">
        <widget class="QLabel" name="sampleRateLabel">
         <property name="text">
          <string>采样率: </string>
         </property>
        </widget>
       </item>
       <item row="1" column="0">
        <widget class="QLabel" name="bitrateLabel">
         <property name="text">
          <string>比特率: </string>
         </property>
        </widget>
       </item>
       <item row="1" column="1">
        <widget class="QLabel" name="channelsLabel">
         <property name="text">
          <string>声道: </string>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <widget class="QGroupBox" name="groupBox_3">
      <property name="title">
       <string>控制</string>
      </property>
      <layout class="QHBoxLayout" name="horizontalLayout">
       <item>
        <widget class="QPushButton" name="startButton">
         <property name="text">
          <string>开始录音</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QPushButton" name="stopButton">
         <property name="text">
          <string>停止录音</string>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <widget class="QGroupBox" name="groupBox_4">
      <property name="title">
       <string>状态信息</string>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout_3">
       <item>
        <widget class="QLabel" name="statusLabel">
         <property name="text">
          <string>准备就绪</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QLabel" name="progressLabel">
         <property name="text">
          <string>时长: 00:00</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QLabel" name="filenameLabel">
         <property name="text">
          <string>文件: 未选择</string>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <spacer name="verticalSpacer">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
      </property>
      <property name="sizeHint" stdset="0">
       <size>
        <width>20</width>
        <height>40</height>
       </size>
      </property>
     </spacer>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

main.cpp

cpp
#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    // 设置应用程序信息
    QApplication::setApplicationName("音频采集器");
    QApplication::setApplicationVersion("1.0");
    QApplication::setOrganizationName("MyCompany");
    
    MainWindow w;
    w.show();
    
    return a.exec();
}

功能特点

这个音频采集应用程序具有以下特点:

  1. 音频编码设置:使用 QAudioEncoderSettings 配置音频参数

    • 编码器:AAC

    • 采样率:44.1kHz

    • 比特率:128kbps

    • 声道:立体声

  2. 用户界面

    • 开始/停止控制按钮

    • 音频设备选择

    • 实时状态显示

    • 录音时长显示

    • 编码信息显示

  3. 文件保存

    • 自动生成带时间戳的文件名

    • 支持选择保存位置

    • 保存为常见的音频格式

  4. 错误处理

    • 设备检测

    • 录音错误处理

    • 状态监控

编译和运行

image

 

 

Qt 麦克风音频数据采集(原始数据)

我们将创建一个简单的Qt应用程序,用于采集麦克风的音频数据。我们将使用QAudioInput来捕获音频,并将捕获的原始PCM数据保存到文件中。同时,我们也会在界面上显示一些基本的控制按钮(开始、停止)和状态信息。

步骤:

  1. 创建Qt项目(例如使用Qt Widgets Application)。

  2. 在.pro文件中添加multimedia模块:QT += multimedia

  3. 设计一个简单的界面,包含两个按钮(开始和停止)和一个标签显示状态。

  4. 在后台代码中,设置音频输入设备,并当点击开始按钮时,开始捕获音频数据;点击停止按钮时,停止捕获。

注意:我们这里将捕获的原始PCM数据保存为文件,但请注意原始PCM数据没有文件头(如WAV头),所以不能直接播放。如果需要可播放的文件,可以添加WAV头或者使用其他格式。

但是,为了简单起见,我们只保存原始PCM数据。如果你需要播放,可以使用Audacity等工具导入原始数据(设置正确的采样率、样本大小和通道数)。

我们将使用以下音频格式:

  • 采样率:44100 Hz

  • 样本大小:16位

  • 通道数:2(立体声)

如果默认设备不支持这些参数,我们将使用最接近的设置。

代码结构:

  • 使用QAudioInput进行音频输入。

  • 使用QFile来存储捕获的音频数据。

界面设计:

  • 一个QPushButton用于开始捕获。

  • 一个QPushButton用于停止捕获(初始不可用)。

  • 一个QLabel用于显示状态。

逻辑:

  • 点击开始按钮后,初始化音频输入设备并开始捕获,同时将开始按钮禁用,停止按钮启用。

  • 点击停止按钮后,停止捕获,关闭文件,并将按钮状态重置。

我们将创建一个类,比如叫AudioCapture,来管理音频捕获。

由于Qt6中对多媒体模块有一些变化,请注意:如果你使用的是Qt6,需要确保包括正确的模块。在Qt6中,QAudioInput位于QtMultimedia模块中。

 

下面是一个完整的 Qt 应用程序,用于采集麦克风音频数据并实时显示波形。

1. AudioCaptureApp.pro

qmake
QT       += core gui multimedia widgets

CONFIG   += c++17

TARGET = AudioCaptureApp
TEMPLATE = app

SOURCES += \
    main.cpp \
    mainwindow.cpp \
    audiocapture.cpp

HEADERS += \
    mainwindow.h \
    audiocapture.h

FORMS += \
    widget.ui

main.cpp

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();
}

2. audiocapture.h

cpp
#ifndef AUDIOCAPTURE_H
#define AUDIOCAPTURE_H

#include <QObject>
#include <QAudioInput>
#include <QAudioDeviceInfo>
#include <QByteArray>
#include <QTimer>
#include <QDebug>

class AudioCapture : public QObject
{
    Q_OBJECT

public:
    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);
    void calculateMaxAmplitude();

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;
    quint32 m_maxAmplitude;
    qreal m_smoothedLevel;
    const qreal SMOOTHING_FACTOR = 0.2;
};

#endif // AUDIOCAPTURE_H

3. audiocapture.cpp

cpp
#include "audiocapture.h"
#include <QtEndian>

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_maxAmplitude(0)
    , m_smoothedLevel(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";
    }

    // 计算最大振幅
    calculateMaxAmplitude();

    // 创建音频输入对象
    m_audioInput = new QAudioInput(m_currentDevice, m_audioFormat, this);

    // 设置缓冲区大小
    m_audioInput->setBufferSize(4096);

    return true;
}

void AudioCapture::setupAudioFormat()
{
    m_audioFormat.setSampleRate(44100);      // 44.1 kHz
    m_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::calculateMaxAmplitude()
{
    switch (m_audioFormat.sampleSize()) {
    case 8:
        switch (m_audioFormat.sampleType()) {
        case QAudioFormat::UnSignedInt:
            m_maxAmplitude = 255;
            break;
        case QAudioFormat::SignedInt:
            m_maxAmplitude = 127;
            break;
        default:
            m_maxAmplitude = 255;
            break;
        }
        break;
    case 16:
        switch (m_audioFormat.sampleType()) {
        case QAudioFormat::UnSignedInt:
            m_maxAmplitude = 65535;
            break;
        case QAudioFormat::SignedInt:
            m_maxAmplitude = 32767;
            break;
        default:
            m_maxAmplitude = 32767;
            break;
        }
        break;
    case 32:
        switch (m_audioFormat.sampleType()) {
        case QAudioFormat::UnSignedInt:
            m_maxAmplitude = 0xffffffff;
            break;
        case QAudioFormat::SignedInt:
            m_maxAmplitude = 0x7fffffff;
            break;
        case QAudioFormat::Float:
            m_maxAmplitude = 0x7fffffff; // Kind of
        default:
            m_maxAmplitude = 0x7fffffff;
            break;
        }
        break;
    default:
        m_maxAmplitude = 32767;
        break;
    }

    qDebug() << "Max amplitude:" << m_maxAmplitude
             << "Sample size:" << m_audioFormat.sampleSize()
             << "Sample type:" << m_audioFormat.sampleType();
}

void AudioCapture::calculateAudioLevel(const QByteArray &data)
{
    if (m_maxAmplitude) {
        const int channelBytes = m_audioFormat.sampleSize() / 8;
        const int sampleBytes = m_audioFormat.channelCount() * channelBytes;

        // 使用 data.size() 而不是 len
        if (data.size() % sampleBytes != 0) {
            qDebug() << "Audio data length is not multiple of sample bytes";
            return;
        }

        const int numSamples = data.size() / sampleBytes;
        quint32 maxValue = 0;
        const unsigned char* ptr = reinterpret_cast<const unsigned char*>(data.constData());

        for (int i = 0; i < numSamples; ++i) {
            for (int j = 0; j < m_audioFormat.channelCount(); ++j) {
                quint32 value = 0;

                if (m_audioFormat.sampleSize() == 8 &&
                    m_audioFormat.sampleType() == QAudioFormat::UnSignedInt) {
                    value = *reinterpret_cast<const quint8*>(ptr);
                }
                else if (m_audioFormat.sampleSize() == 8 &&
                         m_audioFormat.sampleType() == QAudioFormat::SignedInt) {
                    value = qAbs(*reinterpret_cast<const qint8*>(ptr));
                }
                else if (m_audioFormat.sampleSize() == 16 &&
                         m_audioFormat.sampleType() == QAudioFormat::UnSignedInt) {
                    if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian)
                        value = qFromLittleEndian<quint16>(ptr);
                    else
                        value = qFromBigEndian<quint16>(ptr);
                }
                else if (m_audioFormat.sampleSize() == 16 &&
                         m_audioFormat.sampleType() == QAudioFormat::SignedInt) {
                    if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian)
                        value = qAbs(qFromLittleEndian<qint16>(ptr));
                    else
                        value = qAbs(qFromBigEndian<qint16>(ptr));
                }
                else if (m_audioFormat.sampleSize() == 32 &&
                         m_audioFormat.sampleType() == QAudioFormat::UnSignedInt) {
                    if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian)
                        value = qFromLittleEndian<quint32>(ptr);
                    else
                        value = qFromBigEndian<quint32>(ptr);
                }
                else if (m_audioFormat.sampleSize() == 32 &&
                         m_audioFormat.sampleType() == QAudioFormat::SignedInt) {
                    if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian)
                        value = qAbs(qFromLittleEndian<qint32>(ptr));
                    else
                        value = qAbs(qFromBigEndian<qint32>(ptr));
                }
                else if (m_audioFormat.sampleSize() == 32 &&
                         m_audioFormat.sampleType() == QAudioFormat::Float) {
                    value = qAbs(*reinterpret_cast<const float*>(ptr) * 0x7fffffff);
                }

                maxValue = qMax(value, maxValue);
                ptr += channelBytes;
            }
        }

        maxValue = qMin(maxValue, m_maxAmplitude);
        qreal rawLevel = qreal(maxValue) / m_maxAmplitude;

        // 添加轻微平滑,避免过度跳动
        m_smoothedLevel = SMOOTHING_FACTOR * rawLevel + (1 - SMOOTHING_FACTOR) * m_smoothedLevel;
        m_currentLevel = m_smoothedLevel;
    }
}

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_smoothedLevel = 0.0; // 重置平滑电平

    // 启动电平计时器
    m_levelTimer->start(50);

    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;
    m_smoothedLevel = 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::updateAudioLevel()
{
    emit audioLevelChanged(m_currentLevel);
}

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();
    }
}

4. mainwindow.h

cpp
#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_OBJECT

public:
    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

5. mainwindow.cpp

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();
}

功能特点

  1. 设备选择:自动检测并列出所有可用的音频输入设备

  2. 实时监控:显示音频电平,可视化音频输入强度

  3. 灵活控制:开始/停止、暂停/继续采集

  4. 参数设置:可调节采样率、声道数等音频参数

  5. 数据保存:将采集的原始PCM数据保存为文件

  6. 状态显示:实时显示采集状态和统计数据

编译和运行

  1. 使用 Qt Creator 打开项目文件 AudioCaptureApp.pro

  2. 配置正确的 Qt 版本(确保包含 multimedia 模块)

  3. 编译并运行项目

使用说明

  1. 启动应用程序后,选择要使用的音频输入设备

  2. 根据需要调整采样率和声道数设置

  3. 点击"开始采集"按钮开始录音

  4. 观察音频电平显示,确认设备正常工作

  5. 点击"暂停"可以临时停止采集

  6. 点击"停止采集"结束录音

  7. 点击"保存数据"将采集的音频保存为文件

image

 

posted @ 2025-09-23 15:48  [BORUTO]  阅读(39)  评论(0)    收藏  举报