FFmpeg - 音频采集程序
下面是一个完整的音频采集程序,使用 Qt 作为界面框架,FFmpeg 4.0 进行音频编码和录制。
AudioCapture.pro
QT += core gui multimedia multimediawidgets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
win32 {
LIBS += -L$$PWD/lib/ffmpeg-4.2.0/lib \
-lavcodec \
-lavdevice \
-lavfilter \
-lavformat \
-lavutil \
-lswresample \
-lswscale
INCLUDEPATH += \
lib/ffmpeg-4.2.0/include
}
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
audiorecorder.cpp \
main.cpp \
mainwindow.cpp
HEADERS += \
audiorecorder.h \
mainwindow.h
FORMS += \
mainwindow.ui
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QAudioInput>
#include <QAudioDeviceInfo>
#include <QVBoxLayout>
#include <QPushButton>
#include <QComboBox>
#include <QMessageBox>
#include <QHBoxLayout>
#include <QFileDialog>
#include <QDateTime>
#include <QLabel>
#include <QTimer>
#include <QIODevice>
#include "audiorecorder.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
QString getFileExtension(const QString &codecName);
private slots:
void startRecording();
void stopRecording();
void onAudioDataReady();
void onRecordingError(const QString &error);
void onRecordingStatusChanged(const QString &status);
void onAudioLevelChanged(int level);
void updateRecordTime();
private:
Ui::MainWindow *ui;
void setupUI();
void populateAudioDevices();
void populateCodecs();
void populateSampleRates();
QString getOutputFileName();
QAudioInput *audioInput;
QIODevice *audioDevice;
QTimer *recordTimer;
qint64 recordDuration;
// FFmpeg 录制器
AudioRecorder *recorder;
bool isRecording;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMetaType>
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
#include <QHBoxLayout>
#include <QFileDialog>
#include <QDateTime>
#include <QAudioFormat>
#include <QProgressBar>
// MainWindow 实现
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow), audioInput(nullptr),
audioDevice(nullptr), recordDuration(0), recorder(nullptr), isRecording(false)
{
qDebug() << "MainWindow constructor";
ui->setupUi(this);
setupUI();
populateAudioDevices();
populateCodecs();
populateSampleRates();
// 初始化音频录制器
recorder = new AudioRecorder(this);
connect(recorder, &AudioRecorder::errorOccurred, this, &MainWindow::onRecordingError);
connect(recorder, &AudioRecorder::recordingStatusChanged, this, &MainWindow::onRecordingStatusChanged);
connect(recorder, &AudioRecorder::audioLevelChanged, this, &MainWindow::onAudioLevelChanged);
qDebug() << "MainWindow initialized";
}
MainWindow::~MainWindow()
{
qDebug() << "MainWindow destructor";
stopRecording();
if (recorder) {
recorder->deleteLater();
recorder = nullptr;
}
delete ui;
}
void MainWindow::setupUI()
{
qDebug() << "Setting up UI";
// 连接信号槽
connect(ui->startButton, &QPushButton::clicked, this, &MainWindow::startRecording);
connect(ui->stopButton, &QPushButton::clicked, this, &MainWindow::stopRecording);
// 初始化按钮状态
ui->stopButton->setEnabled(false);
// 创建录制计时器
recordTimer = new QTimer(this);
connect(recordTimer, &QTimer::timeout, this, &MainWindow::updateRecordTime);
setWindowTitle("音频采集录制程序");
resize(600, 400);
}
void MainWindow::populateAudioDevices()
{
qDebug() << "Populating audio devices";
ui->deviceComboBox->clear();
QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
if (devices.isEmpty()) {
ui->deviceComboBox->addItem("未找到音频输入设备");
ui->startButton->setEnabled(false);
qDebug() << "No audio input devices found";
return;
}
for (const QAudioDeviceInfo &device : devices) {
ui->deviceComboBox->addItem(device.deviceName(), QVariant::fromValue(device));
qDebug() << "Found audio device:" << device.deviceName();
}
qDebug() << "Audio devices populated, count:" << devices.count();
}
void MainWindow::populateCodecs()
{
ui->codecComboBox->clear();
// 添加支持的音频编码器
ui->codecComboBox->addItem("AAC", "aac");
ui->codecComboBox->addItem("MP3", "mp3");
ui->codecComboBox->addItem("MP3 (LAME)", "libmp3lame");
// 默认选择 AAC
ui->codecComboBox->setCurrentIndex(0);
}
void MainWindow::populateSampleRates()
{
ui->sampleRateComboBox->clear();
// 添加常见的采样率
ui->sampleRateComboBox->addItem("8000 Hz", 8000);
ui->sampleRateComboBox->addItem("11025 Hz", 11025);
ui->sampleRateComboBox->addItem("16000 Hz", 16000);
ui->sampleRateComboBox->addItem("22050 Hz", 22050);
ui->sampleRateComboBox->addItem("44100 Hz", 44100);
ui->sampleRateComboBox->addItem("48000 Hz", 48000);
// 默认选择 44100 Hz
ui->sampleRateComboBox->setCurrentIndex(4);
}
void MainWindow::startRecording()
{
qDebug() << "=== Starting Audio Recording ===";
try {
if (ui->deviceComboBox->currentData().isNull()) {
QMessageBox::warning(this, "警告", "没有可用的音频输入设备");
return;
}
if (isRecording) {
QMessageBox::information(this, "提示", "已经在录制中");
return;
}
QString fileName = getOutputFileName();
if (fileName.isEmpty()) {
return;
}
// 获取选中的音频设备
QAudioDeviceInfo device = ui->deviceComboBox->currentData().value<QAudioDeviceInfo>();
qDebug() << "Selected audio device:" << device.deviceName();
// 配置音频格式
QAudioFormat format;
format.setSampleRate(ui->sampleRateComboBox->currentData().toInt());
format.setChannelCount(2); // 立体声
format.setSampleSize(16); // 16位
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
qDebug() << "Audio format configured - SampleRate:" << format.sampleRate()
<< "Channels:" << format.channelCount() << "SampleSize:" << format.sampleSize();
// 检查格式是否支持
if (!device.isFormatSupported(format)) {
QMessageBox::warning(this, "警告", "音频格式不被设备支持,使用默认格式");
format = device.preferredFormat();
qDebug() << "Using preferred format - SampleRate:" << format.sampleRate()
<< "Channels:" << format.channelCount() << "SampleSize:" << format.sampleSize();
}
// 创建音频输入
audioInput = new QAudioInput(device, format, this);
// 开始录制音频
audioDevice = audioInput->start();
if (!audioDevice) {
QMessageBox::critical(this, "错误", "无法启动音频输入");
delete audioInput;
audioInput = nullptr;
return;
}
qDebug() << "Audio input started successfully";
// 连接音频数据可用信号
connect(audioDevice, &QIODevice::readyRead, this, &MainWindow::onAudioDataReady);
// 获取编码器参数
QString codecName = ui->codecComboBox->currentData().toString();
int sampleRate = format.sampleRate();
int channels = format.channelCount();
// 初始化录制器
if (!recorder->initialize(fileName, sampleRate, channels, codecName)) {
QMessageBox::critical(this, "错误", "初始化音频录制器失败");
stopRecording();
return;
}
// 开始录制线程
recorder->start();
isRecording = true;
ui->startButton->setEnabled(false);
ui->stopButton->setEnabled(true);
recordDuration = 0;
recordTimer->start(1000);
ui->statusLabel->setText("正在录制音频...");
qDebug() << "=== Audio Recording Started Successfully ===";
} catch (const std::exception& e) {
qDebug() << "Exception in startRecording:" << e.what();
QMessageBox::critical(this, "错误", QString("启动音频录制失败: %1").arg(e.what()));
stopRecording();
}
}
void MainWindow::stopRecording()
{
qDebug() << "=== Stopping Audio Recording ===";
if (isRecording) {
// 停止音频输入
if (audioInput) {
qDebug() << "Stopping audio input";
audioInput->stop();
if (audioDevice) {
disconnect(audioDevice, &QIODevice::readyRead, this, &MainWindow::onAudioDataReady);
}
delete audioInput;
audioInput = nullptr;
audioDevice = nullptr;
qDebug() << "Audio input stopped";
}
// 停止录制器
if (recorder) {
qDebug() << "Stopping recorder";
recorder->stopRecording();
if (recorder->isRunning()) {
recorder->wait(3000);
}
qDebug() << "Recorder stopped";
}
recordTimer->stop();
ui->startButton->setEnabled(true);
ui->stopButton->setEnabled(false);
isRecording = false;
qDebug() << "=== Audio Recording Stopped ===";
}
}
void MainWindow::onAudioDataReady()
{
if (isRecording && audioDevice && recorder) {
// 读取音频数据并发送到录制器
QByteArray audioData = audioDevice->readAll();
if (!audioData.isEmpty()) {
recorder->addAudioData(audioData);
}
}
}
void MainWindow::onRecordingError(const QString &error)
{
qDebug() << "Recording error:" << error;
QMessageBox::critical(this, "录制错误", error);
stopRecording();
}
void MainWindow::onRecordingStatusChanged(const QString &status)
{
ui->statusLabel->setText(status);
qDebug() << "Recording status:" << status;
}
void MainWindow::onAudioLevelChanged(int level)
{
// 更新音频电平显示
ui->levelProgressBar->setValue(level);
// 可选:在状态栏显示当前电平
if (level > 80) {
ui->levelLabel->setText(QString("音频电平: %1% (过高)").arg(level));
} else if (level > 50) {
ui->levelLabel->setText(QString("音频电平: %1% (良好)").arg(level));
} else {
ui->levelLabel->setText(QString("音频电平: %1% (较低)").arg(level));
}
}
void MainWindow::updateRecordTime()
{
recordDuration++;
ui->statusLabel->setText(QString("正在录制音频... 时长: %1秒").arg(recordDuration));
}
QString MainWindow::getOutputFileName()
{
QString musicDir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
if (musicDir.isEmpty()) {
musicDir = QDir::currentPath();
}
QDir dir(musicDir);
if (!dir.exists()) {
dir.mkpath(".");
}
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss");
QString codecName = ui->codecComboBox->currentData().toString();
QString defaultName = QString("%1/audio_%2.%3").arg(musicDir).arg(timestamp).arg(getFileExtension(codecName));
QString filter;
if (codecName == "aac") {
filter = "AAC 文件 (*.aac *.m4a)";
} else if (codecName == "mp3" || codecName == "libmp3lame") {
filter = "MP3 文件 (*.mp3)";
} else {
filter = "音频文件 (*.*)";
}
QString fileName = QFileDialog::getSaveFileName(this, "保存音频", defaultName, filter);
qDebug() << "Output file selected:" << fileName;
return fileName;
}
QString MainWindow::getFileExtension(const QString &codecName)
{
if (codecName == "aac") {
return "m4a";
} else if (codecName == "mp3" || codecName == "libmp3lame") {
return "mp3";
} else {
return "wav";
}
}
audiorecorder.h
#ifndef AUDIORECORDER_H
#define AUDIORECORDER_H
#include <QThread>
#include <QMutex>
// FFmpeg 头文件
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
}
// 音频录制线程类
class AudioRecorder : public QThread
{
Q_OBJECT
public:
explicit AudioRecorder(QObject *parent = nullptr);
~AudioRecorder();
bool initialize(const QString &outputFile, int sampleRate, int channels, const QString &codecName);
void stopRecording();
void addAudioData(const QByteArray &data);
signals:
void errorOccurred(const QString &error);
void recordingStatusChanged(const QString &status);
void audioLevelChanged(int level); // 音频电平信号
protected:
void run() override;
private:
bool setupOutput(const QString &outputFile);
bool encodeAudio(const uint8_t *data, int size);
void cleanup();
// FFmpeg 相关变量
AVFormatContext *outputFormatContext;
AVCodecContext *audioCodecContext;
AVStream *audioStream;
SwrContext *swrContext;
AVFrame *frame;
AVPacket *packet;
// 录制参数
QString outputFilename;
int sampleRate;
int channels;
QString codecName;
// 状态控制
QMutex mutex;
bool recording;
bool initialized;
// 音频数据队列
QList<QByteArray> audioQueue;
QMutex queueMutex;
// 重采样相关
int64_t pts;
};
#endif // AUDIORECORDER_H
audiorecorder.cpp
#include "audiorecorder.h"
#include <QDebug>
// FFmpeg 错误处理辅助函数
QString ffmpegErrorString(int errnum)
{
char errbuf[AV_ERROR_MAX_STRING_SIZE];
av_strerror(errnum, errbuf, sizeof(errbuf));
return QString(errbuf);
}
// 在 AudioRecorder 类中添加音频数据添加方法
void AudioRecorder::addAudioData(const QByteArray &data)
{
QMutexLocker locker(&queueMutex);
if (audioQueue.size() < 50) { // 限制队列大小
audioQueue.append(data);
}
}
// AudioRecorder 实现
AudioRecorder::AudioRecorder(QObject *parent)
: QThread(parent), outputFormatContext(nullptr),
audioCodecContext(nullptr), audioStream(nullptr), swrContext(nullptr),
frame(nullptr), packet(nullptr), sampleRate(44100), channels(2),
recording(false), initialized(false), pts(0)
{
qDebug() << "AudioRecorder constructor";
// 注册所有 FFmpeg 组件
avdevice_register_all();
avformat_network_init();
}
AudioRecorder::~AudioRecorder()
{
qDebug() << "AudioRecorder destructor";
stopRecording();
if (isRunning()) {
wait(3000);
}
cleanup();
}
bool AudioRecorder::initialize(const QString &outputFile, int sampleRate,
int channels, const QString &codecName)
{
qDebug() << "Initializing AudioRecorder with file:" << outputFile
<< "sampleRate:" << sampleRate << "channels:" << channels
<< "codec:" << codecName;
this->sampleRate = sampleRate;
this->channels = channels;
this->codecName = codecName;
outputFilename = outputFile;
if (!setupOutput(outputFile)) {
emit errorOccurred("无法初始化输出文件");
return false;
}
initialized = true;
return true;
}
bool AudioRecorder::setupOutput(const QString &outputFile)
{
qDebug() << "Setting up output for:" << outputFile;
int ret;
// 创建输出格式上下文
ret = avformat_alloc_output_context2(&outputFormatContext, nullptr, nullptr, outputFile.toUtf8().constData());
if (ret < 0) {
QString error = QString("无法创建输出上下文: %1").arg(ffmpegErrorString(ret));
qDebug() << error;
emit errorOccurred(error);
return false;
}
// 查找音频编码器
AVCodec *audioCodec = nullptr;
if (codecName == "aac") {
audioCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
} else if (codecName == "mp3") {
audioCodec = avcodec_find_encoder(AV_CODEC_ID_MP3);
} else if (codecName == "libmp3lame") {
audioCodec = avcodec_find_encoder_by_name("libmp3lame");
} else {
// 默认使用 AAC
audioCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
}
if (!audioCodec) {
QString error = QString("找不到音频编码器: %1").arg(codecName);
qDebug() << error;
emit errorOccurred(error);
return false;
}
qDebug() << "Found codec:" << audioCodec->name;
// 创建音频流
audioStream = avformat_new_stream(outputFormatContext, audioCodec);
if (!audioStream) {
QString error = "无法创建音频流";
qDebug() << error;
emit errorOccurred(error);
return false;
}
// 配置编码器上下文
audioCodecContext = avcodec_alloc_context3(audioCodec);
if (!audioCodecContext) {
QString error = "无法分配编码器上下文";
qDebug() << error;
emit errorOccurred(error);
return false;
}
audioCodecContext->codec_id = audioCodec->id;
audioCodecContext->codec_type = AVMEDIA_TYPE_AUDIO;
audioCodecContext->sample_fmt = audioCodec->sample_fmts ? audioCodec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
audioCodecContext->sample_rate = sampleRate;
audioCodecContext->channels = channels;
audioCodecContext->channel_layout = av_get_default_channel_layout(channels);
audioCodecContext->bit_rate = 128000; // 128kbps
// 设置编码器预设
if (outputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) {
audioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
// 打开编码器
ret = avcodec_open2(audioCodecContext, audioCodec, nullptr);
if (ret < 0) {
QString error = QString("无法打开音频编码器: %1").arg(ffmpegErrorString(ret));
qDebug() << error;
emit errorOccurred(error);
return false;
}
// 复制编码器参数到流
ret = avcodec_parameters_from_context(audioStream->codecpar, audioCodecContext);
if (ret < 0) {
QString error = QString("无法复制编码器参数: %1").arg(ffmpegErrorString(ret));
qDebug() << error;
emit errorOccurred(error);
return false;
}
// 打开输出文件
if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&outputFormatContext->pb, outputFile.toUtf8().constData(), AVIO_FLAG_WRITE);
if (ret < 0) {
QString error = QString("无法打开输出文件: %1").arg(ffmpegErrorString(ret));
qDebug() << error;
emit errorOccurred(error);
return false;
}
}
// 写入文件头
ret = avformat_write_header(outputFormatContext, nullptr);
if (ret < 0) {
QString error = QString("无法写入文件头: %1").arg(ffmpegErrorString(ret));
qDebug() << error;
emit errorOccurred(error);
return false;
}
// 分配帧和包
frame = av_frame_alloc();
packet = av_packet_alloc();
if (!frame || !packet) {
QString error = "无法分配帧或包";
qDebug() << error;
emit errorOccurred(error);
return false;
}
frame->format = audioCodecContext->sample_fmt;
frame->channel_layout = audioCodecContext->channel_layout;
frame->sample_rate = audioCodecContext->sample_rate;
frame->nb_samples = audioCodecContext->frame_size;
if (frame->nb_samples == 0) {
frame->nb_samples = 1024; // 默认帧大小
}
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
QString error = QString("无法分配帧缓冲区: %1").arg(ffmpegErrorString(ret));
qDebug() << error;
emit errorOccurred(error);
return false;
}
// 创建音频重采样上下文
// 从 Qt 的格式 (S16, 交错) 转换为编码器格式
swrContext = swr_alloc_set_opts(nullptr,
audioCodecContext->channel_layout,
audioCodecContext->sample_fmt,
audioCodecContext->sample_rate,
av_get_default_channel_layout(channels),
AV_SAMPLE_FMT_S16,
sampleRate,
0, nullptr);
if (!swrContext) {
QString error = "无法创建音频重采样上下文";
qDebug() << error;
emit errorOccurred(error);
return false;
}
ret = swr_init(swrContext);
if (ret < 0) {
QString error = QString("无法初始化音频重采样: %1").arg(ffmpegErrorString(ret));
qDebug() << error;
emit errorOccurred(error);
return false;
}
qDebug() << "Output setup completed successfully";
return true;
}
void AudioRecorder::stopRecording()
{
qDebug() << "AudioRecorder::stopRecording called";
QMutexLocker locker(&mutex);
recording = false;
}
void AudioRecorder::run()
{
qDebug() << "AudioRecorder thread started";
if (!initialized) {
QString error = "录制器未初始化";
qDebug() << error;
emit errorOccurred(error);
return;
}
recording = true;
pts = 0;
emit recordingStatusChanged("开始录制音频");
qDebug() << "Audio recording started";
while (recording) {
// 从队列获取音频数据
QByteArray audioData;
{
QMutexLocker locker(&queueMutex);
if (!audioQueue.isEmpty()) {
audioData = audioQueue.takeFirst();
}
}
if (!audioData.isEmpty()) {
if (!encodeAudio(reinterpret_cast<const uint8_t*>(audioData.constData()), audioData.size())) {
QString error = "编码音频数据失败";
qDebug() << error;
emit errorOccurred(error);
break;
}
// 计算音频电平(简化版本)
int16_t maxLevel = 0;
const int16_t* samples = reinterpret_cast<const int16_t*>(audioData.constData());
int sampleCount = audioData.size() / sizeof(int16_t);
for (int i = 0; i < sampleCount; ++i) {
int16_t absValue = qAbs(samples[i]);
if (absValue > maxLevel) {
maxLevel = absValue;
}
}
// 转换为 0-100 的电平值
int level = qMin(100, static_cast<int>((maxLevel * 100) / 32767));
emit audioLevelChanged(level);
} else {
// 队列为空,短暂休眠
msleep(10);
}
}
qDebug() << "Audio recording loop ended, flushing encoder";
// 刷新编码器
encodeAudio(nullptr, 0);
// 写入文件尾
if (outputFormatContext) {
av_write_trailer(outputFormatContext);
}
emit recordingStatusChanged("音频录制完成");
qDebug() << "Audio recording completed";
}
bool AudioRecorder::encodeAudio(const uint8_t *data, int size)
{
int ret;
if (data && size > 0) {
// 准备重采样
const uint8_t *in_data[1] = { data };
int in_samples = size / (channels * sizeof(int16_t));
// 重采样音频数据
ret = swr_convert(swrContext, frame->data, frame->nb_samples,
in_data, in_samples);
if (ret < 0) {
qDebug() << "音频重采样失败:" << ffmpegErrorString(ret);
return false;
}
frame->pts = pts;
pts += ret;
}
// 发送帧到编码器
ret = avcodec_send_frame(audioCodecContext, data ? frame : nullptr);
if (ret < 0) {
qDebug() << "发送音频帧到编码器失败:" << ffmpegErrorString(ret);
return false;
}
// 接收编码后的包
while (ret >= 0) {
ret = avcodec_receive_packet(audioCodecContext, packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
qDebug() << "接收编码包失败:" << ffmpegErrorString(ret);
return false;
}
// 调整时间戳
av_packet_rescale_ts(packet, audioCodecContext->time_base, audioStream->time_base);
packet->stream_index = audioStream->index;
// 写入包
ret = av_interleaved_write_frame(outputFormatContext, packet);
av_packet_unref(packet);
if (ret < 0) {
qDebug() << "写入音频包失败:" << ffmpegErrorString(ret);
return false;
}
}
return true;
}
void AudioRecorder::cleanup()
{
qDebug() << "AudioRecorder cleanup";
if (swrContext) {
swr_free(&swrContext);
swrContext = nullptr;
}
if (frame) {
av_frame_free(&frame);
frame = nullptr;
}
if (packet) {
av_packet_free(&packet);
packet = nullptr;
}
if (audioCodecContext) {
avcodec_free_context(&audioCodecContext);
audioCodecContext = nullptr;
}
if (outputFormatContext && !(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&outputFormatContext->pb);
}
if (outputFormatContext) {
avformat_free_context(outputFormatContext);
outputFormatContext = nullptr;
}
// 清空音频队列
{
QMutexLocker locker(&queueMutex);
audioQueue.clear();
}
initialized = false;
}
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>600</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>音频采集录制程序</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>音频输入设备:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="deviceComboBox"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>音频编码器:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="codecComboBox"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>采样率:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="sampleRateComboBox"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="startButton">
<property name="text">
<string>开始录制</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopButton">
<property name="text">
<string>停止录制</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="levelLabel">
<property name="text">
<string>音频电平: 0%</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="levelProgressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="text">
<string>就绪</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置应用程序信息
QApplication::setApplicationName("AudioCapture");
QApplication::setApplicationVersion("1.0");
QApplication::setOrganizationName("MyCompany");
MainWindow w;
w.show();
return a.exec();
}
功能特点

