当前位置: 首页 > news >正文

ffmpeg录制视频功能

本文目录

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

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库。
*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));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:释放数据包。
注意在写入的时候,调整时间戳

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);}

以下是完整的代码片段:

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

*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");```dartavcodec_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是一种流媒体协议,通常用于视频流媒体。
//样例

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

相关文章:

ffmpeg录制视频功能

本文目录 1.环境配置2.ffmpeg编解码的主要逻辑&#xff1a;3. 捕获屏幕帧与写入输出文件4. 释放资源 在录制结束时&#xff0c;释放所有分配的资源。5.自定义I/O上下文6.对于ACC编码器注意事项 1.环境配置 下载并安装FFmpeg库 在Windows上 从FFmpeg官方网站下载预编译的FFmpeg…...

【LeetCode】每日一题 2024_10_1 最低票价(记忆化搜索/DP)

前言 每天和你一起刷 LeetCode 每日一题~ 大家国庆节快乐呀~ LeetCode 启动&#xff01; 题目&#xff1a;最低票价 代码与解题思路 今天这道题是经典动态规划&#xff0c;我们定义 dfs(i) 表示从第 1 天到 第 i 天的最小花费&#xff0c;然后使用祖传的&#xff1a;从记忆…...

[C++] 小游戏 征伐 SLG DNF 0.0.1 版本 zty出品

目录 先赞后看 养成习惯 War and Expedition SLG DNF 0.0.1 version 讲人话就是 图标解释&#xff1a; 绿色代表空地&#xff0c;可通过&#xff0c;对应数值 0 蓝色“~ ”为水&#xff0c;不可通过&#xff0c;对应数值 1 棕色“”为桥梁&#xff0c;可通过&#xff0…...

黑马头条day7-app端文章搜索

今天的内容也只是跑了一下 对于具体的实现掌握的很差 仔细看 es 在微服务学的es使用基本忘光了 这里用起来一点都熟悉 重学&#xff01;&#xff01;&#xff01; kafka异步 文章自动构建索引的时候用到了‘’ mongoDB 用来存储用户的搜索记录 遗忘&#xff08;拦截器 j…...

嵌入式必懂微控制器选型:STM32、ESP32、AVR与PIC的比较分析

目录 1 微控制器基础概述 1.1 微控制器基本概念 1.2 工作原理及架构 1.3 STM32、ESP32、AVR和PIC简介 2 微控制器性能比较分析 2.1 性能比较 2.2 功耗比较 2.3 功耗分析 2.4 外设接口对比 3 应用场景与选择策略 3.1 物联网应用场景 3.2 工业控制场景 3.3 智能家居场…...

Python selenium库学习使用实操二

系列文章目录 Python selenium库学习使用实操 文章目录 系列文章目录前言一、模拟登录二、表单录入 前言 在上一篇文章中&#xff0c;我们完成Selenium环境的搭建&#xff0c;和简单的自动化。今天继续深入学习。今天的目标是完成模拟登录&#xff0c;和表单录入。 一、模拟登…...

基于Hive和Hadoop的电信流量分析系统

本项目是一个基于大数据技术的电信流量分析系统&#xff0c;旨在为用户提供全面的通信数据和深入的流量使用分析。系统采用 Hadoop 平台进行大规模数据存储和处理&#xff0c;利用 MapReduce 进行数据分析和处理&#xff0c;通过 Sqoop 实现数据的导入导出&#xff0c;以 Spark…...

访问docker容器中服务的接口,报错提示net::ERR_CONNECTION_REFUSED

背景 使用httpclient和前端调用docker容器中部署的springboot服务接口,一直连接不上。 报错信息 AxiosError {message: Network Error, name: AxiosError, code: ERR_NETWORK, config: {…}, request: XMLHttpRequest, …} sys.ts:28 POST http://172.33.28.179:8181/sy…...

【mysql相关总结】

mysql相关总结 数据库小的表,全表扫描效率更高&#xff0c;不用建索引。 索引的类型 1.普通索引&#xff1a;基本的索引&#xff0c;没有任何约束限制 2.唯一索引&#xff1a;类似普通索引,有唯一约束性 3.主键索引&#xff1a;特殊的唯一索引,不允许有空值 4.组合索引&#xf…...

uniapp 微信小程序 微信支付

本章的内容我尽量描述的细致一些&#xff0c;哪里看不懂给我评论就可以&#xff0c;我看到进行回复 微信支付大致分为4步&#xff0c;具体看后端设计 1. 获取code 2. 根据code获取openid 3. 根据openid&#xff0c;以及部分订单相关数据&#xff0c;生成prepayId (预支付交易会…...

CSS 效果:实现动态展示双箭头

最近写了一段 CSS 样式&#xff0c;虽然不难&#xff0c;但实现过程比较繁琐。这个效果结合了两个箭头&#xff0c;一个突出&#xff0c;一个内缩&#xff0c;非常适合用于步骤导航或选项卡切换等场景。样式不仅仅是静态的&#xff0c;还可以通过点击 click 或者 hover 事件&am…...

Linux 创建开发用的账户

