开源 C++ QT Widget 开发(十四)多媒体--录音机

发布于:2025-09-11 ⋅ 阅读:(21) ⋅ 点赞:(0)

   文章的目的为了记录使用C++ 进行QT Widget 开发学习的经历。临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
 

 相关链接:

开源 C++ QT Widget 开发(一)工程文件结构-CSDN博客

开源 C++ QT Widget 开发(二)基本控件应用-CSDN博客

开源 C++ QT Widget 开发(三)图表--波形显示器-CSDN博客

开源 C++ QT Widget 开发(四)文件--二进制文件查看编辑-CSDN博客

 开源 C++ QT Widget 开发(五)通讯--串口调试-CSDN博客

开源 C++ QT Widget 开发(六)通讯--TCP调试-CSDN博客

开源 C++ QT Widget 开发(七)线程--多线程及通讯-CSDN博客

开源 C++ QT Widget 开发(八)网络--Http文件下载-CSDN博客

开源 C++ QT Widget 开发(九)图表--仪表盘-CSDN博客

开源 C++ QT Widget 开发(十)IPC进程间通信--共享内存-CSDN博客

开源 C++ QT Widget 开发(十一)进程间通信--Windows 窗口通信-CSDN博客

开源 C++ QT Widget 开发(十二)图表--环境监测表盘-CSDN博客

开源 C++ QT Widget 开发(十三)IPC通讯--本地套接字 (Local Socket)

开源 C++ QT Widget 开发(十四)多媒体--录音机

开源 C++ QT Widget 开发(十五)多媒体--音频播放



推荐链接:

开源 java android app 开发(一)开发环境的搭建-CSDN博客

开源 java android app 开发(二)工程文件结构-CSDN博客

开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客

开源 java android app 开发(四)GUI界面重要组件-CSDN博客

开源 java android app 开发(五)文件和数据库存储-CSDN博客

开源 java android app 开发(六)多媒体使用-CSDN博客

开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客

开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客

开源 java android app 开发(九)后台之线程和服务-CSDN博客

开源 java android app 开发(十)广播机制-CSDN博客

开源 java android app 开发(十一)调试、发布-CSDN博客

开源 java android app 开发(十二)封库.aar-CSDN博客

推荐链接:

开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客

开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客

开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-CSDN博客

开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-CSDN博客

开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客

内容:Qt音频录音机应用程序。实现win10系统下,用笔记本进行录音的功能。

目录:

1.功能介绍

2.核心代码分析

3.所有源码

4.显示效果

一、功能介绍

1. 音频录制功能
使用QAudioInput进行音频输入捕获

设置16kHz采样率、16位深度、单声道PCM格式

自动处理不支持的格式,使用nearestFormat()适配

2. WAV文件处理
实时生成符合标准的WAV文件头

先预留头部空间,录音完成后填充实际数据大小

支持标准的RIFF WAVE格式

3. 用户界面交互
录音按钮切换开始/停止状态

实时显示录音时长和进度

状态标签和日志系统提供用户反馈

二、核心代码分析

1. 构造函数 MainWindow::MainWindow()
功能:初始化主窗口和所有成员变量

详细分析:


// 初始化列表:正确初始化所有成员变量
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)          // 创建UI对象
    , audioInput(nullptr)             // 音频输入设备初始为空
    , outputFile(nullptr)             // 输出文件初始为空
    , buffer(nullptr)                 // 缓冲区初始为空
    , timer(new QTimer(this))         // 创建计时器,设置父对象
    , isRecording(false)              // 录音状态初始为false
    , audioDuration(0)                // 录音时长初始为0
{
    ui->setupUi(this);                // 设置UI界面
    
    setWindowTitle("音频录音机");      // 设置窗口标题
    
    // 连接计时器信号到更新进度槽函数
    connect(timer, &QTimer::timeout, this, &MainWindow::updateProgress);
    
    // 初始化UI控件状态
    ui->recordButton->setText("开始录音");
    ui->statusLabel->setText("就绪");
    ui->progressBar->setValue(0);
    
    logMessage("程序启动完成");        // 记录启动日志
}


2. 析构函数 MainWindow::~MainWindow()
功能:清理资源和内存

详细分析:

MainWindow::~MainWindow()
{
    stopRecording();    // 确保停止正在进行的录音
    delete ui;          // 删除UI对象(自动删除所有子控件)
}



3. setupAudioInput() - 音频输入设置
功能:配置音频输入设备和格式参数

详细分析:


