【QT window】ffmpeg实现录音功能之AAC格式--mp4

前言

翻看其他博客,基本都是无损PCM,这是最简单的方法,我的上一篇博文(

复制代码
https://blog.csdn.net/liangyuna8787/article/details/155987199?spm=1001.2014.3001.5502

)有详细讲解及实现代码,本篇介绍更常见的压缩格式mp4的音频录音,整体上多了编码等环节,比PCM无损格式要复杂一些。

功能讲解

延续上一篇博文的风格,方便做比较,流程有很多是相同的,不同的在编码等环节,下面会专门标注出增加(绿色 )和修改(蓝色)的内容。

环境搭建

参照我分享的第一篇ffmpeg库调用的博文,传送门:

【QT window】ffmpeg实现手动绘图(裁剪)、缩放、拍照,显示fps等功能

本篇涉及的ffmpeg接口

类别 函数名称 作用说明 所属模块
设备注册 avdevice_register_all() 注册所有输入/输出设备(如 dshow、v4l2、alsa 等 libavdevice
输入格式查找 av_find_input_format(const char *short_name) 查找指定名称的输入格式(如 "dshow" libavformat
打开输入设备 avformat_open_input(AVFormatContext **, const char *, AVInputFormat *, AVDictionary **) 打开输入设备或文件,创建并初始化 AVFormatContext libavformat
获取流信息 avformat_find_stream_info(AVFormatContext *, AVDictionary **) 读取媒体流信息(如采样率、声道数等) libavformat
编解码器操作 avcodec_find_encoder(enum AVCodecID id) 根据编码器 ID(如 AV_CODEC_ID_AAC)查找编码器 libavcodec
编解码器操作 avcodec_alloc_context3(const AVCodec *) 分配编码器上下文 libavcodec
编解码器操作 avcodec_open2(AVCodecContext *, const AVCodec *, AVDictionary **) 打开编码器,应用配置参数 libavcodec
编解码器操作 avcodec_parameters_from_context(AVCodecParameters *, const AVCodecContext *) 将编码器上下文参数复制到流的 codecpar libavcodec
编解码器操作 avcodec_send_frame(AVCodecContext *, const AVFrame *) 向编码器发送原始音频帧 libavcodec
编解码器操作 avcodec_receive_packet(AVCodecContext *, AVPacket *) 从编码器接收编码后的数据包 libavcodec
编解码器操作 avcodec_free_context(AVCodecContext **) 释放编码器上下文及其内部资源 libavcodec
输出文件操作 avformat_alloc_output_context2(AVFormatContext **, AVOutputFormat *, const char *, const char *) 创建输出上下文(用于写入 MP4 等容器) libavformat
输出流创建 avformat_new_stream(AVFormatContext *, const AVCodec *) 为输出上下文添加新流 libavformat
文件 IO 打开 avio_open(AVIOContext **, const char *, int) 打开输出文件用于写入 libavformat (libavio)
文件 IO 关闭 avio_closep(AVIOContext **) 安全关闭并置空 AVIOContext libavformat (libavio)
写入容器头 avformat_write_header(AVFormatContext *, AVDictionary **) 写入容器头部(如 MP4 的 moov box) libavformat
写入数据帧 av_interleaved_write_frame(AVFormatContext *, AVPacket *) 写入交错排列的数据包(自动处理时间戳排序) libavformat
写入容器尾 av_write_trailer(AVFormatContext *) 写入容器尾部信息(如更新 moov) libavformat
释放上下文 avformat_free_context(AVFormatContext *) 释放整个输出/输入上下文 libavformat
重采样(音频格式转换) swr_alloc_set_opts2(SwrContext **, ...) 分配并配置重采样上下文(新版 API) libswresample
重采样初始化 swr_init(SwrContext *) 初始化重采样器 libswresample
重采样 swr_get_out_samples(SwrContext *, int in_samples) 获取重采样后预计输出的样本数 libswresample
执行重采样 swr_convert(SwrContext *, uint8_t **, int, const uint8_t **, int) 执行实际重采样(格式/采样率/布局转换) libswresample
释放重采样 swr_free(SwrContext **) 释放重采样上下文 libswresample
内存与数据管理 av_packet_alloc() 分配 AVPacket 结构体 libavcodec
释放数据包资源 av_packet_unref(AVPacket *) 解引用并清空 packet 数据 libavcodec
内存与数据管理 av_packet_free(AVPacket **) 释放 AVPacket 及其内部缓冲区 libavcodec
内存与数据管理 av_frame_alloc() 分配 AVFrame 结构体 libavutil
内存与数据管理 av_frame_get_buffer(AVFrame *, int align) 为 frame 分配数据缓冲区 libavutil
内存与数据管理 av_frame_unref(AVFrame *) 解引用并清空 frame 数据 libavutil
内存与数据管理 av_samples_alloc(uint8_t ***, ..., enum AVSampleFormat, ...) 分配 planar 音频样本缓冲区 libavutil
内存与数据管理 av_freep(void *) 安全释放内存并置指针为 NULL libavutil
辅助工具 av_get_bytes_per_sample(enum AVSampleFormat) 获取每采样的字节数(如 FLTP 为 4) libavutil
构建声道布局 av_channel_layout_from_mask(AVChannelLayout *, uint64_t) 从位掩码(如 AV_CH_LAYOUT_STEREO)构建声道布局 libavutil
复制声道布局 av_channel_layout_copy(AVChannelLayout *, const AVChannelLayout *) 复制声道布局结构 libavutil
av_packet_rescale_ts(AVPacket *, AVRational src_tb, AVRational dst_tb) 转换 packet 的时间戳(time base) libavutil
av_strerror(int errnum, char *errbuf, size_t errbuf_size) 将 FFmpeg 错误码转为可读字符串 libavutil

过程讲解

1、初始化

1.1、注册所有设备

avdevice_register_all(),注册所有设备,下文有专门的打开音频设备和判断音频数据的环节,不需要特意的只检测音频设备。

因为avdevice_register_all执行很快,ffmpeg未提供专门只检测某类设备的接口。

2、打开输入设备

2.1、找到设备

av_find_input_format("dshow"),获取指定名称的输入格式(如 DirectShow)

2.2、打开设备

avformat_open_input(&inputCtx, device, iformat, opts),通过device指定的音频路径/描述打开音频设备

  • inputCtx:指向 AVFormatContext* 的指针。函数会分配内存并赋值。
  • device:设备路径或文件路径(如 "audio=麦克风" 或你的 GUID 字符串);
  • iformat:指定输入格式(av_find_input_format的返回值);
  • opts: 可选字典参数(如设置采样率、缓冲区大小等),通常传 NULL;

2.3、获取设备流信息

avformat_find_stream_info(inputCtx, opts),读取并解析设备/文件中的流元数据

  • inputCtx:指向 AVFormatContext* 的指针
  • opts:每个流的选项字典(通常传 NULL

3、查找音频流

3.1、遍历所有可用流

通过inputCtx->nb_streams遍历所有的可用流

复制代码
for (uint i = 0; i < inputCtx->nb_streams; i++) {
    ....
}

3.2、识别音频流类型

通过AVMEDIA_TYPE_AUDIO识别出音频流,并且记录音频流索引audioIndex,在数据采集中用到

复制代码
if (inputCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
    audioIndex = i; break;//记录音频流索引供后续使用
}

3.3、若通道布局未指定,手动设置

av_channel_layout_from_mask(AVChannelLayout *, uint64_t),需要手动指定为立体声布局(AV_CH_LAYOUT_STEREO)

4、准备输出上下文

4.1、创建 mp4 输出上下文---change

avformat_alloc_output_context2(&outputCtx, oformat, format_name, filename) 显示指定mp4格式

  • outputCtx:输出参数,分配好的 AVFormatContext*
  • oformat:指定输出格式(可为 NULL
  • format_name:"mp4",指定为mp4格式
  • filename:文件名(用于自动推断格式,文件名后缀名为 ".mp4"

4.2、查找 AAC 编码器---mp4是aac编码的一种-----new

avcodec_find_encoder(AV_CODEC_ID_AAC),指定用AAC编码器

4.3、创建新的输出流-----change

avformat_new_stream(outputCtx, codec) 为输出上下文添加新流

  • outputCtx:输出参数,分配好的 AVFormatContext*
  • codec:编码器,即4.2中返回的aac编码器

4.4、分配编码器上下文,并设置相关参数-----new

avcodec_alloc_context3(codec),alloc即是分配

codec:编码器,即4.2中返回的aac编码器

4.5、打开 AAC 编码器-----new

avcodec_open2(AVCodecContext *codecCtx, const AVCodec *codec, AVDictionary **opt)

  • codecCtx:4.4的编码器的上下文
  • codec:即4.2中返回的aac编码器
  • opt:通常传 NULL

4.6、将编码器上下文参数复制到输出流中-----new

avcodec_parameters_from_context(AVCodecParameters *codecpar, const AVCodecContext *codecCtx)

  • codecpar:输出流的codecpar
  • codecCtx:4.4的编码器的上下文

5、文件IO建立

5.1、打开输出文件

avio_open(&outputCtx->pb, outputfilename, flags) 打开文件用于写入(底层 I/O)

  • &outputCtx->pb:分配 AVIOContext* 并赋值给 outputCtx->pb
  • outputfilename: 文件路径(如 "output.mp4"
  • flags:打开模式,如 AVIO_FLAG_WRITE

6、写入文件头及初始化重采样器

6.1、写入文件头部信息

avformat_write_header(outputCtx, options),

  • outputCtx:输出参数,分配好的 AVFormatContext*
  • options: 写入选项(通常 NULL

6.2、配置并分配 SwrContext-----new

swr_alloc_set_opts2(SwrContext **, ...),设置采样器的上下文

6.3、初始化重采样器-----new

swr_init(SwrContext **)

7、录音主循环

7.1、读原始包

av_read_frame(inputCtx, &pkt),从inputCtx读取数据

7.2、过滤出音频流数据

pkt.stream_index == audioIndex

7.3 音频处理-----new

7.3.1、计算样本数、分配缓冲区、拷贝数据

av_get_bytes_per_sample

7.3.2、设置输入帧

av_channel_layout_copy

7.3.3、计算重采样后样本数

swr_get_out_samples

7.3.4、分配指针数组(每个通道一个指针)

calloc

7.3.5、分配实际音频数据缓冲区

av_samples_alloc

7.4、执行重采样---new

swr_convert

7.5、拆分成标准帧并编码

7.5.1、设置输出帧的基本属性

av_channel_layout_copy

7.5.2、为 outFrame 分配内存缓冲区

av_frame_get_buffer

7.5.3、计算单个采样的字节数

av_get_bytes_per_sample

7.5.4、拷贝 planar 格式数据

每个通道独立存储,需逐通道 memcpy

7.5.5、将构建好的音频帧送入 AAC 编码器

avcodec_send_frame

7.5.6、循环接收编码后的数据包

avcodec_receive_packet

7.6、写入到输出文件

av_interleaved_write_frame(outputCtx, &pkt),将数据包写入输出文件

7.7、释放数据包资源

av_packet_unref(&pkt),释放 AVPacket(原始压缩帧)

7.8、此循环持续直到用户停止录制

通过一个bool值作为开关,让程序跳出while循环即可

8、结束录制

8.1、刷新编码器---new

avcodec_send_frame,重复7.5.5的操作,将剩余的音频帧送入 AAC 编码器

8.2、获取剩余编码包--new

avcodec_receive_packet,接收剩余的编码数据

8.3、写入容器尾

av_write_trailer(outputCtx),ffmpeg根据输出容器格式(如 WAV、MP4、FLV 等)自动构建并写入该格式所需的"尾部"(trailer)数据,包括音频流的必要结束信息。

8.4、释放输入设备资源

avformat_close_input(&inputCtx),关闭输入设备并释放资源

8.5、关闭文件和设备

avio_closep(&outputCtx->pb) ,安全关闭文件句柄

8.6、释放输出上下文

avformat_free_context(outputCtx),释放输出上下文及其内部流、参数等

流程图

图片有点大,暂时上传不了。

源码

复制代码
//mainwindow.h
// mainwindow.h(修改部分)

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPushButton>
#include <QThread>
#include <atomic>
#include <cstring>  // for memset (如果用 av_malloc_array + memset)
#include <cstdlib>  // for calloc, free
extern "C" {
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
}

class RecorderThread : public QThread {
    Q_OBJECT
public:
    explicit RecorderThread(QObject *parent = nullptr);
    virtual ~RecorderThread() = default;
    void run() override;
    void stop();

private:
    // >>> 新增:AAC 编码所需上下文 <<<
    AVFormatContext* m_inputCtx = nullptr;
    AVFormatContext* m_outputCtx = nullptr;
    AVCodecContext* m_codecCtx = nullptr;
    SwrContext* m_swrCtx = nullptr;
    AVStream* m_outStream = nullptr;

    int m_audioStreamIndex = -1;
    std::atomic<bool> m_stop{false};
    int64_t m_pts = 0;

    // >>> 移除旧的 cleanup 函数 <<<
    void cleanup(); // 简化为无参
};

class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private slots:
    void onStartClicked();
    void onStopClicked();
private:
    QPushButton *startBtn;
    QPushButton *stopBtn;
    RecorderThread *recorderThread = nullptr;
    bool isRecording = false;
};

#endif // MAINWINDOW_H

//mainwindow.cpp
#include "mainwindow.h"
#include <QVBoxLayout>
#include <QWidget>
#include <QApplication>
#include <QDebug>

// 初始化 FFmpeg(全局一次)
static void initFFmpeg() {
    //1.初始化FFmpeg‌
    avdevice_register_all();//注册所有输入设备
}

RecorderThread::RecorderThread(QObject *parent)
    : QThread(parent) {}

void RecorderThread::stop() {
    m_stop = true;
    wait(); // 等待线程结束
}

void RecorderThread::cleanup() {
    if (m_swrCtx) {
        swr_free(&m_swrCtx);
        m_swrCtx = nullptr;
    }
    if (m_codecCtx) {
        //8.3、释放编解码器上下文---new
        avcodec_free_context(&m_codecCtx);
        m_codecCtx = nullptr;
    }
    if (m_inputCtx) {
        //8.4、释放输入设备资源
        avformat_close_input(&m_inputCtx);
        m_inputCtx = nullptr;
    }
    if (m_outputCtx) {
        if (!(m_outputCtx->oformat->flags & AVFMT_NOFILE)) {
            //8.5、关闭文件和设备
            avio_closep(&m_outputCtx->pb);
        }
        //8.6、释放输出上下文
        avformat_free_context(m_outputCtx);
        m_outputCtx = nullptr;
    }
    m_pts = 0;
}

void RecorderThread::run()
{
    const char* deviceName = R"(audio=@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{60D84350-EE0D-4AD4-BDA1-9714DE1AB548})";
    const char* outputFilename = "output.mp4";

    int ret = 0;

    //‌2.打开输入设备
    //2.1、找到DirectShow输入格式
    const AVInputFormat* iformat = av_find_input_format("dshow");
    //2.2、打开指定的音频设备(麦克风)
    ret = avformat_open_input(&m_inputCtx, deviceName, iformat, nullptr);
    if (ret < 0) {
        qWarning() << "Open device failed";
        cleanup();
        return;
    }
    //2.3、获取设备流信息
    ret = avformat_find_stream_info(m_inputCtx, nullptr);
    if (ret < 0) {
        qWarning() << "Find stream info failed";
        cleanup();
        return;
    }

    //3、查找音频流
    m_audioStreamIndex = -1;
    //3.1、遍历流查找音频流 (AVMEDIA_TYPE_AUDIO)
    for (uint i = 0; i < m_inputCtx->nb_streams; ++i) {
        //3.2、识别音频流类型
        if (m_inputCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            m_audioStreamIndex = i;//记录音频索引
            break;
        }
    }
    if (m_audioStreamIndex == -1) {
        qWarning() << "No audio stream found";
        cleanup();
        return;
    }
    //3.3、若通道布局未指定,手动设置为 AV_CH_LAYOUT_STEREO-----new
    AVCodecParameters* inPar = m_inputCtx->streams[m_audioStreamIndex]->codecpar;
    if (inPar->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) {
        av_channel_layout_from_mask(&inPar->ch_layout, AV_CH_LAYOUT_STEREO);//手动指定为立体声布局
    }

    qDebug() << "[Input] SampleRate:" << inPar->sample_rate
             << "Channels:" << inPar->ch_layout.nb_channels
             << "Format:" << inPar->format;

    // ===== 初始化输出上下文和 AAC 编码器 =====
    //4.‌输出文件准备
    //4.1、创建 mp4 输出上下文---change
    ret = avformat_alloc_output_context2(&m_outputCtx, nullptr, "mp4", outputFilename);
    if (ret < 0 || !m_outputCtx) {
        qWarning() << "Failed to create output context";
        cleanup();
        return;
    }
    //4.2、查找 AAC 编码器---mp4是aac编码的一种-----new
    const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    if (!codec) {
        qWarning() << "AAC encoder not found";
        cleanup();
        return;
    }
    //4.3、创建新的输出流
    m_outStream = avformat_new_stream(m_outputCtx, codec);
    if (!m_outStream) {
        qWarning() << "Failed to create output stream";
        cleanup();
        return;
    }
    //4.4、分配编码器上下文,并设置相关参数-----new
    m_codecCtx = avcodec_alloc_context3(codec);
    m_codecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP;
    m_codecCtx->bit_rate = 128000;
    m_codecCtx->sample_rate = inPar->sample_rate;
    m_codecCtx->time_base = (AVRational){1, m_codecCtx->sample_rate};
    av_channel_layout_copy(&m_codecCtx->ch_layout, &inPar->ch_layout);
    //4.5、打开 AAC 编码器-----new
    ret = avcodec_open2(m_codecCtx, codec, nullptr);
    if (ret < 0) {
        qWarning() << "Failed to open AAC encoder";
        cleanup();
        return;
    }
    //4.6、将编码器上下文参数复制到输出流中-----new
    ret = avcodec_parameters_from_context(m_outStream->codecpar, m_codecCtx);
    m_outStream->time_base = m_codecCtx->time_base;

    //5.‌文件IO建立
    if (!(m_outputCtx->oformat->flags & AVFMT_NOFILE)) {
        //5.1打开输出文件
        ret = avio_open(&m_outputCtx->pb, outputFilename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            qWarning() << "Failed to open output file";
            cleanup();
            return;
        }
    }

    //6、写入文件头及初始化重采样器
    //6.1、写入文件头,使用 outputCtx 中已配置好的流信息来生成正确的头部
    ret = avformat_write_header(m_outputCtx, nullptr);
    if (ret < 0) {
        qWarning() << "Failed to write header";
        cleanup();
        return;
    }

    // ===== 初始化重采样器 =====
    //6.2、配置并分配 SwrContext-----new
    ret = swr_alloc_set_opts2(
        &m_swrCtx,
        &m_codecCtx->ch_layout, m_codecCtx->sample_fmt, m_codecCtx->sample_rate,
        &inPar->ch_layout, static_cast<AVSampleFormat>(inPar->format), inPar->sample_rate,
        0, nullptr
    );
    if (ret < 0 || !m_swrCtx) {
        qWarning() << "Failed to alloc resampler";
        cleanup();
        return;
    }
    //6.3、初始化重采样器-----new
    swr_init(m_swrCtx);

    // ===== 主循环 =====
    AVPacket* pkt = av_packet_alloc();
    AVFrame* inFrame = av_frame_alloc();
    AVFrame* outFrame = av_frame_alloc();
    uint8_t** tempBuffer = nullptr; // 用于存储重采样后的 planar 数据
    int totalPackets = 0;
    int audioFrameCount = 0;
    m_pts = 0;

    int frame_size = m_codecCtx->frame_size;
    if (frame_size <= 0) frame_size = 1024; // AAC 通常需要 1024

    qDebug() << "Encoder expected frame_size:" << frame_size;
    qDebug() << "=== Starting main capture loop ===";

    while (!m_stop.load()) {
        //7.1、读原始包
        ret = av_read_frame(m_inputCtx, pkt);
        if (ret == AVERROR(EAGAIN)) continue;
        if (ret == AVERROR_EOF) break;
        if (ret < 0) break;
        //7.2、过滤出音频流数据
        if (pkt->stream_index == m_audioStreamIndex) {
            audioFrameCount++;
            qDebug() << ">>> Processing audio frame #" << audioFrameCount << "| size:" << pkt->size;
            //7.3 音频处理
            //7.3.1、计算样本数、分配缓冲区、拷贝数据
            int bytes_per_sample = av_get_bytes_per_sample(static_cast<AVSampleFormat>(inPar->format));
            int channels = inPar->ch_layout.nb_channels;
            int nb_samples = pkt->size / (bytes_per_sample * channels);
            if (nb_samples <= 0) {
                av_packet_unref(pkt);
                continue;
            }

            //7.3.2、设置输入帧
            inFrame->nb_samples = nb_samples;
            inFrame->format = inPar->format;
            inFrame->sample_rate = inPar->sample_rate;
            av_channel_layout_copy(&inFrame->ch_layout, &inPar->ch_layout); // 声道布局

            ret = av_frame_get_buffer(inFrame, 0);
            if (ret < 0) {
                av_packet_unref(pkt);
                continue;
            }
            memcpy(inFrame->data[0], pkt->data, pkt->size);

            //7.3.3、计算重采样后样本数
            int out_samples = swr_get_out_samples(m_swrCtx, nb_samples);
            if (out_samples <= 0) {
                av_frame_unref(inFrame);
                av_packet_unref(pkt);
                continue;
            }

            //7.3.4、分配指针数组(每个通道一个指针)
            tempBuffer = (uint8_t**)calloc(channels, sizeof(uint8_t*));
            if (!tempBuffer) {
                qWarning() << "calloc tempBuffer failed";
                av_frame_unref(inFrame);
                av_packet_unref(pkt);
                continue;
            }

            //7.3.5、分配实际音频数据缓冲区(由 FFmpeg 管理)
            ret = av_samples_alloc(tempBuffer, nullptr, channels, out_samples,
                                   m_codecCtx->sample_fmt, 0);
            if (ret < 0) {
                qWarning() << "av_samples_alloc failed";
                free(tempBuffer);
                tempBuffer = nullptr;
                av_frame_unref(inFrame);
                av_packet_unref(pkt);
                continue;
            }

            //7.4、执行重采样---new
            int converted = swr_convert(m_swrCtx, tempBuffer, out_samples,
                                        (const uint8_t**)inFrame->data, nb_samples);
            if (converted <= 0) {
                av_freep(&tempBuffer[0]);
                free(tempBuffer);
                tempBuffer = nullptr;
                av_frame_unref(inFrame);
                av_packet_unref(pkt);
                continue;
            }

            //7.5、拆分成标准帧(如 1024 samples)并编码,按照 frame_size进行切片处理
            int offset = 0;
            while (offset + frame_size <= converted) {
                //7.5.1、设置输出帧的基本属性
                outFrame->nb_samples = frame_size;
                outFrame->format = m_codecCtx->sample_fmt;
                outFrame->sample_rate = m_codecCtx->sample_rate;
                av_channel_layout_copy(&outFrame->ch_layout, &m_codecCtx->ch_layout); // 声道布局
                //7.5.2、为 outFrame 分配内存缓冲区
                ret = av_frame_get_buffer(outFrame, 0);
                if (ret < 0) {
                    offset += frame_size;
                    continue;
                }

                //7.5.3、计算单个采样的字节数(例如 FLTP 是 4 字节/采样)
                int sample_size = av_get_bytes_per_sample(m_codecCtx->sample_fmt);
                //7.5.4、拷贝 planar 格式数据:每个通道独立存储,需逐通道 memcpy
                for (int ch = 0; ch < channels; ch++) {
                    memcpy(outFrame->data[ch],
                           tempBuffer[ch] + offset * sample_size,
                           frame_size * sample_size);
                }
                // 设置 PTS(Presentation Timestamp):用于同步和容器写入
                outFrame->pts = m_pts;
                m_pts += frame_size;
                //7.5.5、将构建好的音频帧送入 AAC 编码器
                ret = avcodec_send_frame(m_codecCtx, outFrame);
                if (ret < 0) {
                    char errbuf[256];
                    av_strerror(ret, errbuf, sizeof(errbuf));
                    qDebug() << " avcodec_send_frame failed:" << errbuf;
                } else {
                    // 成功送帧后,尝试接收所有可输出的编码包(可能 0 个、1 个或多个)
                    AVPacket* enc_pkt = av_packet_alloc();
                    //7.5.6、循环接收编码后的数据包
                    while (avcodec_receive_packet(m_codecCtx, enc_pkt) == 0) {
                        enc_pkt->stream_index = m_outStream->index;
                        av_packet_rescale_ts(enc_pkt, m_codecCtx->time_base, m_outStream->time_base);
                        //7.6、写入到输出文件(自动处理 interleaving)
                        av_interleaved_write_frame(m_outputCtx, enc_pkt);
                        //7.7、释放数据包资源
                        av_packet_unref(enc_pkt);
                        totalPackets++;
                    }
                    av_packet_free(&enc_pkt);//释放packet
                }
                // 释放 outFrame 的 buffer 引用(下次循环会重新 av_frame_get_buffer)
                av_frame_unref(outFrame);
                offset += frame_size;// 移动偏移,处理下一帧
            }

            // 释放临时缓冲区
            av_freep(&tempBuffer[0]); // 音频数据
            free(tempBuffer);         // 指针数组
            tempBuffer = nullptr;
            // 释放 inFrame 的 buffer 引用(下次循环会重新 av_frame_get_buffer)
            av_frame_unref(inFrame);
        }
        //7.5.8、释放数据包资源
        av_packet_unref(pkt);
    }

    //8.结束录制
    //8.1、刷新编码器---new
    avcodec_send_frame(m_codecCtx, nullptr);
    AVPacket* flush_pkt = av_packet_alloc();
    //8.2、获取剩余编码包--new
    while (avcodec_receive_packet(m_codecCtx, flush_pkt) == 0) {
        flush_pkt->stream_index = m_outStream->index;
        av_packet_rescale_ts(flush_pkt, m_codecCtx->time_base, m_outStream->time_base);
        av_interleaved_write_frame(m_outputCtx, flush_pkt);
        av_packet_unref(flush_pkt);
        totalPackets++;
    }
    av_packet_free(&flush_pkt);
    //8.3、写入文件尾部信息
    av_write_trailer(m_outputCtx);

    // 清理
    if (tempBuffer) {
        av_freep(&tempBuffer[0]);
        free(tempBuffer);
    }
    cleanup();

    qDebug() << "=== Final Report ===";
    qDebug() << "Audio frames processed:" << audioFrameCount;
    qDebug() << "Total encoded packets:" << totalPackets;
}


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent) {
    initFFmpeg();

    QWidget *central = new QWidget(this);
    setCentralWidget(central);

    startBtn = new QPushButton("开始录音", this);
    stopBtn = new QPushButton("结束录音", this);
    stopBtn->setEnabled(false);

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(startBtn);
    layout->addWidget(stopBtn);
    central->setLayout(layout);

    connect(startBtn, &QPushButton::clicked, this, &MainWindow::onStartClicked);
    connect(stopBtn, &QPushButton::clicked, this, &MainWindow::onStopClicked);
}

MainWindow::~MainWindow() {
    if (recorderThread) {
        recorderThread->stop();
        delete recorderThread;
    }
}

void MainWindow::onStartClicked() {
    if (isRecording) return;
    recorderThread = new RecorderThread(this);
    recorderThread->start();
    isRecording = true;
    startBtn->setEnabled(false);
    stopBtn->setEnabled(true);
}

void MainWindow::onStopClicked() {
    if (!isRecording) return;
    if (recorderThread) {
        recorderThread->stop();
        delete recorderThread;
        recorderThread = nullptr;
    }
    isRecording = false;
    startBtn->setEnabled(true);
    stopBtn->setEnabled(false);
}
相关推荐
SunkingYang2 小时前
QT设计师里的Text Edit、Plain Text Edit、Text Browser分别用什么作用,又有什么区别
qt·区别·qt设计师·功能·text edit·plain text edit·text browser
小曾同学.com2 小时前
HLS协议中m3u8列表及ts文件的由来
ffmpeg·hls·ts文件·m3u8列表
木千11 小时前
Qt中关于eventFilter函数无法过滤QTableWidget鼠标事件的处理方式
qt
skyjilygao16 小时前
n8n整合ffmpeg
ffmpeg·视频编辑·n8n
奇树谦18 小时前
【Qt实战】实现图片缩放、平移与像素级查看功能
开发语言·qt
别动哪条鱼20 小时前
SDL 函数对各对象缓冲区的影响
网络·数据结构·ffmpeg
010米粉01021 小时前
Qt Cmake之路(一):Cmake变量语法
开发语言·qt·cmake
颜*鸣&空21 小时前
Qt Creator快速搭建项目
开发语言·qt