【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);
}
相关推荐
环黄金线HHJX.15 小时前
拼音字母量子编程PQLAiQt架构”这一概念。结合上下文《QuantumTuan ⇆ QT:Qt》
开发语言·人工智能·qt·编辑器·量子计算
猿小路15 小时前
视频流熟知
ffmpeg·h.264
abcd_zjq16 小时前
VS2022+QT6.9配置ONNXruntime GPU、CUDA、cuDNN(附官网下载链接)(GPU开启代码示例)
qt·visual studio·cuda·onnx
chen_22716 小时前
动态桌面方案
c++·qt·ffmpeg·kanzi
GeniuswongAir1 天前
苹果电脑上启动一个 RTSP 推流,用来做测试
ffmpeg
雒珣1 天前
Qt简单任务的多线程操作(无需创建类)
开发语言·qt
qq_401700411 天前
QT C++ 好看的连击动画组件
开发语言·c++·qt
Benny的老巢1 天前
n8n工作流通过Execute Command用FFmpeg处理音频,报错 stderr maxBuffer length exceeded的解决方案
ffmpeg·音频合成·n8n·n8n工作流·execute command
m0_635647481 天前
Qt使用第三方组件库新手教程(一)
开发语言·c++·qt
雒珣1 天前
控制QT生成目录
开发语言·qt