开源 C++ QT QML 开发(十九)多媒体--音频录制

文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

相关链接:

开源 C++ QT QML 开发(一)基本介绍

开源 C++ QT QML 开发(二)工程结构

开源 C++ QT QML 开发(三)常用控件

开源 C++ QT QML 开发(四)复杂控件--Listview

开源 C++ QT QML 开发(五)复杂控件--Gridview

开源 C++ QT QML 开发(六)自定义控件--波形图

开源 C++ QT QML 开发(七)自定义控件--仪表盘

开源 C++ QT QML 开发(八)自定义控件--圆环

开源 C++ QT QML 开发(九)文件--文本和二进制

开源 C++ QT QML 开发(十)通讯--串口

开源 C++ QT QML 开发(十一)通讯--TCP服务器端

开源 C++ QT QML 开发(十二)通讯--TCP客户端

开源 C++ QT QML 开发(十三)多线程

开源 C++ QT QML 开发(十四)进程用途

开源 C++ QT QML 开发(十五)通讯--http下载

开源 C++ QT QML 开发(十六)进程--共享内存

开源 C++ QT QML 开发(十七)进程--LocalSocket

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

推荐链接:

开源 C# 快速开发(一)基础知识

开源 C# 快速开发(二)基础控件

开源 C# 快速开发(三)复杂控件

开源 C# 快速开发(四)自定义控件--波形图

开源 C# 快速开发(五)自定义控件--仪表盘

开源 C# 快速开发(六)自定义控件--圆环

开源 C# 快速开发(七)通讯--串口

开源 C# 快速开发(八)通讯--Tcp服务器端

开源 C# 快速开发(九)通讯--Tcp客户端

开源 C# 快速开发(十)通讯--http客户端

开源 C# 快速开发(十一)线程

开源 C# 快速开发(十二)进程监控

开源 C# 快速开发(十三)进程--管道通讯

开源 C# 快速开发(十四)进程--内存映射

开源 C# 快速开发(十五)进程--windows消息

开源 C# 快速开发(十六)数据库--sqlserver增删改查

本章节主要内容是:qml的多媒体音频的录制,保存为wav的16KHz格式。

1.代码分析

2.所有源码

3.效果演示

一、代码分析

  1. AudioManager 类分析

1.1 构造函数 AudioManager::AudioManager()

复制代码
AudioManager::AudioManager(QObject *parent)
    : QObject(parent)
    , m_audioInput(nullptr)
    , m_outputFile(nullptr)
    , m_buffer(nullptr)
    , m_timer(new QTimer(this))
    , m_isRecording(false)
    , m_audioDuration(0)
    , m_statusText("就绪")
    , m_timeText("00:00")
    , m_progress(0)
    , m_logText("")
{
    // 连接计时器信号到更新进度槽函数
    connect(m_timer, &QTimer::timeout, this, &AudioManager::updateProgress);
    // 记录启动日志
    logMessage("程序启动完成");
}

功能分析:

初始化所有成员变量:将指针变量初始化为nullptr,布尔值初始化为false,数值初始化为0,字符串初始化为默认值

创建计时器:QTimer用于定期更新录音进度显示

信号槽连接:将计时器的timeout信号连接到updateProgress槽函数,每100ms触发一次

初始状态设置:设置初始状态文本、时间显示和进度

日志记录:调用logMessage记录程序启动信息

1.2 析构函数 AudioManager::~AudioManager()

复制代码
AudioManager::~AudioManager()
{
    stopRecording();
}

功能分析:

资源清理:在对象销毁时自动调用stopRecording()确保录音被正确停止

防止资源泄漏:确保所有动态分配的资源被正确释放

1.3 音频输入设置 AudioManager::setupAudioInput()

复制代码
void AudioManager::setupAudioInput()
{
    // 设置音频格式:16kHz, 16位, 单声道
    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()));

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

功能分析:

音频格式配置:设置标准的PCM音频格式参数

硬件兼容性检查:检查默认音频设备是否支持指定格式

格式自适应:如果不支持则使用最接近的格式

