ffmpeg录制视频功能

本文目录

1.环境配置

下载并安装FFmpeg库 在Windows上 从FFmpeg官方网站下载预编译的FFmpeg库: 解压下载的文件,并记下解压后的路径。
FFmpeg下载(windows版本)_libijkffmpeg.so 32位下载-CSDN博客
记住一定下载是32位,否则使用msvc32位时无法进行链接对应库的。
将下载后的 static 中动态库拿出来,将shared的include与lib拿出来,放在当前项目目录下。

复制代码
#FFmpeg库的路径
INCLUDEPATH += /path/to/ffmpeg/include
LIBS += -L/path/to/ffmpeg/lib -lavcodec -lavformat -lavutil -lswscale
#如果你在Windows上使用预编译的FFmpeg库,可能还需要添加以下内容
LIBS += -lavdevice -lavfilter -lswresample -lpostproc
重要的一点:指定可执行程序的路径,这里就是我们动态库的目录下:
DESTDIR =$$PWD/bin    #指定可执行的生成路径--到库的位置

2.ffmpeg编解码的主要逻辑:

  1. 初始化FFmpeg库在使用FFmpeg之前,需要初始化FFmpeg库。
dart 复制代码
*extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/avutil.h>
}
void initializeFFmpeg() {
    av_register_all();
    avcodec_register_all();
    avformat_network_init();
}*

首先,在使用ffmpeg的上下文,音视频流,编解码...之前,都需要先进性初始化,

在使用FFmpeg进行多媒体处理时,初始化库是非常重要的步骤。以下是对 av_register_all、avcodec_register_all 和 avformat_network_init 三个初始化函数的详细解释:

  1. av_register_all()

    含义

    av_register_all 函数用于注册所有的编解码器、文件格式和协议。它是FFmpeg库的初始化函数之一,确保在使用FFmpeg的任何功能之前,所有的编解码器和格式都已注册。

    2.av_register_all();

    作用:

    注册所有的编解码器(如H.264、AAC等)。

    注册所有的文件格式(如MP4、AVI等)。

    注册所有的协议(如HTTP、RTMP等)。

  2. avcodec_register_all()

    avcodec_register_all 函数用于注册所有的编解码器。虽然 av_register_all 已经包含了这个步骤,但在某些情况下,你可能只需要注册编解码器而不需要注册其他组件。

    avcodec_register_all();

    主要作用:注册所有的编解码器(如H.264、AAC等)。

  3. avformat_network_init()

    avformat_network_init 函数用于初始化网络组件。它在使用任何网络协议(如HTTP、RTMP等)之前调用,以确保网络功能已正确初始化。

    主要作用:初始化网络组件,确保网络协议(如HTTP、RTMP等)可以正常使用。

    一般我们在构造进行库的初始化,之后,我们回去在一个初始化接口中去进行ffmpeg的准备工作:

    第一个准备工作就是去设置编解码器:

  4. 设置编解码器 ---参数设置

dart 复制代码
// 设置选项
AVDictionary *options = nullptr;
av_dict_set(&options, "rtbufsize", "100M", 0); // 设置实时缓冲区大小
av_dict_set(&options, "framerate", "30", 0);  // 设置帧率为30 FPS
复制代码
//在打开输入文件,打开编码器,写入输出文件时可以设置这些参数

通过 AVDictionary 设置选项来配置输入设备的参数,例如帧率,缓冲区大小,防止在写入时崩溃。

初始化

