ffmpeg录制视频功能

本文目录

  • 1.环境配置
  • 2.ffmpeg编解码的主要逻辑:
  • 3. 捕获屏幕帧与写入输出文件
  • 4. 释放资源 在录制结束时,释放所有分配的资源。
  • 5.自定义I/O上下文
  • 6.对于ACC编码器注意事项

1.环境配置

下载并安装FFmpeg库 在Windows上 从FFmpeg官方网站下载预编译的FFmpeg库: 解压下载的文件,并记下解压后的路径。
FFmpeg下载(windows版本)_libijkffmpeg.so 32位下载-****博客
记住一定下载是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库。
*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. 设置编解码器 —参数设置

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

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

主要是对与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. 创建输出上下文

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

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

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。
3. 创建视频流

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

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

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:设置像素格式。
6. 设置全局头部标志

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 函数打开编码器。
两者使用基本上一样,但注意及时两个类型一样,但编码器与解码器使用的是独立的上下文,注意这一点!!
以下是对于编码器的编写流程:

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. 打开编码器
if (avcodec_open2(codecContext, codec, nullptr) < 0) {
    qDebug() << "无法打开编解码器";
    return;
}

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

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

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

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

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

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

avformat_write_header 函数将文件头写入输出文件。如果成功,返回0;如果失败,返回负值。
11. 分配视频帧

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。然后设置帧的格式、宽度和高度。
12. 分配视频帧数据

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

av_frame_get_buffer 函数为视频帧分配数据缓冲区。如果成功,返回0;如果失败,返回负值。
13. 初始化像素格式转换上下文

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 分量的分辨率是原图像的一半(水平和垂直方向上各减少一半))。

完整代码:

    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. 捕获屏幕
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。
2. 准备像素格式转换
uint8_t *data[1] = { image.bits() };
int linesize[1] = { static_cast(image.bytesPerLine()) };
功能:准备源图像数据和行字节数,以便进行像素格式转换。
解释:
data[1]:指向源图像数据的指针数组。
linesize[1]:源图像每行的字节数。
3. 像素格式转换

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:目标图像数据和行字节数。
4. 记录帧的个数
frame->pts = frameCounter++;
解释:frameCounter 是一个递增的计数器。
5. 发送帧到编码器

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:获取详细的错误信息。
6. 初始化数据包

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

av_init_packet:初始化数据包。
pkt.data 和 pkt.size:设置数据包的数据指针和大小。
7. 接收编码后的数据包

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
上一篇:彻底连接pip工具


下一篇:Paddlets时间序列集成模型回测实战:MLPRegressor、NHiTSModel与RNNBlockRegressor