日志记录:记录最终使用的音频格式信息

对象创建:创建QAudioInput对象用于音频输入

1.4 开始录音 AudioManager::startRecording()

复制代码
void AudioManager::startRecording()
{
    if (m_isRecording) return;  // 防止重复开始

    logMessage("开始录音...");

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

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

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

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

    // 开始录音
    m_audioInput->start(m_buffer);
    m_isRecording = true;

    // 启动计时器
    m_timer->start(100);  // 100ms间隔
    m_audioDuration = 0;

    // 更新状态
    m_statusText = "正在录音...";
    m_progress = 0;
    m_timeText = "00:00";

    // 发出属性变化信号
    emit isRecordingChanged();
    emit statusTextChanged();
    emit timeTextChanged();
    emit progressChanged();

    logMessage("录音进行中");
}

功能分析:

状态检查:防止在录音状态下重复开始

资源初始化:依次初始化音频输入、输出文件、缓冲区

文件操作:创建WAV文件并写入初始文件头

缓冲区设置:创建内存缓冲区存储音频数据

启动录音:调用QAudioInput的start()方法开始录音

计时器启动:开始计时器用于更新UI

状态更新:更新所有相关状态变量

信号发射:通知QML界面更新显示

1.5 停止录音 AudioManager::stopRecording()

复制代码
void AudioManager::stopRecording()
{
    if (!m_isRecording) return;  // 如果不在录音状态,直接返回

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

    // 停止录音
    m_audioInput->stop();
    m_isRecording = false;

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

    // 写入音频数据到文件
    if (m_buffer && m_outputFile) {
        QByteArray audioData = m_buffer->data();
        m_outputFile->seek(0);  // 回到文件开头
        setupWavHeader(*m_outputFile, audioData.size());  // 重新写入正确的文件头
        m_outputFile->write(audioData);  // 写入音频数据
        m_outputFile->close();

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

    // 清理资源
    if (m_buffer) {
        m_buffer->close();
        m_buffer->deleteLater();
        m_buffer = nullptr;
    }
    if (m_outputFile) {
        m_outputFile->deleteLater();
        m_outputFile = nullptr;
    }
    if (m_audioInput) {
        m_audioInput->deleteLater();
        m_audioInput = nullptr;
    }

    // 更新状态
    m_statusText = "录音完成";
    m_progress = 100;

    // 发出属性变化信号
    emit isRecordingChanged();
    emit statusTextChanged();
    emit progressChanged();
}

功能分析:

状态检查:确保在录音状态下才执行停止操作

停止录音:调用QAudioInput的stop()方法

停止计时器:停止进度更新

文件处理:

获取缓冲区中的音频数据

重新写入正确的WAV文件头(包含实际数据大小)

写入音频数据并关闭文件

资源清理:使用deleteLater安全删除对象

状态更新:更新UI状态为完成

1.6 更新进度 AudioManager::updateProgress()

复制代码
void AudioManager::updateProgress()
{
    m_audioDuration += 100;  // 每次增加100ms
    int seconds = m_audioDuration / 1000;  // 转换为秒
    
    // 格式化时间显示:MM:SS
    m_timeText = QString("%1:%2")
                .arg(seconds / 60, 2, 10, QLatin1Char('0'))  // 分钟,2位,补0
                .arg(seconds % 60, 2, 10, QLatin1Char('0')); // 秒钟,2位,补0
                
    m_progress = qMin(100, seconds);  // 进度最大100%

    // 发出变化信号
    emit timeTextChanged();
    emit progressChanged();
}

功能分析:

时间累计:每次调用增加100ms录音时间

时间格式化:将毫秒转换为MM:SS格式的字符串

进度计算:每秒增加1%进度,最大100%

信号发射:通知QML更新时间显示和进度条

1.7 WAV文件头设置 AudioManager::setupWavHeader()

复制代码
void AudioManager::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 * 1 * 16 / 8;       // 采样率 × 声道数 × 位深度 / 8
    header.blockAlign = 1 * 16 / 8;             // 声道数 × 位深度 / 8
    header.dataChunkSize = dataSize;            // 音频数据大小

    // 写入文件头到文件
    file.write(reinterpret_cast<const char*>(&header), sizeof(WavHeader));
}

