作为音视频开发的初学者,本文分享基于 Qt+FFmpeg 实现 Windows 平台麦克风音频采集,并将采集到的音频数据保存为 PCM 原始文件的完整代码和解析,适合刚入门音视频开发的同学参考。
一、开发环境准备
1. 基础环境
- 操作系统:Windows 10/11
- Qt 版本:Qt 5.14
- FFmpeg 版本:4.3.2
2. FFmpeg 库配置
将编译好的 FFmpeg 库(包含include、lib、bin目录)放在 Qt 项目的上级目录(或自定义路径),并在.pro文件中配置如下(文末附完整.pro 文件)
3.查看录音设备
ffmepg -f dshow -list_devices true -i ''
ffplay -f s16le -ar 44100 -ac 2 -i D:/out.pcm
二、核心功能说明
通过 Qt 的QThread封装音频采集逻辑,避免阻塞主线程;利用 FFmpeg 的dshow设备(Windows DirectShow)读取麦克风数据,最终将原始 PCM 数据写入文件。
三、完整代码实现
1. 项目结构
AudioCapture/
├── audiothread.h // 音频采集线程头文件
├── audiothread.cpp // 音频采集线程实现
├── mainwindow.h // 主窗口头文件
├── mainwindow.cpp // 主窗口实现
├── main.cpp // 程序入口
├── mainwindow.ui // 主窗口UI(仅一个按钮)
└── AudioCapture.pro // 项目配置文件
└── ffmpeg/ // 上级目录的FFmpeg库(include/lib/bin)
2. 项目配置文件(AudioCapture.pro)
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# 开启Qt废弃API警告
DEFINES += QT_DEPRECATED_WARNINGS
# 源文件
SOURCES += \
audiothread.cpp \
main.cpp \
mainwindow.cpp
# 头文件
HEADERS += \
audiothread.h \
mainwindow.h
# UI文件
FORMS += \
mainwindow.ui
# 部署规则(默认)
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
# Windows平台FFmpeg配置
win32
{
# FFmpeg根目录(上级目录),可根据实际路径修改
FFMPEG_HOME = ..
# 引入FFmpeg头文件
INCLUDEPATH += $${FFMPEG_HOME}/include
# 链接FFmpeg库
LIBS += -L $${FFMPEG_HOME}/lib \
-lavdevice \
-lavformat \
-lavutil
# 可选:自动拷贝FFmpeg的dll到输出目录(避免手动拷贝)
# COPY_FILES += $${FFMPEG_HOME}/bin/avdevice-58.dll \
# $${FFMPEG_HOME}/bin/avformat-58.dll \
# $${FFMPEG_HOME}/bin/avutil-56.dll
}
3. 音频采集线程(AudioThread)
audiothread.
cpp
#ifndef AUDIOTHREAD_H
#define AUDIOTHREAD_H
#include <QThread>
class AudioThread : public QThread
{
Q_OBJECT
private:
void run();
public:
explicit AudioThread(QObject *parent = nullptr);
~AudioThread();
signals:
};
#endif // AUDIOTHREAD_H
audiothread.cpp
cpp
#include "audiothread.h"
#include "qdebug.h"
#include <qfile.h>
// 引入FFmpeg的C接口(extern "C"避免C++名称修饰问题)
extern "C"
{
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
}
// Windows平台配置(按需修改设备名称和输出路径)
#ifdef Q_OS_WIN
// dshow设备格式
#define FMT_NAME "dshow"
// 麦克风设备名(需替换为自己的设备名)
#define DEVICE_NAME "audio=麦克风阵列 (英特尔® 智音技术)"
// PCM输出文件路径
#define FILENAME "D:/out.pcm"
#endif
AudioThread::AudioThread(QObject *parent) : QThread(parent)
{
// 线程结束后自动回收内存
connect(this,&AudioThread::finished,this,&AudioThread::deleteLater);
}
AudioThread::~AudioThread()
{
// 安全终止线程
requestInterruption();
quit();
wait();
qDebug() << this << "音频线程析构完成";
}
void AudioThread::run()
{
// 1. 获取DShow输入格式对象
AVInputFormat* fmt = av_find_input_format(FMT_NAME);
if(!fmt)
{
qDebug() << "获取输入格式对象失败:" << FMT_NAME;
return;
}
// 2. 创建并打开音频设备上下文
AVFormatContext* ctx = nullptr;
int ret = avformat_open_input(&ctx, DEVICE_NAME, fmt, nullptr);
if(ret < 0)
{
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
qDebug() << "打开设备失败:" << errbuf;
return;
}
// 3. 打开PCM文件用于写入
QFile file(FILENAME);
if(!file.open(QIODevice::WriteOnly))
{
qDebug() << "文件打开失败:" << FILENAME;
avformat_close_input(&ctx); // 释放设备资源
return;
}
// 4. 循环采集音频数据(★初学易踩坑:条件需为!isInterruptionRequested())
AVPacket pkt;
while(!isInterruptionRequested() && av_read_frame(ctx, &pkt) == 0)
{
// 将采集到的音频数据写入文件
file.write((const char*)pkt.data, pkt.size);
// 释放数据包引用(避免内存泄漏)
av_packet_unref(&pkt);
}
// 5. 释放所有资源
file.close(); // 关闭文件
avformat_close_input(&ctx); // 关闭音频设备
qDebug() << "音频采集结束,PCM文件已保存至:" << FILENAME;
}
4. 主窗口(MainWindow)
mainwindow.h
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "audiothread.h"
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_audioBtn_clicked();
private:
Ui::MainWindow *ui;
AudioThread *_audioThread;
};
#endif // MAINWINDOW_H
mainwindow.cpp
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "qdebug.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
_audioThread = nullptr; // 初始化线程指针
}
MainWindow::~MainWindow()
{
delete ui;
}
// 录音按钮点击事件处理
void MainWindow::on_audioBtn_clicked()
{
if(!_audioThread) // 未开始采集:启动线程
{
_audioThread = new AudioThread(this);
_audioThread->start();
ui->audioBtn->setText("结束录音");
qDebug() << "开始音频采集...";
}
else // 已开始采集:终止线程
{
_audioThread->requestInterruption();
_audioThread = nullptr;
ui->audioBtn->setText("开始录音");
qDebug() << "停止音频采集...";
}
}
5. 程序入口(main.cpp)
cpp
#include "mainwindow.h"
#include <QApplication>
// 引入FFmpeg设备注册接口
extern "C"{
#include <libavdevice/avdevice.h>
}
int main(int argc, char *argv[])
{
// 注册FFmpeg所有输入输出设备(必须!否则无法找到dshow设备)
avdevice_register_all();
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
四、核心代码解析
1. FFmpeg 核心流程
| 步骤 | 核心函数 | 作用 |
|---|---|---|
| 注册设备 | avdevice_register_all() |
注册 FFmpeg 所有设备,是使用 dshow 的前提 |
| 获取输入格式 | av_find_input_format("dshow") |
拿到 Windows DirectShow 输入格式对象 |
| 打开设备 | avformat_open_input() |
打开指定麦克风设备,返回设备上下文 |
| 采集数据 | av_read_frame() |
循环读取音频数据包(AVPacket) |
| 释放资源 | avformat_close_input()/av_packet_unref() |
关闭设备、释放数据包,避免内存泄漏 |
五、运行说明
-
配置 FFmpeg 路径 :将编译好的 FFmpeg 库(include/lib/bin)放在项目上级目录,或修改
.pro文件中的FFMPEG_HOME路径; -
修改设备名称 :通过上述 FFmpeg 命令查看并替换
DEVICE_NAME; -
编译运行 :
- 点击 "开始录音",麦克风开始采集音频;
- 点击 "结束录音",停止采集并生成
D:/out.pcm文件;
-
验证 PCM 文件 :PCM 是原始音频数据,无头部信息,需用 FFmpeg 转为 WAV 播放:
ffmpeg -f s16le -ar 44100 -ac 2 -i D:/out.pcm output.wav(参数说明:
s16le=16 位深度、ar 44100=44.1kHz 采样率、ac 2= 双声道,需匹配实际采集参数)
六、注意事项
- 编译器匹配:确保 FFmpeg 库的编译环境(MSVC/Mingw)与 Qt 编译器一致,否则会链接失败;
- 权限问题:Windows 需授予程序麦克风访问权限;
- DLL 拷贝 :运行程序时需将 FFmpeg 的
avdevice-58.dll、avformat-58.dll、avutil-56.dll拷贝到程序输出目录(或开启.pro 文件中的COPY_FILES自动拷贝); - 线程安全 :Qt 线程通过
requestInterruption()终止,避免直接调用terminate()(不安全)。
总结
本文实现了最基础的 Windows 音频采集功能,核心是理解 FFmpeg 的设备操作流程和 Qt 线程的安全使用。初学者可先掌握核心流程,再逐步学习音频编码、封装等进阶知识,为后续音视频开发打下基础。