dart 复制代码
主要是对与FFmpeg的以下成员进行设置初始化:
AVFormatContext)格式上下文(用于管理输出文件的格式信息
 AVCodecContext:FFmpeg的编解码器上下文,用于管理视频编码器的参数和状态
AVStream :FFmpeg的视频流,用于表示输出文件中的视频流
SwsContext :FFmpeg的图像转换上下文,用于将捕获的图像转换为编码器需要的格式
 AVFrame :FFmpeg的视频帧,用于存储要编码的视频数据
int frame:帧计数器,用于跟踪已捕获的帧数

常见的资源设置有如下这些:

AVCodecContext

资源:AVCodecContext 是编码器或解码器的上下文,包含了编解码器的所有状态信息和参数。

释放函数:avcodec_free_context

  1. AVFrame

    资源:AVFrame 用于存储解码后的帧数据或编码前的帧数据。

    释放函数:av_frame_free

  2. SwsContext

    资源:SwsContext 是用于图像缩放和像素格式转换的上下文。

    释放函数:sws_freeContext

  3. AVFormatContext

    资源:AVFormatContext 是多媒体文件的上下文,包含了文件格式、流信息等。

    释放函数:avformat_free_context

  4. AVPacket

    资源:AVPacket 用于存储编码后的数据包。

    释放函数:av_packet_unref

  5. AVCodecParameters

    资源:AVCodecParameters 用于存储编解码器的参数。

    释放函数:avcodec_parameters_free

  6. AVIOContext

    资源:AVIOContext 是用于输入输出操作的上下文。

    释放函数:avio_context_free

  7. AVFilterGraph

    资源:AVFilterGraph 是用于音视频过滤操作的图。

    释放函数:avfilter_graph_free

  8. AVFilterContext

    资源:AVFilterContext 是用于音视频过滤操作的上下文。

    释放函数:avfilter_free

  9. AVDictionary

    资源:AVDictionary 是用于存储键值对的字典,常用于传递选项。

    释放函数:av_dict_free

    设置编解码器的主要步骤:

  10. 创建输出上下文

dart 复制代码
avformat_alloc_output_context2(&formatContext, nullptr, nullptr, filename);
if (!formatContext) {
    qDebug() << "无法创建输出上下文";
    return;
}

avformat_alloc_output_context2 函数根据指定的文件名创建一个输出格式上下文。如果成功,formatContext 将指向一个新的AVFormatContext 结构体。如果失败,formatContext 将为 nullptr。

  1. 查找编码器
dart 复制代码
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec) {
    qDebug() << "找不到编码器";
    return;
}

avcodec_find_encoder 函数根据编码器ID(这里是AV_CODEC_ID_H264)查找对应的编码器。如果成功,codec 将指向一个 AVCodec 结构体。如果失败,codec 将为 nullptr。

  1. 创建视频流
dart 复制代码
videoStream = avformat_new_stream(formatContext, codec);
if (!videoStream) {
    qDebug() << "无法创建视频流";
    return;
}

avformat_new_stream 函数在 formatContext 中创建一个新的流,并将其与指定的编码器关联。如果成功,videoStream 将指向一个新的 AVStream 结构体。如果失败,videoStream 将为 nullptr。

  1. 分配编码器上下文
dart 复制代码
codecContext = avcodec_alloc_context3(codec);
if (!codecContext) {
    qDebug() << "无法分配视频编解码器上下文";
    return;
}
//设置编码器参数
codecContext->bit_rate = 400000;
codecContext->width = width;
codecContext->height = height;
codecContext->time_base = {1, 25};
codecContext->framerate = {25, 1};
codecContext->gop_size = 10;
codecContext->max_b_frames = 1;
codecContext->pix_fmt = AV_PIX_FMT_YUV420P;

avcodec_alloc_context3 函数为指定的编码器分配并初始化一个新的编码器上下文。如果成功,codecContext 将指向一个新的 AVCodecContext 结构体。如果失败,codecContext 将为 nullptr。

参数设置:

bit_rate:设置编码器的比特率。

width 和 height:设置视频的宽度和高度。

time_base:设置时间基,表示每秒25帧。

framerate:设置帧率。

gop_size:设置GOP(Group of Pictures)大小。

max_b_frames:设置最大B帧数。

pix_fmt:设置像素格式。

  1. 设置全局头部标志
dart 复制代码
if (formatContext->oformat->flags & AVFMT_GLOBALHEADER) {
    codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}

如果输出格式需要全局头部(AVFMT_GLOBALHEADER),则在编码器上下文中设置 AV_CODEC_FLAG_GLOBAL_HEADER 标志。

注意:

在使用FFmpeg进行解码和编码操作时,都需要打开对应的编解码器。具体来说,你需要为解码器和编码器分别初始化和打开它们的上下文。(demo这里只涉及编码)

解码器和编码器的初始化和打开

解码器

查找解码器:使用 avcodec_find_decoder 函数查找解码器。

分配解码器上下文:使用 avcodec_alloc_context3 函数分配解码器上下文。

打开解码器:使用 avcodec_open2 函数打开解码器。

编码器

查找编码器:使用 avcodec_find_encoder 函数查找编码器。

分配编码器上下文:使用 avcodec_alloc_context3 函数分配编码器上下文。

