简介:这是一份基于Qt5.6框架开发的轻量级录音机源码项目,面向C++与Qt初学者及GUI音视频开发入门者,解决跨平台音频录制、实时播放与麦克风音量可视化等核心实践问题。资源共6个文件,含2个关键实现文件(main.cpp与Audiorecorder.cpp)、1个界面定义文件(.ui)、1个头文件(.h)、1个工程配置文件(.pro)和1个用户配置文件(.pro.user),总大小仅7KB,结构精简,便于快速编译运行与代码剖析。已有907人学习下载,是理解Qt Multimedia模块(QAudioInput/QAudioOutput/QAudioLevelControl)、信号槽事件驱动、实时音量显示与WAV文件保存机制的典型教学案例。读者可直接构建运行,深入掌握音频设备控制、GUI动态响应、异步数据流处理及Qt Designer界面集成等关键技术点,为后续开发更复杂的多媒体应用打下扎实基础。
1. 一个能真正在 Windows/macOS/Linux 上按下“录音”就写进 WAV 文件的 QT5 录音机:不依赖第三方 DLL,不调用系统命令,纯 Qt Multimedia 模块实现,适合嵌入式 Qt 环境或教学项目快速验证音频采集链路
你有没有试过在 Qt 项目里加个录音功能,结果卡在QAudioRecorder找不到后端、QMediaRecorder报错 “No valid encoder found”、或者干脆编译不过——因为 CMakeLists.txt 里漏了find_package(Qt5Multimedia)?这个项目就是为这种时刻写的:它用 Qt5.12+ 原生QAudioInput+QBuffer+QDataStream手动拼出标准 WAV 头 + PCM 数据流,全程不碰 GStreamer、不调用 ffmpeg 命令行、不依赖系统录音服务。我把它跑在树莓派 4 的 Qt5.15.2(无桌面环境)上,只开一个QApplication和QAudioInput,就能把麦克风数据实时写成可播放的.wav文件。它不是玩具 Demo——文件头严格遵循 RIFF/WAV 格式规范(44.1kHz/16bit/stereo),用 Audacity、VLC、甚至 Windows 自带播放器都能秒开;它也不是黑匣子——核心录音逻辑就 3 个类:AudioRecorder(主控)、WavWriter(文件封装)、AudioDeviceManager(设备枚举)。如果你正卡在“Qt 怎么把麦克风数据存成文件”这一步,或者需要在一个精简 Qt 构建中(比如-no-gstreamer -no-alsa)实现基础录音能力,这个项目就是你的后悔药。
2. 编译与运行:从零构建可执行文件,覆盖 Windows/macOS/Linux 三大平台实测路径
2.1 项目结构与依赖解析:为什么它不报 “QAudioInput not found”
项目目录非常干净,共 7 个关键文件,无子目录嵌套:
recorder/ ├── main.cpp # QApplication 入口,创建 MainWindow ├── MainWindow.h/.cpp # 主窗口类,含 UI 布局(QPushButton + QLabel) ├── AudioRecorder.h/.cpp # 核心录音控制器:启停、采样率/通道配置、错误回调 ├── WavWriter.h/.cpp # WAV 文件生成器:写 RIFF 头、fmt 子块、data 子块,支持追加写 ├── AudioDeviceManager.h/.cpp # 设备枚举工具:列出所有可用输入设备及参数(采样率范围、通道数) └── CMakeLists.txt # 关键!显式链接 Qt5Multimedia 并启用音频输入模块提示:Qt5 默认构建可能禁用音频输入支持。若
QAudioInput::availableDevices()返回空列表,请确认你的 Qt 安装启用了multimedia和multimedia-audioinput模块(Linux 下需libasound-dev,macOS 需CoreAudio,Windows 用 WASAPI)。
2.2 CMakeLists.txt 关键配置:三行解决 90% 编译失败
这是本项目最易被忽略但最关键的文件。很多开发者复制代码后编译报undefined reference to QAudioInput,问题就出在这里:
# CMakeLists.txt cmake_minimum_required(VERSION 3.10) project(recorder) set(CMAKE_CXX_STANDARD 11) # 必须显式查找并链接 Multimedia 模块(Qt5.12+ 推荐写法) find_package(Qt5 REQUIRED COMPONENTS Core Widgets Multimedia) add_executable(recorder main.cpp MainWindow.h MainWindow.cpp AudioRecorder.h AudioRecorder.cpp WavWriter.h WavWriter.cpp AudioDeviceManager.h AudioDeviceManager.cpp ) # 必须链接 Qt5Multimedia(仅此一行不够,见下条) target_link_libraries(recorder Qt5::Core Qt5::Widgets Qt5::Multimedia) # 【血泪经验】关键补丁:强制启用音频输入后端(尤其 Linux/macOS) if(APPLE) target_compile_definitions(recorder PRIVATE QT_MULTIMEDIA_LIB) elseif(UNIX AND NOT APPLE) target_compile_definitions(recorder PRIVATE QT_MULTIMEDIA_LIB) endif()注意:
target_compile_definitions中的QT_MULTIMEDIA_LIB是 Qt 内部宏,用于触发QAudioInput的实际实现加载。没有它,即使链接了库,QAudioInput构造函数也会静默失败(返回 null)。这是 Qt 文档极少提及的玄学开关。
2.3 三平台运行命令与验证方式
| 平台 | 编译命令(终端进入 recorder/ 目录) | 运行后验证动作 |
|---|---|---|
| Windows | mkdir build && cd build && cmake .. -G "MinGW Makefiles" && mingw32-make | 双击recorder.exe→ 点“开始录音” → 说话 5 秒 → 点“停止” → 用 VLC 打开output.wav听是否清晰 |
| macOS | mkdir build && cd build && cmake .. -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" && make | 运行./recorder→ 点按钮 → 查看控制台输出Recording started on device: "MacBook Pro Microphone"→ 检查output.wav时长是否匹配 |
| Linux | mkdir build && cd build && cmake .. && make | 运行前确保有录音权限:sudo usermod -a -G audio $USER(需重启终端)→./recorder→ 用arecord -l对比设备名 |
提示:首次运行时,程序会自动调用
AudioDeviceManager::enumerateInputDevices()并打印所有可用设备。如果列表为空,请检查系统音频服务(如 PulseAudio 是否运行)或 Qt 构建选项。
3. 核心模块拆解:AudioRecorder 与 WavWriter 如何协作完成一次“按下即录”
3.1 AudioRecorder:状态机驱动的录音控制器(非阻塞式设计)
AudioRecorder类不继承QObject,而是通过信号槽与 UI 解耦。其核心是QAudioInput实例和一个QBuffer缓冲区,采用“推模式”(push mode)而非“拉模式”(pull mode),避免线程阻塞:
// AudioRecorder.cpp void AudioRecorder::startRecording(const QString &deviceName, int sampleRate, int channelCount) { // 1. 构建音频格式:固定 16-bit PCM,小端字节序 QAudioFormat format; format.setSampleRate(sampleRate); // e.g., 44100 format.setChannelCount(channelCount); // 1 or 2 format.setSampleSize(16); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); // 2. 创建 QAudioInput 实例(关键:必须传入有效设备) m_audioInput = new QAudioInput(deviceName, format, this); // 3. 绑定缓冲区:所有音频数据直接写入 m_buffer m_buffer.open(QIODevice::ReadWrite); m_audioInput->start(&m_buffer); // 启动采集,数据自动流入 buffer // 4. 启动定时器,每 100ms 切片写入 WAV 文件(防内存暴涨) m_timer.start(100, this); }逻辑说明:
QAudioInput::start(QIODevice*)是 Qt 音频采集的“魔法入口”。它启动底层音频流,并将原始 PCM 数据持续写入m_buffer。这里不使用QIODevice::read()主动读取,而是让 Qt 自动推送——这是低延迟的关键。m_timer的作用是分片写入:避免m_buffer无限增长(录音 1 小时可能达几百 MB),每 100ms 触发一次writeChunkToWav()。
3.2 WavWriter:手写 RIFF/WAV 头,拒绝黑盒编码器
WAV 文件本质是 RIFF 容器,必须包含RIFF头、fmt子块、data子块。WavWriter不调用任何编码库,纯 C++ 字节操作:
// WavWriter.cpp bool WavWriter::writeHeader(QFile &file, int sampleRate, int channels, int bitsPerSample) { QDataStream out(&file); out.setByteOrder(QDataStream::LittleEndian); // RIFF 头 (12 bytes) out.writeRawData("RIFF", 4); out << quint32(0); // Placeholder for total file size (filled later) out.writeRawData("WAVE", 4); // fmt 子块 (24 bytes) out.writeRawData("fmt ", 4); out << quint32(16); // fmt size = 16 for PCM out << quint16(1); // PCM format code out << quint16(channels); out << quint32(sampleRate); out << quint32(sampleRate * channels * bitsPerSample / 8); // byte rate out << quint16(channels * bitsPerSample / 8); // block align out << quint16(bitsPerSample); // data 子块头 (8 bytes) out.writeRawData("data", 4); out << quint32(0); // Placeholder for data size m_dataStartPos = file.pos(); // 记录 data 子块起始位置,用于后续填大小 return true; } void WavWriter::appendPcmData(const QByteArray &pcmData) { QFile file(m_filename); if (!file.open(QIODevice::ReadWrite)) return; // 定位到文件末尾,追加 PCM 数据 file.seek(file.size()); file.write(pcmData); // 更新 data 子块大小(从 data 头开始到文件末尾) quint32 dataSize = file.size() - m_dataStartPos - 4; // -4 for "data" tag file.seek(m_dataStartPos - 4); // 回退到 data size 字段 QDataStream out(&file); out << dataSize; // 更新总文件大小(RIFF size = file size - 8) file.seek(4); out << quint32(file.size() - 8); file.close(); }参数说明:
sampleRate: 采样率(Hz),常见值 44100、48000channels: 声道数(1=mono, 2=stereo)bitsPerSample: 位深度(16)m_dataStartPos: 在writeHeader()中记录data子块大小字段的位置,用于后续appendPcmData()动态更新——这是 WAV 文件可边录边写的底层保障。
3.3 AudioDeviceManager:跨平台设备枚举的兼容性处理
不同系统返回的设备名格式差异极大,AudioDeviceManager封装了适配逻辑:
// AudioDeviceManager.cpp QList<AudioDeviceInfo> AudioDeviceManager::enumerateInputDevices() { QList<AudioDeviceInfo> devices; const QList<QAudioDeviceInfo> qtDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); for (const QAudioDeviceInfo &dev : qtDevices) { AudioDeviceInfo info; info.name = dev.deviceName(); // 原始名,如 "hw:0,0" (Linux), "Built-in Microphone" (macOS) info.description = dev.description(); // 更友好的描述 // 【关键兼容处理】标准化设备标识符(供 UI 显示和选择) #ifdef Q_OS_LINUX if (info.name.startsWith("hw:")) { info.id = "ALSA:" + info.name.mid(3); // 转为 "ALSA:0,0" } else if (info.name.contains("pulse")) { info.id = "PulseAudio"; } #elif defined(Q_OS_MACOS) info.id = "CoreAudio:" + info.name; #elif defined(Q_OS_WIN) info.id = "WASAPI:" + info.name; #endif // 提取支持的采样率范围(避免用户选 192kHz 导致失败) info.supportedSampleRates = dev.supportedSampleRates(); devices.append(info); } return devices; }逻辑说明:
QAudioDeviceInfo::supportedSampleRates()返回QList<int>,项目 UI 中“采样率”下拉框即从此列表生成。若用户强行选择不支持的值(如设备只支持 44.1k 却选 48k),QAudioInput构造会失败并返回 null——AudioRecorder内部有qWarning()日志捕获该错误。
4. 常见问题排查:那些让你怀疑 Qt 音频模块已死的翻车现场
4.1 现象:点击“开始录音”无反应,控制台无日志,QAudioInput构造返回 null
原因:Qt 构建时未启用音频输入后端,或当前平台缺少底层音频服务(如 Linux 无 PulseAudio/ALSA,macOS 未授权麦克风)。
解决:
- 检查
qmake -query QT_INSTALL_PLUGINS输出中是否存在audio/目录; - Linux 下运行
pactl list sources short确认 PulseAudio 正常; - macOS 在“系统设置 > 隐私与安全性 > 麦克风”中为
recorder添加权限; - 终极验证:在
main.cpp中添加qDebug() << QAudioDeviceInfo::availableDevices(QAudio::AudioInput);,若输出为空数组,则问题在 Qt 或系统层。
4.2 现象:录音文件output.wav能打开但无声,或播放时爆音/失真
原因:WAV 头中block align或byte rate计算错误,导致播放器解析 PCM 数据偏移。
解决:
- 检查
WavWriter::writeHeader()中block align = channels * bitsPerSample / 8是否为整数(16bit stereo = 4 bytes); - 确认
QAudioFormat设置的sampleSize(16)、channelCount(1/2)、sampleRate与WavWriter写入头时传入的参数完全一致; - 用
xxd output.wav | head -20查看前 100 字节,对照 WAV spec 验证fmt子块字段。
4.3 现象:录音过程中 UI 卡死,或停止后文件损坏(data size 为 0)
原因:QAudioInput数据写入QBuffer与WavWriter::appendPcmData()文件写入发生在同一主线程,大块数据导致事件循环阻塞。
解决:
- 必须启用定时器分片:
m_timer.start(100, this)不可删除; appendPcmData()中file.open()改为QFile::Append模式,避免重写整个文件;- 若仍卡顿,将
appendPcmData()移至独立QThread(需moveToThread(),注意QFile非线程安全,应改用QSaveFile)。
4.4 现象:Windows 上编译报错LNK2019: unresolved external symbol __imp__waveOutOpen@24
原因:Qt5.15+ 默认使用 WASAPI 后端,但链接器未自动包含winmm.lib(旧 API 依赖)。
解决:在CMakeLists.txt中添加:
if(WIN32) target_link_libraries(recorder winmm) endif()4.5 现象:macOS 上录音音量极小,或只有左声道有声
原因:macOS 默认音频设备可能为“Multi-Output Device”,且 Qt 获取的QAudioDeviceInfo未正确映射声道布局。
解决:
- 在
AudioDeviceManager::enumerateInputDevices()中过滤掉含"Multi-Output"的设备名; - 强制使用
QAudioDeviceInfo::preferredFormat()替代手动构造QAudioFormat; - UI 中默认采样率设为 44100(macOS 对 48000 支持不稳定)。
5. 二次开发实战:从“能录音”到“可集成”的 3 个改造技巧
5.1 技巧一:替换为 QSaveFile 实现原子写入,杜绝录音中断导致文件损坏
原项目用QFile直接追加,若录音中程序崩溃,output.wav可能因data size未更新而无法播放。QSaveFile提供原子提交:
// 修改 WavWriter.h #include <QSaveFile> class WavWriter { private: QSaveFile m_saveFile; // 替换 QFile QByteArray m_pendingData; // 缓存待写入的 PCM 数据 }; // 修改 WavWriter.cpp::appendPcmData() void WavWriter::appendPcmData(const QByteArray &pcmData) { m_pendingData.append(pcmData); // 先缓存 // 每 500ms 或缓存超 64KB 时批量提交 if (m_pendingData.size() > 64 * 1024 || m_flushTimer.elapsed() > 500) { if (!m_saveFile.isOpen()) { m_saveFile.setFileName(m_filename); m_saveFile.open(QIODevice::WriteOnly); } m_saveFile.write(m_pendingData); m_pendingData.clear(); m_flushTimer.restart(); // 提交:将临时文件重命名为目标文件(原子操作) if (m_saveFile.commit()) { // 更新 data size 和 RIFF size(需重新读写文件头) updateWavHeader(); } } }验证方法:录音中强制
kill -9进程,再启动程序检查output.wav是否仍可播放。QSaveFile::commit()成功即表示文件完整。
5.2 技巧二:暴露音频数据流接口,支持实时 FFT 分析或 VU 表
AudioRecorder当前只写文件,但QBuffer中的数据可被实时读取。添加信号void audioDataReady(const QByteArray&):
// AudioRecorder.h signals: void audioDataReady(const QByteArray &pcmData); // 新增信号 // AudioRecorder.cpp::timerEvent() void AudioRecorder::timerEvent(QTimerEvent *e) { if (e->timerId() == m_timer.timerId()) { QByteArray chunk = m_buffer.readAll(); // 从 buffer 读取新数据 if (!chunk.isEmpty()) { emit audioDataReady(chunk); // 发射给 UI 或分析模块 // ... 原 writeChunkToWav() 逻辑保持不变 } } }集成示例(在 MainWindow.cpp 中):
connect(m_recorder, &AudioRecorder::audioDataReady, this, [=](const QByteArray &data) { // 将 16-bit PCM 转为 float 数组用于 FFT const short *samples = reinterpret_cast<const short*>(data.constData()); QVector<float> floats(data.size()/2); for (int i = 0; i < floats.size(); ++i) { floats[i] = samples[i] / 32768.0f; // 归一化到 [-1.0, 1.0] } runFFT(floats); // 调用你的 FFT 函数 });
5.3 技巧三:支持多文件轮转(按时间/大小切片),适配长时间监控场景
工业场景常需“每 5 分钟一个文件”。修改AudioRecorder的stopRecording()逻辑:
// AudioRecorder.h private: int m_fileIndex = 0; qint64 m_startTime = 0; static const qint64 MAX_FILE_DURATION_MS = 5 * 60 * 1000; // 5分钟 // AudioRecorder.cpp::startRecording() void AudioRecorder::startRecording(...) { m_startTime = QDateTime::currentMSecsSinceEpoch(); generateFilename(); // ... 其余逻辑 } void AudioRecorder::generateFilename() { QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss"); m_filename = QString("rec_%1_%2.wav").arg(timestamp).arg(m_fileIndex++); } // AudioRecorder.cpp::timerEvent() 中添加轮转判断 if (QDateTime::currentMSecsSinceEpoch() - m_startTime > MAX_FILE_DURATION_MS) { stopRecording(); // 关闭当前文件 startRecording(...); // 开启新文件(自动调用 generateFilename) }参数表:轮转策略可配置项
参数 类型 默认值 说明 MAX_FILE_DURATION_MSqint64300000(5min)单文件最大时长,超时自动切片 MAX_FILE_SIZE_BYTESqint640(禁用)单文件最大字节数,设为 0 则忽略 m_fileIndexint0文件序号,保证命名不冲突
从那以后我每次做 Qt 音频项目,都会先跑通这个录音机,再往里加功能——它像一把瑞士军刀,不花哨,但每个齿都咬得准。遇到设备枚举失败,我就qDebug()打印availableDevices();遇到文件无声,我就xxd看 WAV 头;想加新功能,我就先在audioDataReady信号里接个qDebug()确认数据流畅通。它教会我的不是 Qt 多媒体 API 的全部,而是如何把一个“应该能工作”的模块,变成“一定在工作”的模块。希望帮到你。
本文还有配套的精品资源,点击获取