功能分析:

WAV文件结构:定义标准的WAV文件头结构

参数计算:

chunkSize = 36 + 数据大小(文件总大小-8)

byteRate = 采样率 × 声道数 × 位深度 ÷ 8

blockAlign = 声道数 × 位深度 ÷ 8

二进制写入:将结构体以二进制形式写入文件

1.8 日志记录 AudioManager::logMessage()

复制代码
void AudioManager::logMessage(const QString &message)
{
    QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
    QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
    
    // 添加换行符(如果不是第一条日志)
    if (!m_logText.isEmpty()) {
        m_logText += "\n";
    }
    m_logText += logEntry;
    
    // 发出日志变化信号
    emit logTextChanged();
    // 同时输出到调试控制台
    qDebug() << message;
}

功能分析:

时间戳生成:获取当前时间并格式化为hh:mm:ss

日志格式化:组合时间戳和消息内容

日志累积:在现有日志后追加新日志,用换行符分隔

信号通知:通知QML界面更新日志显示

调试输出:同时输出到qDebug便于调试

  1. Main函数分析

2.1 应用程序初始化 main()

复制代码
int main(int argc, char *argv[])
{
    // 启用高DPI缩放
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    // 创建GUI应用程序
    QGuiApplication app(argc, argv);

    // 设置应用程序信息
    app.setApplicationName("音频录音机");
    app.setApplicationVersion("1.0");
    app.setOrganizationName("MyCompany");

    // 注册AudioManager类到QML系统
    qmlRegisterType<AudioManager>("AudioRecorder", 1, 0, "AudioManager");

    // 创建QML引擎
    QQmlApplicationEngine engine;
    
    // 创建全局AudioManager实例并设置为上下文属性
    AudioManager *audioManager = new AudioManager(&app);
    engine.rootContext()->setContextProperty("audioManager", audioManager);

    // 加载QML文件
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    
    engine.load(url);

    // 检查QML加载是否成功
    if (engine.rootObjects().isEmpty()) {
        return -1;
    }

    // 进入事件循环
    return app.exec();
}

功能分析:

应用程序配置:设置高DPI缩放和应用信息

QML类型注册:将C++类注册到QML系统中

上下文属性设置:创建AudioManager实例并使其在QML中可用

QML加载:加载并显示QML界面

错误处理:检查QML加载是否成功

事件循环:启动Qt事件循环

  1. QML界面分析

3.1 主窗口结构

复制代码
ApplicationWindow {
    id: window
    width: 600
    height: 500
    title: "音频录音机 - QML版本"
    visible: true

    // 背景渐变
    Rectangle {
        anchors.fill: parent
        gradient: Gradient {
            GradientStop { position: 0.0; color: "#2c3e50" }
            GradientStop { position: 1.0; color: "#34495e" }
        }
    }
    // ... 其他组件
}

功能分析:

窗口设置:定义窗口大小、标题和可见性

背景设计:使用渐变背景创造现代感界面

3.2 录音按钮实现

复制代码
Button {
    id: recordButton
    text: audioManager.isRecording ? "停止录音" : "开始录音"
    // ... 其他属性
    
    background: Rectangle {
        radius: 30
        color: audioManager.isRecording ? "#e74c3c" : "#2ecc71"
        // ... 其他属性
        
        Behavior on color {
            ColorAnimation { duration: 300 }
        }
    }
    
    onClicked: {
        if (audioManager.isRecording) {
            audioManager.stopRecording()
        } else {
            audioManager.startRecording()
        }
    }
}

功能分析:

动态文本:根据录音状态显示不同文本

颜色切换:录音时显示红色,停止时显示绿色

动画效果:颜色变化时有300ms的过渡动画

点击处理:调用C++的start/stopRecording方法

3.3 数据绑定机制

复制代码
// 状态文本绑定
Label {
    text: audioManager.statusText  // 自动绑定到C++属性
}