设置编码器参数:设置编码器的必要参数,如比特率、宽度、高度、像素格式等。

打开编码器:使用 avcodec_open2 函数打开编码器。

两者使用基本上一样,但注意及时两个类型一样,但编码器与解码器使用的是独立的上下文,注意这一点!!

以下是对于编码器的编写流程:

dart 复制代码
int main() {
    // 初始化FFmpeg库
    avcodec_register_all();
    avformat_network_init();

    // 查找H.264编码器
    AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
        std::cerr << "找不到H.264编码器" << std::endl;
        return -1;
    }

    // 分配编码器上下文
    AVCodecContext *codecContext = avcodec_alloc_context3(codec);
    if (!codecContext) {
        std::cerr << "无法分配编码器上下文" << std::endl;
        return -1;
    }

    // 设置编码器参数
    codecContext->bit_rate = 400000;
    codecContext->width = 640;
    codecContext->height = 480;
    codecContext->time_base = {1, 25};
    codecContext->framerate = {25, 1};
    codecContext->gop_size = 10;
    codecContext->max_b_frames = 1;
    codecContext->pix_fmt = AV_PIX_FMT_YUV420P;

    if (avcodec_open2(codecContext, codec, nullptr) < 0) {
        std::cerr << "无法打开编解码器" << std::endl;
        avcodec_free_context(&codecContext);
        return -1;
    }


    // 发送一个空帧到编码器,以便刷新编码器的内部缓冲区
    int ret = avcodec_send_frame(codecContext, nullptr);
    if (ret < 0) {
        char errbuf[128];
        av_strerror(ret, errbuf, sizeof(errbuf));
        std::cerr << "发送空帧到编码器时出错: " << errbuf << std::endl;
        avcodec_free_context(&codecContext);
        return -1;
    }

    // 接收所有剩余的编码数据包
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = nullptr;
    pkt.size = 0;

    while (true) {
        ret = avcodec_receive_packet(codecContext, &pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if (ret < 0) {
            char errbuf[128];
            av_strerror(ret, errbuf, sizeof(errbuf));
            std::cerr << "接收编码后的数据包时出错: " << errbuf << std::endl;
            break;
        }

        // 处理编码后的数据包
        std::cout << "接收到一个编码数据包,大小: " << pkt.size << " 字节" << std::endl;

        // 释放数据包
        av_packet_unref(&pkt);
    }

    // 释放资源
    avcodec_free_context(&codecContext);

    return 0;
}
  1. 打开编码器
dart 复制代码
if (avcodec_open2(codecContext, codec, nullptr) < 0) {
    qDebug() << "无法打开编解码器";
    return;
}

avcodec_open2 函数初始化编码器上下文并打开编码器。如果成功,返回0;如果失败,返回负值。

  1. 复制编码器参数
dart 复制代码
if (avcodec_parameters_from_context(videoStream->codecpar, codecContext) < 0) {
    qDebug() << "无法复制编解码器参数";
    return;
}

avcodec_parameters_from_context 函数将编码器上下文中的参数复制到视频流的参数结构体中。如果成功,返回0;如果失败,返回负值。

  1. 打开输出文件
dart 复制代码
if (!(formatContext->oformat->flags & AVFMT_NOFILE)) {
    if (avio_open(&formatContext->pb, filename, AVIO_FLAG_WRITE) < 0) {
        qDebug() << "无法打开输出文件";
        return;
    }
}

如果输出格式不需要文件(AVFMT_NOFILE),则跳过此步骤。否则,使用 avio_open 函数打开输出文件。如果成功,返回0;如果失败,返回负值。 修改输出文件路径为桌面路径,---修改输出上下文中的输出路径为桌面路径

dart 复制代码
if (avformat_write_header(formatContext, nullptr) < 0) {
    qDebug() << "打开输出文件时出错";
    return;
}

avformat_write_header 函数将文件头写入输出文件。如果成功,返回0;如果失败,返回负值。

  1. 分配视频帧
dart 复制代码
frame = av_frame_alloc();
if (!frame) {
    qDebug() << "无法分配视频帧";
    return;
}
frame->format = codecContext->pix_fmt;
frame->width = codecContext->width;
frame->height = codecContext->height;

分配并初始化一个视频帧。

av_frame_alloc 函数分配一个新的视频帧。如果成功,frame 将指向一个新的 AVFrame 结构体。如果失败,frame 将为 nullptr。然后设置帧的格式、宽度和高度。

  1. 分配视频帧数据