void MainWindow::setupAudioInput()
{
    // 设置PCM音频格式参数
    QAudioFormat format;
    format.setSampleRate(16000);     // 16kHz采样率(适合语音)
    format.setChannelCount(1);       // 单声道
    format.setSampleSize(16);        // 16位采样深度
    format.setCodec("audio/pcm");    // PCM编码
    format.setByteOrder(QAudioFormat::LittleEndian); // 小端字节序
    format.setSampleType(QAudioFormat::SignedInt);   // 有符号整数
    
    // 检查设备支持
    QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
    if (!info.isFormatSupported(format)) {
        logMessage("警告:默认格式不支持,使用最近似格式");
        format = info.nearestFormat(format); // 自适应调整格式
    }
    
    // 记录实际使用的格式
    logMessage(QString("音频格式:%1Hz, %2位, %3声道")
              .arg(format.sampleRate())
              .arg(format.sampleSize())
              .arg(format.channelCount()));
    
    // 创建音频输入对象
    audioInput = new QAudioInput(format, this);
}


4. on_recordButton_clicked() - 录音按钮点击处理
功能:处理开始/停止录音的逻辑切换

详细分析:

void MainWindow::on_recordButton_clicked()
{
    if (!isRecording) {
        // 开始录音分支
        logMessage("开始录音...");
        
        setupAudioInput(); // 设置音频输入
        
        // 创建输出文件
        QString filePath = QDir::currentPath() + "/input.wav";
        outputFile = new QFile(filePath, this);
        if (!outputFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
            logMessage("错误:无法创建文件");
            delete outputFile;
            outputFile = nullptr;
            return; // 错误处理:文件创建失败时返回
        }
        
        setupWavHeader(*outputFile, 0); // 写入空的WAV文件头
        
        // 创建内存缓冲区
        buffer = new QBuffer(this);
        buffer->open(QIODevice::ReadWrite);
        
        audioInput->start(buffer); // 开始录音到缓冲区
        isRecording = true;
        
        timer->start(100); // 启动100ms间隔的计时器
        audioDuration = 0; // 重置录音时长
        
        // 更新UI状态
        ui->recordButton->setText("停止录音");
        ui->statusLabel->setText("正在录音...");
        logMessage("录音进行中");
        
    } else {
        // 停止录音分支
        stopRecording();
    }
}


5. stopRecording() - 停止录音
功能:停止录音并完成文件保存

详细分析:

void MainWindow::stopRecording()
{
    if (!isRecording) return; // 安全检查:如果不在录音状态则返回
    
    logMessage("停止录音...");
    
    audioInput->stop();     // 停止音频输入
    isRecording = false;    // 更新状态
    
    timer->stop();          // 停止计时器
    
    // 处理录音数据
    if (buffer && outputFile) {
        QByteArray audioData = buffer->data(); // 获取缓冲区数据
        outputFile->seek(0); // 回到文件开头
        setupWavHeader(*outputFile, audioData.size()); // 写入正确的文件头
        outputFile->write(audioData); // 写入音频数据
        outputFile->close(); // 关闭文件
        
        logMessage(QString("录音完成,文件大小:%1 字节").arg(audioData.size()));
    }
    
    // 清理资源
    if (buffer) {
        buffer->close();
        buffer = nullptr;
    }
    if (outputFile) {
        delete outputFile;
        outputFile = nullptr;
    }
    if (audioInput) {
        delete audioInput;
        audioInput = nullptr;
    }
    
    // 更新UI状态
    ui->recordButton->setText("开始录音");
    ui->statusLabel->setText("录音完成");
    ui->progressBar->setValue(100);
}



6. updateProgress() - 更新进度
功能:定时更新录音进度和显示

详细分析:

void MainWindow::updateProgress()
{
    audioDuration += 100; // 增加100ms(计时器间隔)
    int seconds = audioDuration / 1000; // 转换为秒
    
    // 格式化时间显示 (MM:SS)
    ui->timeLabel->setText(QString("%1:%2")
                          .arg(seconds / 60, 2, 10, QLatin1Char('0'))  // 分钟
                          .arg(seconds % 60, 2, 10, QLatin1Char('0'))); // 秒
    
    // 更新进度条(最大100秒)
    ui->progressBar->setValue(qMin(100, seconds));
}


7. setupWavHeader() - 设置WAV文件头
功能:生成标准的WAV文件头结构

详细分析:

