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 += targetmainwindow.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_Hmainwindow.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_Haudiorecorder.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号
浙公网安备 33010602011771号