文章的目的为了记录使用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。