void MainWindow::setupWavHeader(QFile &file, quint32 dataSize)
{
    // WAV文件头结构定义
    struct WavHeader {
        char riff[4] = {'R','I','F','F'};      // RIFF标识
        quint32 chunkSize;                     // 文件总大小-8
        char wave[4] = {'W','A','V','E'};      // WAVE标识
        char fmt[4] = {'f','m','t',' '};       // fmt块标识
        quint32 fmtChunkSize = 16;             // fmt块大小
        quint16 audioFormat = 1;               // 格式代码(1=PCM)
        quint16 numChannels = 1;               // 声道数
        quint32 sampleRate = 16000;            // 采样率
        quint32 byteRate;                      // 字节率
        quint16 blockAlign;                    // 块对齐
        quint16 bitsPerSample = 16;            // 位深度
        char data[4] = {'d','a','t','a'};      // data块标识
        quint32 dataChunkSize;                 // 数据块大小
    };
    
    WavHeader header;
    header.chunkSize = 36 + dataSize;     // 文件总大小-8
    header.byteRate = 16000 * 2;          // 采样率×位深度÷8×声道数
    header.blockAlign = 2;                // 位深度÷8×声道数
    header.dataChunkSize = dataSize;      // 音频数据大小
    
    // 写入文件头
    file.write(reinterpret_cast<const char*>(&header), sizeof(WavHeader));
}


8. logMessage() - 日志记录
功能:统一的消息日志记录系统

详细分析:


void MainWindow::logMessage(const QString &message)
{
    QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
    ui->logTextEdit->append(QString("[%1] %2").arg(timestamp).arg(message));
    qDebug() << message; // 同时输出到控制台
}

三、所有源码

1. pro文件

QT       += core gui
QT       += core gui multimedia
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

2.mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QAudioInput>
#include <QFile>
#include <QBuffer>
#include <QTimer>

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_recordButton_clicked();
    void updateProgress();

private:
    Ui::MainWindow *ui;
    QAudioInput *audioInput;
    QFile *outputFile;
    QBuffer *buffer;
    QTimer *timer;
    bool isRecording;
    qint64 audioDuration;

    void setupAudioInput();
    void stopRecording();
    void setupWavHeader(QFile &file, quint32 dataSize);
    void logMessage(const QString &message);
};

#endif // MAINWINDOW_H

3.mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QAudioDeviceInfo>
#include <QAudioFormat>
#include <QDir>
#include <QDateTime>
#include <QDebug>
#include <cmath>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , audioInput(nullptr)
    , outputFile(nullptr)
    , buffer(nullptr)
    , timer(new QTimer(this))
    , isRecording(false)
    , audioDuration(0)
{
    ui->setupUi(this);

    // 设置窗口标题
    setWindowTitle("音频录音机");

    // 连接计时器
    connect(timer, &QTimer::timeout, this, &MainWindow::updateProgress);

    // 初始化UI状态
    ui->recordButton->setText("开始录音");
    ui->statusLabel->setText("就绪");
    ui->progressBar->setValue(0);

    logMessage("程序启动完成");
}

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

void MainWindow::setupAudioInput()
{
    // 设置音频格式:16kHz, 16位, 单声道
    QAudioFormat format;
    format.setSampleRate(16000);
    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)) {
        logMessage("警告:默认格式不支持,使用最近似格式");
        format = info.nearestFormat(format);
    }

    logMessage(QString("音频格式:%1Hz, %2位, %3声道")
              .arg(format.sampleRate())
              .arg(format.sampleSize())
              .arg(format.channelCount()));

    audioInput = new QAudioInput(format, this);
}

void MainWindow::on_recordButton_clicked()
{
    if (!isRecording) {
        // 开始录音
        logMessage("开始录音...");

        // 设置音频输入
        setupAudioInput();

        // 创建输出文件
        QString filePath = QDir::currentPath() + "/input.wav";
        outputFile = new QFile(filePath, this);
        if (!outputFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
            logMessage("错误:无法创建文件");
            delete outputFile;
            outputFile = nullptr;
            return;
        }

        // 先写入空的WAV文件头
        setupWavHeader(*outputFile, 0);

        // 创建缓冲区
        buffer = new QBuffer(this);
        buffer->open(QIODevice::ReadWrite);

        // 开始录音
        audioInput->start(buffer);
        isRecording = true;

        // 启动计时器
        timer->start(100);
        audioDuration = 0;

        // 更新UI状态
        ui->recordButton->setText("停止录音");
        ui->statusLabel->setText("正在录音...");
        logMessage("录音进行中");

    } else {
        // 停止录音
        stopRecording();
    }
}