dart 复制代码
if (av_frame_get_buffer(frame, 32) < 0) {
    qDebug() << "无法分配视频帧数据";
    return;
}

av_frame_get_buffer 函数为视频帧分配数据缓冲区。如果成功,返回0;如果失败,返回负值。

  1. 初始化像素格式转换上下文
dart 复制代码
swsContext = sws_getContext(codecContext->width, codecContext->height, AV_PIX_FMT_RGB24,
                            codecContext->width, codecContext->height, codecContext->pix_fmt,
                            SWS_BICUBIC, nullptr, nullptr, nullptr);
if (!swsContext) {
    qDebug() << "无法初始化转换上下文";
    return;
}

sws_getContext 函数创建一个用于像素格式转换的上下文。它将源图像的格式(AV_PIX_FMT_RGB24)转换为目标图像的格式(codecContext->pix_fmt)。如果成功,swsContext 将指向一个新的 SwsContext 结构体。如果失败,swsContext 将为 nullptr。

参数为:输入图像的高度,宽度,像素格式,输出图像的宽度,高度,像素格式,(缩放算法,过滤器

输入格式:RGB24(每个像素由三个字节表示,分别表示红色、绿色和蓝色分量)。

输出格式:通常是 YUV420P(每个像素由三个分量表示,分别是亮度(Y)和两个色度(U 和 V)。Y 分量的分辨率与原图像相同,而 U 和 V 分量的分辨率是原图像的一半(水平和垂直方向上各减少一半))。

完整代码:

dart 复制代码
    avformat_alloc_output_context2(&formatContext, nullptr, nullptr, filename);
    if (!formatContext) {
        qDebug() << "无法创建输出上下文";
        return;
    }

    AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
        qDebug() << "找不到编码器";
        return;
    }

    videoStream = avformat_new_stream(formatContext, codec);
    if (!videoStream) {
        qDebug() << "无法创建视频流";
        return;
    }

    codecContext = avcodec_alloc_context3(codec);
    if (!codecContext) {
        qDebug() << "无法分配视频编解码器上下文";
        return;
    }

    codecContext->bit_rate = 400000;
    codecContext->width = width;
    codecContext->height = height;
    codecContext->time_base = {1, 25};
    codecContext->framerate = {25, 1};
    codecContext->gop_size = 10;
    codecContext->max_b_frames = 1;
    codecContext->pix_fmt = AV_PIX_FMT_YUV420P;

    if (formatContext->oformat->flags & AVFMT_GLOBALHEADER) {
        codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    if (avcodec_open2(codecContext, codec, nullptr) < 0) {
        qDebug() << "无法打开编解码器";
        return;
    }

    if (avcodec_parameters_from_context(videoStream->codecpar, codecContext) < 0) {
        qDebug() << "无法复制编解码器参数";
        return;
    }

    if (!(formatContext->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&formatContext->pb, filename, AVIO_FLAG_WRITE) < 0) {
            qDebug() << "无法打开输出文件";
            return;
        }
    }

    if (avformat_write_header(formatContext, nullptr) < 0) {
        qDebug() << "打开输出文件时出错";
        return;
    }

    frame = av_frame_alloc();
    if (!frame) {
        qDebug() << "无法分配视频帧";
        return;
    }
    frame->format = codecContext->pix_fmt;
    frame->width = codecContext->width;
    frame->height = codecContext->height;

    if (av_frame_get_buffer(frame, 32) < 0) {
        qDebug() << "无法分配视频帧数据";
        return;
    }

    swsContext = sws_getContext(codecContext->width, codecContext->height, AV_PIX_FMT_RGB24,
                                codecContext->width, codecContext->height, codecContext->pix_fmt,
                                SWS_BICUBIC, nullptr, nullptr, nullptr);
    if (!swsContext) {
        qDebug() << "无法初始化转换上下文";
        return;
    }
}

3. 捕获屏幕帧与写入输出文件

这里主要是使用Qt的grab函数捕获屏幕帧,并进行像素格式转换。具体步骤如下:

  1. 捕获屏幕
dart 复制代码
QPixmap originalPixmap = QGuiApplication::primaryScreen()->grabWindow(0);
QImage image = originalPixmap.toImage().convertToFormat(QImage::Format_RGB888);