// 时间显示绑定  
Label {
    text: audioManager.timeText    // 自动绑定到C++属性
}

// 进度条绑定
ProgressBar {
    value: audioManager.progress   // 自动绑定到C++属性
}

// 日志显示绑定
TextArea {
    text: audioManager.logText     // 自动绑定到C++属性
}

功能分析:

自动更新:当C++端属性变化并发出信号时,QML界面自动更新

双向通信:C++逻辑控制业务,QML界面负责显示和用户交互

  1. 程序架构总结

这个录音程序采用了典型的MVC架构:

Model:AudioManager类,负责所有录音逻辑和数据处理

View:QML界面,负责用户界面显示和交互

Controller:QML中的事件处理和数据绑定

数据流:

用户点击QML按钮

QML调用C++的录音方法

C++处理录音逻辑并更新属性

C++发出属性变化信号

QML自动更新界面显示

二、所有源码

audiomanager.h文件源码

复制代码
#ifndef AUDIOMANAGER_H
#define AUDIOMANAGER_H

#include <QObject>
#include <QAudioInput>
#include <QAudioFormat>
#include <QFile>
#include <QBuffer>
#include <QTimer>
#include <QDateTime>

class AudioManager : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bool isRecording READ isRecording NOTIFY isRecordingChanged)
    Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged)
    Q_PROPERTY(QString timeText READ timeText NOTIFY timeTextChanged)
    Q_PROPERTY(int progress READ progress NOTIFY progressChanged)
    Q_PROPERTY(QString logText READ logText NOTIFY logTextChanged)

public:
    explicit AudioManager(QObject *parent = nullptr);
    ~AudioManager();

    bool isRecording() const { return m_isRecording; }
    QString statusText() const { return m_statusText; }
    QString timeText() const { return m_timeText; }
    int progress() const { return m_progress; }
    QString logText() const { return m_logText; }

    Q_INVOKABLE void startRecording();
    Q_INVOKABLE void stopRecording();

private slots:
    void updateProgress();

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

    QAudioInput *m_audioInput;
    QFile *m_outputFile;
    QBuffer *m_buffer;
    QTimer *m_timer;

    bool m_isRecording;
    int m_audioDuration;

    // Q_PROPERTY backing fields
    QString m_statusText;
    QString m_timeText;
    int m_progress;
    QString m_logText;

signals:
    void isRecordingChanged();
    void statusTextChanged();
    void timeTextChanged();
    void progressChanged();
    void logTextChanged();
};

#endif // AUDIOMANAGER_H

audiomanager.cpp文件源码

复制代码
#include "audiomanager.h"
#include <QAudioDeviceInfo>
#include <QDir>
#include <QDebug>
#include <cmath>

AudioManager::AudioManager(QObject *parent)
    : QObject(parent)
    , m_audioInput(nullptr)
    , m_outputFile(nullptr)
    , m_buffer(nullptr)
    , m_timer(new QTimer(this))
    , m_isRecording(false)
    , m_audioDuration(0)
    , m_statusText("就绪")
    , m_timeText("00:00")
    , m_progress(0)
    , m_logText("")
{
    connect(m_timer, &QTimer::timeout, this, &AudioManager::updateProgress);
    logMessage("程序启动完成");
}

AudioManager::~AudioManager()
{
    stopRecording();
}

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

    m_audioInput = new QAudioInput(format, this);
}

void AudioManager::startRecording()
{
    if (m_isRecording) return;

    logMessage("开始录音...");

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

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

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

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

    // 开始录音
    m_audioInput->start(m_buffer);
    m_isRecording = true;

    // 启动计时器
    m_timer->start(100);
    m_audioDuration = 0;

    // 更新状态
    m_statusText = "正在录音...";
    m_progress = 0;
    m_timeText = "00:00";

    emit isRecordingChanged();
    emit statusTextChanged();
    emit timeTextChanged();
    emit progressChanged();

    logMessage("录音进行中");
}