void MainWindow::stopRecording()
{
    if (!isRecording) return;

    logMessage("停止录音...");

    // 停止录音
    audioInput->stop();
    isRecording = false;

    // 停止计时器
    timer->stop();

    // 写入音频数据到文件
    if (buffer && outputFile) {
        QByteArray audioData = buffer->data();
        outputFile->seek(0);
        setupWavHeader(*outputFile, audioData.size());
        outputFile->write(audioData);
        outputFile->close();

        logMessage(QString("录音完成,文件大小:%1 字节").arg(audioData.size()));
    }

    // 清理资源
    if (buffer) {
        buffer->close();
        buffer = nullptr;
    }
    if (outputFile) {
        delete outputFile;
        outputFile = nullptr;
    }
    if (audioInput) {
        delete audioInput;
        audioInput = nullptr;
    }

    // 更新UI状态
    ui->recordButton->setText("开始录音");
    ui->statusLabel->setText("录音完成");
    ui->progressBar->setValue(100);
}

void MainWindow::updateProgress()
{
    audioDuration += 100;
    int seconds = audioDuration / 1000;
    ui->timeLabel->setText(QString("%1:%2")
                          .arg(seconds / 60, 2, 10, QLatin1Char('0'))
                          .arg(seconds % 60, 2, 10, QLatin1Char('0')));
    ui->progressBar->setValue(qMin(100, seconds));
}

void MainWindow::setupWavHeader(QFile &file, quint32 dataSize)
{
    // WAV文件头结构
    struct WavHeader {
        char riff[4] = {'R','I','F','F'};
        quint32 chunkSize;
        char wave[4] = {'W','A','V','E'};
        char fmt[4] = {'f','m','t',' '};
        quint32 fmtChunkSize = 16;
        quint16 audioFormat = 1; // PCM
        quint16 numChannels = 1;
        quint32 sampleRate = 16000;
        quint32 byteRate;
        quint16 blockAlign;
        quint16 bitsPerSample = 16;
        char data[4] = {'d','a','t','a'};
        quint32 dataChunkSize;
    };

    WavHeader header;
    header.chunkSize = 36 + dataSize;
    header.byteRate = 16000 * 2;
    header.blockAlign = 2;
    header.dataChunkSize = dataSize;

    file.write(reinterpret_cast<const char*>(&header), sizeof(WavHeader));
}

void MainWindow::logMessage(const QString &message)
{
    QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
    ui->logTextEdit->append(QString("[%1] %2").arg(timestamp).arg(message));
    qDebug() << message;
}

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>500</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>音频录音机</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QLabel" name="titleLabel">
      <property name="text">
       <string>音频录音机</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
      <property name="font">
       <font>
        <pointsize>16</pointsize>
        <bold>true</bold>
       </font>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QLabel" name="timeLabel">
      <property name="text">
       <string>00:00</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
      <property name="font">
       <font>
        <pointsize>24</pointsize>
        <bold>true</bold>
       </font>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QProgressBar" name="progressBar">
      <property name="value">
       <number>0</number>
      </property>
      <property name="textVisible">
       <bool>true</bool>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="recordButton">
      <property name="text">
       <string>开始录音</string>
      </property>
      <property name="minimumSize">
       <size>
        <width>0</width>
        <height>50</height>
       </size>
      </property>
      <property name="styleSheet">
       <string>QPushButton {
    background-color: #ff4444;
    color: white;
    font-weight: bold;
    font-size: 14px;
    border-radius: 5px;
}
QPushButton:hover {
    background-color: #ff6666;
}
QPushButton:pressed {
    background-color: #dd2222;
}</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QLabel" name="statusLabel">
      <property name="text">
       <string>就绪</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
      <property name="frameShape">
       <enum>QFrame::Box</enum>
      </property>
      <property name="font">
       <font>
        <pointsize>10</pointsize>
       </font>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QLabel" name="fileInfoLabel">
      <property name="text">
       <string>保存文件:input.wav</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
      <property name="frameShape">
       <enum>QFrame::Panel</enum>
      </property>
      <property name="frameShadow">
       <enum>QFrame::Sunken</enum>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QTextEdit" name="logTextEdit">
      <property name="maximumHeight">
       <number>150</number>
      </property>
      <property name="readOnly">
       <bool>true</bool>
      </property>
      <property name="placeholderText">
       <string>日志信息将显示在这里...</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

四、显示效果

点击按钮开始录音,打印过程,停止录音,保存为工程文件夹下的input.wav。


网站公告

今日签到

点亮在社区的每一天
去签到