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

文章的目的为了记录使用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()适配

  1. WAV文件处理

实时生成符合标准的WAV文件头

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

支持标准的RIFF WAVE格式

  1. 用户界面交互

录音按钮切换开始/停止状态

实时显示录音时长和进度

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

二、核心代码分析

  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("程序启动完成");        // 记录启动日志
}
  1. 析构函数 MainWindow::~MainWindow()

功能:清理资源和内存

详细分析:

复制代码
MainWindow::~MainWindow()
{
    stopRecording();    // 确保停止正在进行的录音
    delete ui;          // 删除UI对象(自动删除所有子控件)
}
  1. 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);
}
  1. 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();
    }
}
  1. 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);
}
  1. 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));
}
  1. 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));
}
  1. 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。

相关推荐
咕噜签名分发冰淇淋3 小时前
苹果ios的系统app应用WebClip免签应用开源及方式原理
ios·开源·cocoa
zwhSunday3 小时前
Linux驱动开发(2)进一步理解驱动
linux·驱动开发
算家计算3 小时前
一张图+一段音频=电影级视频!阿里Wan2.2-S2V-14B本地部署教程:实现丝滑口型同步
人工智能·开源·aigc
非优秀程序员3 小时前
免费宝藏书《MCP 从入门到实践(图文指南版)》速览
人工智能·开源·产品
Miraitowa_cheems3 小时前
LeetCode算法日记 - Day 38: 二叉树的锯齿形层序遍历、二叉树最大宽度
java·linux·运维·算法·leetcode·链表·职场和发展
勇闯逆流河4 小时前
【Linux】Linux常用指令合集
linux·运维·服务器
劲镝丶4 小时前
malloc概述
c语言·开发语言·c++
AI Echoes4 小时前
LLMOps平台:开源项目LMForge = GPTs + Coze
人工智能·python·langchain·开源·agent
柯一梦4 小时前
Linux权限以及常用热键集合
linux