void AudioManager::stopRecording()
{
    if (!m_isRecording) return;

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

    // 停止录音
    m_audioInput->stop();
    m_isRecording = false;

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

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

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

    // 清理资源
    if (m_buffer) {
        m_buffer->close();
        m_buffer->deleteLater();
        m_buffer = nullptr;
    }
    if (m_outputFile) {
        m_outputFile->deleteLater();
        m_outputFile = nullptr;
    }
    if (m_audioInput) {
        m_audioInput->deleteLater();
        m_audioInput = nullptr;
    }

    // 更新状态
    m_statusText = "录音完成";
    m_progress = 100;

    emit isRecordingChanged();
    emit statusTextChanged();
    emit progressChanged();
}

void AudioManager::updateProgress()
{
    m_audioDuration += 100;
    int seconds = m_audioDuration / 1000;
    m_timeText = QString("%1:%2")
                .arg(seconds / 60, 2, 10, QLatin1Char('0'))
                .arg(seconds % 60, 2, 10, QLatin1Char('0'));
    m_progress = qMin(100, seconds);

    emit timeTextChanged();
    emit progressChanged();
}

void AudioManager::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 * 1 * 16 / 8; // sampleRate * channels * bitsPerSample / 8
    header.blockAlign = 1 * 16 / 8; // channels * bitsPerSample / 8
    header.dataChunkSize = dataSize;

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

void AudioManager::logMessage(const QString &message)
{
    QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
    QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);

    if (!m_logText.isEmpty()) {
        m_logText += "\n";
    }
    m_logText += logEntry;

    emit logTextChanged();
    qDebug() << message;
}

main.qml文件源码

复制代码
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14

ApplicationWindow {
    id: window
    width: 600
    height: 500
    title: "音频录音机 - QML版本"
    visible: true

    // 背景渐变
    Rectangle {
        anchors.fill: parent
        gradient: Gradient {
            GradientStop { position: 0.0; color: "#2c3e50" }
            GradientStop { position: 1.0; color: "#34495e" }
        }
    }

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // 标题
        Label {
            text: "音频录音机"
            Layout.alignment: Qt.AlignHCenter
            color: "white"
            font.pixelSize: 28
            font.bold: true
        }

        // 状态显示区域
        Rectangle {
            Layout.fillWidth: true
            Layout.preferredHeight: 120
            color: "#34495e"
            radius: 10
            border.color: "#3498db"
            border.width: 2

            ColumnLayout {
                anchors.fill: parent
                anchors.margins: 15
                spacing: 10

                // 状态标签
                Label {
                    text: audioManager.statusText
                    Layout.fillWidth: true
                    color: "white"
                    font.pixelSize: 18
                    horizontalAlignment: Text.AlignHCenter
                }

                // 时间显示
                Label {
                    text: audioManager.timeText
                    Layout.fillWidth: true
                    color: "#e74c3c"
                    font.pixelSize: 32
                    font.bold: true
                    horizontalAlignment: Text.AlignHCenter
                }

                // 进度条
                ProgressBar {
                    id: progressBar
                    Layout.fillWidth: true
                    from: 0
                    to: 100
                    value: audioManager.progress

                    background: Rectangle {
                        implicitWidth: 200
                        implicitHeight: 6
                        color: "#2c3e50"
                        radius: 3
                    }

                    contentItem: Item {
                        implicitWidth: 200
                        implicitHeight: 4

                        Rectangle {
                            width: progressBar.visualPosition * parent.width
                            height: parent.height
                            radius: 2
                            gradient: Gradient {
                                GradientStop { position: 0.0; color: "#3498db" }
                                GradientStop { position: 1.0; color: "#2980b9" }
                            }
                        }
                    }
                }
            }
        }

        // 录音按钮
        Button {
            id: recordButton
            Layout.alignment: Qt.AlignHCenter
            Layout.preferredWidth: 200
            Layout.preferredHeight: 60
            text: audioManager.isRecording ? "停止录音" : "开始录音"
            font.pixelSize: 18
            font.bold: true

            background: Rectangle {
                radius: 30
                color: audioManager.isRecording ? "#e74c3c" : "#2ecc71"
                border.color: audioManager.isRecording ? "#c0392b" : "#27ae60"
                border.width: 3

                Behavior on color {
                    ColorAnimation { duration: 300 }
                }
            }

            contentItem: Text {
                text: recordButton.text
                font: recordButton.font
                color: "white"
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }

            onClicked: {
                if (audioManager.isRecording) {
                    audioManager.stopRecording()
                } else {
                    audioManager.startRecording()
                }
            }
        }

        // 日志区域
        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "#1e272e"
            radius: 10
            border.color: "#7f8c8d"
            border.width: 1

            ColumnLayout {
                anchors.fill: parent
                spacing: 5

                Label {
                    text: "操作日志"
                    color: "#bdc3c7"
                    font.pixelSize: 16
                    font.bold: true
                    Layout.leftMargin: 10
                    Layout.topMargin: 5
                }

                ScrollView {
                    Layout.fillWidth: true
                    Layout.fillHeight: true
                    Layout.margins: 10

                    TextArea {
                        id: logTextArea
                        text: audioManager.logText
                        color: "#ecf0f1"
                        font.pixelSize: 12
                        font.family: "Consolas, Monaco, monospace"
                        wrapMode: Text.Wrap
                        readOnly: true
                        selectByMouse: true
                        background: Rectangle {
                            color: "transparent"
                        }
                    }
                }
            }
        }

        // 底部信息
        Label {
            text: "基于Qt5.14 QML的录音程序 | 支持WAV格式 | 16kHz 16位 单声道"
            Layout.alignment: Qt.AlignHCenter
            color: "#95a5a6"
            font.pixelSize: 12
        }
    }

    // 录音时的动画效果
    Rectangle {
        id: recordingIndicator
        width: 20
        height: 20
        radius: 10
        color: "#e74c3c"
        visible: audioManager.isRecording
        anchors.top: parent.top
        anchors.right: parent.right
        anchors.margins: 15

        SequentialAnimation on opacity {
            running: recordingIndicator.visible
            loops: Animation.Infinite
            NumberAnimation { from: 0.3; to: 1.0; duration: 800 }
            NumberAnimation { from: 1.0; to: 0.3; duration: 800 }
        }
    }
}