1. 音频输入
-
设备选择:自动检测系统可用的音频输入设备
-
格式配置:支持多种采样率(8000Hz - 48000Hz)
-
实时监控:音频电平显示和监控
2. 编码选项
-
多种编码器:支持 AAC、MP3 等常见音频编码格式
-
参数配置:可配置采样率、比特率等参数
-
自动格式匹配:自动选择设备支持的音频格式
3. 录制功能
-
高质量录制:使用 FFmpeg 进行专业级音频编码
-
实时处理:音频数据实时编码,避免延迟
-
文件管理:自动生成带时间戳的文件名
4. 用户界面
-
状态显示:实时显示录制状态和时长
-
电平监控:可视化音频输入电平
-
错误处理:完善的错误提示和处理机制
使用说明
-
选择音频设备:从下拉框选择要使用的麦克风或音频输入设备
-
配置编码参数:
-
选择音频编码器(AAC、MP3等)
-
选择采样率(建议使用44100Hz或48000Hz)
-
-
开始录制:点击"开始录制"按钮,选择保存位置
-
监控录制:
-
查看音频电平显示
-
监控录制时长
-
-
停止录制:点击"停止录制"按钮结束录制
技术细节
音频处理流程
-
Qt 音频采集:使用
QAudioInput从麦克风采集原始 PCM 数据 -
数据缓冲:通过队列管理音频数据,平衡生产和消费速度
-
FFmpeg 编码:
-
使用
swresample进行音频格式转换 -
使用选定编码器进行音频编码
-
写入输出文件
-
关键组件
-
AudioRecorder 类:处理 FFmpeg 编码和文件写入
-
多线程设计:音频采集和编码在不同线程中进行
-
内存管理:合理的队列大小限制,防止内存溢出
这个音频采集程序提供了完整的音频录制功能,具有专业级的音频编码质量和友好的用户界面。

浙公网安备 33010602011771号