在Linux系统中&#xff0c;创建一个用于开发的用户账户通常涉及到添加用户、设置密码以及配置适当的权限和环境。这里将详细介绍如何在Linux系统中创建一个新的开发用户账户&#xff0c;包括为其配置sudo权限&#xff0c;使其能够执行需要管理员权限的命令。 步骤 1: 创建用户…...

检查一个CentOS服务器的配置的常用命令

在CentOS系统中&#xff0c;查看服务器配置的常用命令非常丰富&#xff0c;这些命令可以帮助用户快速了解服务器的硬件信息、系统状态以及网络配置等。以下是一些常用的命令及其简要说明&#xff1a; 1. 查看CPU信息 (1) cat /proc/cpuinfo&#xff1a;显示CPU的详细信息&…...

Redis 简单的消息队列

使用redis 进行简单的队列很容易&#xff0c;不需要使用较为复杂的MQ队列&#xff0c;直接使用redis 进行&#xff0c;不过唯一不足的需要自己构造生产者消费者&#xff0c;这里使用while True的方法进行消费者操作 目录 介绍数据类型StringHash 重要命令消息队列 介绍 key-v…...

C++:继承和多态,自定义封装栈,队列

1.栈&#xff1a; stack.cpp #include "stack.h"Stack::Stack():top(nullptr),len(0){} //析构函数 Stack::~Stack() {while(!empty()){pop();} }bool Stack::empty() //判断栈是否为空 {return topnullptr; }int Stack::size()//获取栈的大小 {return len; } //压…...

Python多个set中的交集

Python多个set中的交集 在 Python 中&#xff0c;集合&#xff08;set&#xff09;是一种非常有用的数据结构&#xff0c;它可以存储唯一的元素&#xff0c;并提供了高效的数学集合操作&#xff0c;包括求交集、并集和差集等。本文将重点介绍如何通过多重集合求交集&#xff0…...

百度百科 X-Bk-Token 算法还原

声明 本文章中所有内容仅供学习交流,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请私信我立即删除! 文章目录 声明案例地址参数分析X-Bk-Token算法追踪X-Bk-Token后缀算法还原c 值跟踪与算法还原往期逆向文章推荐最近太忙了,博客摆烂了好…...

RUST语言的初印象-从一个模拟登陆谈起-slint+reqwest+aes

本文就一个做了三四天的小程序讲第一次学用RUST的感受&#xff0c;内附代码。 了角语言 从一些渠道听说了R&#xff0c;这个字母挺魔性&#xff0c;那个文章说C和R的团体已经上升到了宗教崇拜的高度&#xff0c;然后&#xff0c;我觉得必 有过人之处&#xff0c;大约10年没碰…...

HBase批量写入优化

HBase批量写入性能优化 对于HBase的批量写入性能优化&#xff0c;可以考虑以下几点&#xff1a; 1.批量写入操作&#xff1a;使用HBasef的批量写入操作可以显著提高性能。将多个写入操作放在一个批次中一起提交。这样可以减少网络通信开销和减少多次写入操作的开销。方法不限。…...

江协科技STM32学习- P19 TIM编码器接口

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…...

3403. 从盒子中找出字典序最大的字符串 I

3403. 从盒子中找出字典序最大的字符串 I 题目链接&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 代码如下&#xff1a; class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

快刀集(1): 一刀斩断视频片头广告

一刀流&#xff1a;用一个简单脚本&#xff0c;秒杀视频片头广告&#xff0c;还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农&#xff0c;平时写代码之余看看电影、补补片&#xff0c;是再正常不过的事。 电影嘛&#xff0c;要沉浸&#xff0c;…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...

xmind转换为markdown

文章目录 解锁思维导图新姿势&#xff1a;将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件&#xff08;ZIP处理&#xff09;2.解析JSON数据结构3&#xff1a;递归转换树形结构4&#xff1a;Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...

Spring Boot + MyBatis 集成支付宝支付流程

Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例&#xff08;电脑网站支付&#xff09; 1. 添加依赖 <!…...

Pandas 可视化集成:数据科学家的高效绘图指南

为什么选择 Pandas 进行数据可视化&#xff1f; 在数据科学和分析领域&#xff0c;可视化是理解数据、发现模式和传达见解的关键步骤。Python 生态系统提供了多种可视化工具&#xff0c;如 Matplotlib、Seaborn、Plotly 等&#xff0c;但 Pandas 内置的可视化功能因其与数据结…...

【threejs】每天一个小案例讲解:创建基本的3D场景

代码仓 GitHub - TiffanyHoo/three_practices: Learning three.js together! 可自行clone&#xff0c;无需安装依赖&#xff0c;直接liver-server运行/直接打开chapter01中的html文件 运行效果图 知识要点 核心三要素 场景&#xff08;Scene&#xff09; 使用 THREE.Scene(…...

零基础在实践中学习网络安全-皮卡丘靶场(第十一期-目录遍历模块)

经过前面几期的内容我们学习了很多网络安全的知识&#xff0c;而这期内容就涉及到了前面的第六期-RCE模块&#xff0c;第七期-File inclusion模块&#xff0c;第八期-Unsafe Filedownload模块。 什么是"遍历"呢&#xff1a;对学过一些开发语言的朋友来说应该知道&…...