QGuiApplication::primaryScreen()->grabWindow(0):捕获整个屏幕(窗口ID为0表示整个屏幕)。

originalPixmap.toImage().convertToFormat(QImage::Format_RGB888):将捕获的图像转换为 QImage 格式,并将其像素格式转换为 RGB888。

  1. 准备像素格式转换

uint8_t *data1 = { image.bits() };

int linesize1 = { static_cast(image.bytesPerLine()) };

功能:准备源图像数据和行字节数,以便进行像素格式转换。

解释:

data1:指向源图像数据的指针数组。

linesize1:源图像每行的字节数。

  1. 像素格式转换
dart 复制代码
sws_scale(swsContext, data, linesize, 0, codecContext->height, frame->data, frame->linesize);

swsContext:像素格式转换上下文。data 和 linesize:源图像数据和行字节数。--根据格式转化上下文转换对应的输入到输出----

输入数据属性:swsContext,data为图像数据,RGB24,

输出数据属性:codecContext->height, frame->data, frame->linesize

0:源图像的起始行。

codecContext->height:源图像的行数。

frame->data 和 frame->linesize:目标图像数据和行字节数。

  1. 记录帧的个数

frame->pts = frameCounter++;

解释:frameCounter 是一个递增的计数器。

  1. 发送帧到编码器
dart 复制代码
int ret = avcodec_send_frame(codecContext, frame);
if (ret < 0) {
    char errbuf[128];
    av_strerror(ret, errbuf, sizeof(errbuf));
    qDebug() << "发送帧到编码器时出错: " << errbuf;
    return;
}

功能:将帧发送到编码器。

解释:

avcodec_send_frame:将帧发送到编码器。

ret:返回值,如果小于0表示出错。

av_strerror:获取详细的错误信息。

  1. 初始化数据包
dart 复制代码
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = nullptr;
pkt.size = 0;

av_init_packet:初始化数据包。

pkt.data 和 pkt.size:设置数据包的数据指针和大小。

  1. 接收编码后的数据包
dart 复制代码
while (ret >= 0) {
    ret = avcodec_receive_packet(codecContext, &pkt);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
        break;
    } else if (ret < 0) {
        char errbuf[128];
        av_strerror(ret, errbuf, sizeof(errbuf));
        qDebug() << "接收编码后的数据包时出错: " << errbuf;
        break;
    }
 
    av_interleaved_write_frame(formatContext, &pkt);
    av_packet_unref(&pkt);
}

avcodec_receive_packet:从编码器接收编码后的数据包。

ret:返回值,如果等于 AVERROR(EAGAIN) 或 AVERROR_EOF 表示没有更多数据包可接收。

av_interleaved_write_frame:将数据包写入文件。

av_packet_unref:释放数据包。

注意在写入的时候,调整时间戳

dart 复制代码
while (avcodec_receive_packet(codec_context_, &pkt) == 0) {
        av_log(NULL, AV_LOG_DEBUG, "写入的数据的地址: %p, 大小: %d\n", static_cast<void*>(pkt.data), pkt.size);
        // 重新调整时间戳
        av_packet_rescale_ts(&pkt, codec_context_->time_base, format_context_->streams[0]->time_base);
        pkt.stream_index = 0;

        ret = av_interleaved_write_frame(format_context_, &pkt);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "写入数据包时出错\n");
            av_packet_unref(&pkt);
            return;
        }
        av_packet_unref(&pkt);
    }

以下是完整的代码片段:

dart 复制代码
void captureFrame() {
    QPixmap originalPixmap = QGuiApplication::primaryScreen()->grabWindow(0);
    QImage image = originalPixmap.toImage().convertToFormat(QImage::Format_RGB888);

    uint8_t *data[1] = { image.bits() };
    int linesize[1] = { static_cast<int>(image.bytesPerLine()) };
    sws_scale(swsContext, data, linesize, 0, codecContext->height, frame->data, frame->linesize);

    frame->pts = frameCounter++;

    int ret = avcodec_send_frame(codecContext, frame);
    if (ret < 0) {
        char errbuf[128];
        av_strerror(ret, errbuf, sizeof(errbuf));
        qDebug() << "发送帧到编码器时出错: " << errbuf;
        return;
    }

    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = nullptr;
    pkt.size = 0;

    while (ret >= 0) {
        ret = avcodec_receive_packet(codecContext, &pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if (ret < 0) {
            char errbuf[128];
            av_strerror(ret, errbuf, sizeof(errbuf));
            qDebug() << "接收编码后的数据包时出错: " << errbuf;
            break;
        }

        av_interleaved_write_frame(formatContext, &pkt);
        av_packet_unref(&pkt);
    }
}

4. 释放资源 在录制结束时,释放所有分配的资源。

释放资源ffmpeg提供有对应的释放的方法 以下是如何释放每种资源的具体方法: 刷新缓冲区中的每一帧数据,确保每一帧都写入输出文件当中。

dart 复制代码
*avcodec_send_frame(codec_context_, nullptr); // 传递空帧以刷新编码器
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = nullptr;
    pkt.size = 0;
    while (avcodec_receive_packet(codec_context_, &pkt) == 0) {
        av_log(NULL, AV_LOG_DEBUG, "Flushing packet with data address: %p, size: %d\n", static_cast<void*>(pkt.data), pkt.size);
        av_packet_rescale_ts(&pkt, codec_context_->time_base, format_context_->streams[0]->time_base);
        pkt.stream_index = 0;
        av_interleaved_write_frame(format_context_, &pkt);
        av_packet_unref(&pkt);
    }
1. 释放 AVCodecContext
if (codecContext) {
    avcodec_free_context(&codecContext);
}
2. 释放 AVFrame
if (frame) {
    av_frame_free(&frame);
}
3. 释放 SwsContext
if (swsContext) {
    sws_freeContext(swsContext);
}
4. 释放 AVFormatContext
if (formatContext) {
    avformat_close_input(&formatContext);
    avformat_free_context(formatContext);
}
5. 释放 AVPacket
AVPacket packet;
av_init_packet(&packet);
// 使用 packet
av_packet_unref(&packet);
6. 释放 AVCodecParameters
if (codecParameters) {
    avcodec_parameters_free(&codecParameters);
}
7. 释放 AVIOContext
if (avioContext) {
    avio_context_free(&avioContext);
}
8. 释放 AVFilterGraph
if (filterGraph) {
    avfilter_graph_free(&filterGraph);
}
9. 释放 AVFilterContext
if (filterContext) {
    avfilter_free(filterContext);
}
10. 释放 AVFrame 中的缓冲区
if (frame) {
    av_frame_unref(frame);
}*
复制代码
以下是本功能的释放部分:

av_log(NULL, AV_LOG_DEBUG, "开始刷新编码器\n");

  

```dart
  avcodec_send_frame(codec_context_, nullptr); // 传递空帧以刷新编码器
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = nullptr;
    pkt.size = 0;
    while (avcodec_receive_packet(codec_context_, &pkt) == 0) {
        av_log(NULL, AV_LOG_DEBUG, "Flushing packet with data address: %p, size: %d\n", static_cast<void*>(pkt.data), pkt.size);

        av_packet_rescale_ts(&pkt, codec_context_->time_base, format_context_->streams[0]->time_base);
        pkt.stream_index = 0;

        av_interleaved_write_frame(format_context_, &pkt);
        av_packet_unref(&pkt);
    }

    if (format_context_) {
        av_log(NULL, AV_LOG_DEBUG, "写入文件尾部\n");
        av_write_trailer(format_context_);
        if (!(format_context_->oformat->flags & AVFMT_NOFILE)) {
            av_log(NULL, AV_LOG_DEBUG, "关闭输出文件\n");
            avio_closep(&format_context_->pb);
        }
        avformat_free_context(format_context_);
        format_context_ = nullptr;
    }

    if (codec_context_) {
        av_log(NULL, AV_LOG_DEBUG, "释放编码器上下文\n");
        avcodec_free_context(&codec_context_);
        codec_context_ = nullptr;
    }

    if (frame_) {
        av_log(NULL, AV_LOG_DEBUG, "释放视频帧\n");
        av_frame_free(&frame_);
        frame_ = nullptr;
    }

    if (sws_context_) {
        av_log(NULL, AV_LOG_DEBUG, "释放SWS上下文\n");
        sws_freeContext(sws_context_);
        sws_context_ = nullptr;
    }
}

5.自定义I/O上下文

为什么需要同时用 AVFormatContext 和 AVIOContext

虽然 AVFormatContext 和 AVIOContext 都涉及到数据处理,但它们的职责不同,且在数据处理流程中互补:

AVFormatContext:管理文件的格式和流信息。它负责描述文件的结构,包括音频、视频、字幕等流的元数据。

AVIOContext:处理实际的数据读写操作。它负责将数据从源读取或写入目标。 在实际应用中,AVFormatContext 需要一个

AVIOContext 来执行实际的 I/O 操作。

例如,当你调用 avformat_write_header 或 av_write_frame 等函数时,AVFormatContext 会使用关联的 AVIOContext 来执行实际的写操作。

为了确保写入输出文件之后,文件是正常可以打开的,除了写入输出上下文,我们还需要去自定义一个I/O上下文来控制我们的输入:
在使用 FFmpeg 进行多媒体处理时,通常会涉及到数据的输入和输出操作。FFmpeg 提供了 AVIOContext 结构体,用于处理这些 I/O 操作。默认情况下,FFmpeg 会使用标准的文件 I/O 函数来读取和写入数据。然而,在某些情况下,你可能需要自定义这些 I/O 操作,例如:

特殊的文件格式:你可能需要处理一些特殊的文件格式或协议,这些格式或协议无法通过标准的文件 I/O 函数来处理。

内存缓冲区:你可能希望将数据写入内存缓冲区而不是文件,或者从内存缓冲区读取数据。

网络流:你可能需要处理网络流,而不是本地文件。

自定义逻辑:你可能需要在读写数据时添加一些自定义的逻辑,例如加密、解密、压缩或解压缩。

虽然我们已经将数据写入了输出上下文,但可能需要自定义 I/O 上下文来实现更复杂或特定的功能。以下是一些可能的原因:

  1. 更灵活的控制 通过自定义 AVIOContext,你可以完全控制数据的读写过程。例如,你可以在写入数据之前对其进行处理,或者在读取数据之后对其进行处理。
  2. 处理非标准数据源或目标 如果你的数据源或目标不是标准的文件,而是内存缓冲区、网络流或其他非标准的数据源或目标,自定义 AVIOContext 是必要的。
  3. 性能优化 在某些情况下,自定义 I/O 上下文可以带来性能上的优化。例如,你可以使用更高效的缓冲区管理策略,减少 I/O 操作的次数,从而提高性能。
  4. 兼容性 某些平台或环境可能不支持标准的文件 I/O 操作,通过自定义 AVIOContext,你可以实现跨平台的 I/O 操作。

一般我们可以使用hls流媒体传输协议,HLS是一种流媒体协议,通常用于视频流媒体。

//样例

dart 复制代码
int main() {
    // 注册所有的编解码器和格式
    av_register_all();

    // 输出文件名
    const char *output_filename = "output.m3u8";
    const char *ts_name = "segment_%03d.ts";

    // 分配输出媒体上下文
    AVFormatContext *oc = NULL;
    avformat_alloc_output_context2(&oc, NULL, "hls", output_filename);
    if (!oc) {
        fprintf(stderr, "Could not allocate output context\n");
        return -1;
    }

    // 打开输出文件
    if (!(oc->oformat->flags & AVFMT_NOFILE)) {
        int ret = avio_open(&oc->pb, output_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            char errbuf[128];
            av_strerror(ret, errbuf, sizeof(errbuf));
            fprintf(stderr, "Could not open output file: %s\n", errbuf);
            return -1;
        }
    }

    // 设置HLS相关的选项
    AVDictionary *opt = NULL;
    av_dict_set(&opt, "hls_flags", "append_list", 0);
    av_dict_set(&opt, "hls_time", "10", 0);
    av_dict_set(&opt, "hls_list_size", "0", 0);
    av_dict_set(&opt, "hls_segment_filename", ts_name, 0);

    // 写文件头
    int ret = avformat_write_header(oc, &opt);
    av_dict_free(&opt);
    if (ret < 0) {
        char errbuf[128];
        av_strerror(ret, errbuf, sizeof(errbuf));
        fprintf(stderr, "Error occurred when opening output file: %s\n", errbuf);
        return -1;
    }

    // 释放资源
    if (!(oc->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&oc->pb);
    }
    avformat_free_context(oc);

    return 0;
}

6.对于ACC编码器注意事项

在FFmpeg中,某些音频编解码器(例如AAC)具有固定的帧大小(样本数),这通常是由编解码器的标准和设计决定的。即使你尝试手动设置帧大小,编解码器可能会忽略你的设置并使用其默认值。

确认编解码器的帧大小

在打开编解码器之后,你可以检查 AVCodecContext 的 frame_size 字段,以确认实际使用的帧大小。

要确认编解码器是否支持某些特定的设置(例如可变帧大小),你可以查看编解码器的能力标志(capabilities)。FFmpeg 提供了一些能力标志来描述编解码器的特性和支持的功能。

检查编解码器的能力标志

AVCodec 结构体包含一个 capabilities 字段,它是一个位掩码,表示编解码器支持的功能。你可以使用这些标志来检查编解码器是否支持特定的功能。

常见的能力标志

AV_CODEC_CAP_VARIABLE_FRAME_SIZE:表示编解码器支持可变帧大小。

AV_CODEC_CAP_DELAY:表示编解码器可能会在接收到所有输入数据后才输出数据。

AV_CODEC_CAP_FRAME_THREADS:表示编解码器支持帧级多线程。

// 检查编解码器是否支持可变帧大小

if (codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) {

printf("编解码器支持可变帧大小\n");

} else {

printf("编解码器不支持可变帧大小\n");

}

在FFmpeg中,支持可变帧大小的音频编码器相对较少,但确实存在一些支持可变帧大小的编码器。一个典型的例子是Opus编码器。Opus是一种高效的音频编解码器,广泛用于实时通信和流媒体应用。

使用Opus编码器设置可变帧大小

样例:

int main() {

// 注册所有编解码器

avcodec_register_all();

复制代码
// 查找Opus编码器
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_OPUS);
if (!codec) {
    fprintf(stderr, "找不到Opus编码器\n");
    return -1;
}

// 分配编解码器上下文
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
    fprintf(stderr, "分配编解码器上下文失败\n");
    return -1;
}

// 设置编码参数
codec_ctx->bit_rate = 64000;
codec_ctx->sample_rate = 48000;
codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);
codec_ctx->sample_fmt = codec->sample_fmts[0]; // 使用编码器支持的第一个样本格式

// 检查编解码器是否支持可变帧大小
if (codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) {
    printf("Opus编码器支持可变帧大小\n");
    codec_ctx->frame_size = 960; // 例如,设置为960个样本
} else {
    printf("Opus编码器不支持可变帧大小\n");
    codec_ctx->frame_size = 960; // 使用默认的帧大小
}

// 打开编码器
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
    fprintf(stderr, "打开编码器失败\n");
    avcodec_free_context(&codec_ctx);
    return -1;
}

// 检查实际的帧大小
printf("实际的帧大小:%d\n", codec_ctx->frame_size);

// 释放编解码器上下文
avcodec_free_context(&codec_ctx);

return 0;

}

经过测试,基本上我们是无法去设定编码器的样本数,这通常取决于编码器自己设定的样本数,因此在设计帧率以及样本数,最好按照编码器来。

ACC 1024 OPUS 960

相关推荐
开开心心就好12 小时前
音频降噪剪辑工具,音量裁剪一键处理
运维·服务器·前端·spring·ocr·音视频·1024程序员节
zslefour15 小时前
使用ffmpeg遍历获取音频文件相关信息
ffmpeg
AI视频工具指南15 小时前
AI视频的声音和画面怎么配合?先分清谁在传递信息
人工智能·aigc·音视频
辰辰观影17 小时前
无广告播放器推荐来啦
音视频
TrueDei17 小时前
GStreamer 使用 valve 分别控制音视频流的暂停与恢复
音视频
Android系统攻城狮18 小时前
Linux FFmpeg深度解析之avcodec_parameters_from_context调用流程与实战(十六)
linux·运维·ffmpeg·音视频进阶·ffmpeg音视频·车载音视频进阶
一条破秋裤19 小时前
基于 Hi3516CV610 的立定跳远视频测量系统调研与实施方案
arm开发·音视频
Android系统攻城狮19 小时前
Linux FFmpeg深度解析之avcodec_alloc_context3调用流程与实战(十四)
linux·运维·ffmpeg·车载音视频·音视频进阶·ffmpeg音视频进阶
2601_9668274519 小时前
视频里的特效字幕怎么翻译?外挂字幕与AI字幕一键处理教程
音视频
菜鸟小芯20 小时前
【Quectel Pi M2 实战】Day3 USB 摄像头图像采集:拍照、录像与踩坑记录(V4L2 + FFmpeg)
ffmpeg·嵌入式