main.cpp文件源码

复制代码
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "audiomanager.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    // 设置应用程序信息
    app.setApplicationName("音频录音机");
    app.setApplicationVersion("1.0");
    app.setOrganizationName("MyCompany");

    // 注册AudioManager类到QML
    qmlRegisterType<AudioManager>("AudioRecorder", 1, 0, "AudioManager");

    QQmlApplicationEngine engine;

    // 创建全局AudioManager实例
    AudioManager *audioManager = new AudioManager(&app);
    engine.rootContext()->setContextProperty("audioManager", audioManager);

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);

    engine.load(url);

    if (engine.rootObjects().isEmpty()) {
        return -1;
    }

    return app.exec();
}

三、效果演示

相关推荐
取酒鱼食--【余九】3 小时前
机器人学基础(一)【坐标系和位姿变换】
笔记·算法·机器人·开源·机器人运动学·机器人学基础
晨非辰3 小时前
【面试高频数据结构(四)】--《从单链到双链的进阶,读懂“双向奔赴”的算法之美与效率权衡》
java·数据结构·c++·人工智能·算法·机器学习·面试
野猪亨利6674 小时前
Qt day1
开发语言·数据库·qt
cookies_s_s4 小时前
LRU Cache 最近最少使用
c++
isaki1375 小时前
qt day1
开发语言·数据库·qt
流星白龙5 小时前
【Qt】4.项目文件解析
开发语言·数据库·qt
郝学胜-神的一滴5 小时前
深入解析Linux下的`lseek`函数:文件定位与操作的艺术
linux·运维·服务器·开发语言·c++·软件工程
仰泳的熊猫5 小时前
LeetCode:889. 根据前序和后序遍历构造二叉树
数据结构·c++·算法
ajassi20006 小时前
开源 Linux 服务器与中间件(二)嵌入式Linux服务器和中间件
linux·服务器·开源