开源 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()适配
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; // 文件总大小-8char 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; // 文件总大小-8header.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 += widgetsCONFIG += 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.0SOURCES += \main.cpp \mainwindow.cppHEADERS += \mainwindow.hFORMS += \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_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic: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; // PCMquint16 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。