当前位置: 首页 > 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;​…...

文件上传、重定向、Gin路由

文件上传 单个文件上传 index.html 文件上传前端页面代码&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head><title>index</title> </head> <body> <form action"/upload" method"post"…...

躺平成长:微信小程序运营日记第二天

在进行属于生活的开源之后&#xff0c;自己更加感受到自己存在的渺茫&#xff0c;同时更加开始深刻领会&#xff0c;开源的重要性&#xff0c;在开源&#xff0c;开放&#xff0c;创造&#xff0c;再创新的思维模式下&#xff0c;不发布八部金刚功相关的训练视频&#xff0c;自…...

三分钟速览:Node.js 版本差异与关键特性解析

Node.js 是一个广泛使用的 JavaScript 运行时环境&#xff0c;允许开发者在服务器端运行 JavaScript 代码。随着技术的发展&#xff0c;Node.js 不断推出新版本&#xff0c;引入新特性和改进。了解不同版本之间的差异对于开发者来说至关重要。以下是一个快速指南&#xff0c;帮…...

git创建新分支

git创建新分支 1.先在gitLab上New branch. 2.本地右键git小乌 - /切换/检出-创建新分支&#xff0c;分支名称和上一步创建的一样。 最后记得改个文件提交下&#xff0c;看看gitLab上是否提交成功。...

Chip-seq数据分析处理流程

一、处理过程 要处理 SRR14879780 的 ChIP-seq 数据并进行基序分析&#xff08;包括比对到参考基因组 hg38.fasta 和峰值调用&#xff09;&#xff0c;你可以按照以下步骤操作&#xff0c;并使用相应的代码。每个步骤会涉及一些常用的生物信息学工具&#xff0c;如 FastQC、Tr…...

spring boot3.2.x与spring boot2.7.x对比

Spring Boot 3.2.x 相比 Spring Boot 2.7.x 带来了许多重要的变化、新特性以及性能改进。这些新功能不仅提升了开发者的效率&#xff0c;还优化了应用的性能和安全性。以下是两者的主要差异、优势以及使用说明&#xff1a; 1. JDK 17 支持 Spring Boot 2.7.x 支持 JDK 8 至 J…...

Vue2(十三):路由

一、路由的简介 vue-rooter&#xff1a;是vue的一个插件库&#xff0c;专门用来实现SPA应用 1.对SPA应用的理解 1、单页 Web 应用&#xff08;single page web application&#xff0c;SPA&#xff09;。 2、整个应用只有一个完整的页面 index.html。 3、点击页面中的导航链…...

Java并发:互斥锁,读写锁,公平锁,Condition,StampedLock

阅读本文之前可以看一看 Java 多线程基础&#xff1a; Java&#xff1a;多线程&#xff08;进程线程&#xff0c;线程状态&#xff0c;创建线程&#xff0c;线程操作&#xff09; Java&#xff1a;多线程&#xff08;同步死锁&#xff0c;锁&原子变量&#xff0c;线程通信&…...

在 Linux 中,要让某一个线程或进程排他性地独占一个 CPU

文章目录 1. CPU 亲和性(CPU Affinity)2. 中断隔离(IRQ Isolation)3. 系统 tickless 模式(NoHZ Mode)4. 实时调度策略5. CPU 隔离(CPU Isolation)和 Full CPU Isolation实现最低的延迟抖动在 Linux 中,要让某一个线程 排他性地独占一个 CPU,并且进一步隔离中断(包括…...

滚雪球学MySQL[7.3讲]:数据库日志与审计详解:从错误日志到审计日志的配置与使用

全文目录&#xff1a; 前言7.3 日志与审计1. 日志类型与配置1.1 错误日志&#xff08;Error Log&#xff09;配置错误日志使用场景案例演示 1.2 慢查询日志&#xff08;Slow Query Log&#xff09;配置慢查询日志使用场景案例演示 1.3 查询日志&#xff08;General Query Log&a…...

黄石市城市建设档案馆网站/网络营销的工作内容包括哪些

增 push   在数组的末尾添加一个或多个元素&#xff0c;并返回新的长度。 array.push(1,2,3.........) unshift  在数组的开头添加一个或多个元素&#xff0c;并返回新的长度。 array.unshift(1,2,3......) splice   在制定位置添加一个活多个元素&#xff0c;splice(s…...

政协网站建设功能/百度关键词搜索排行

POST不同提交方式对应的Content-Type,及java服务器接收参数方式注:本博客参考了网上的文章结合自己工作总结后所写,主要用于记录自己工作所得,如有错误请批评指正.简介:Content-Type(MediaType)&#xff0c;即是Internet Media Type&#xff0c;互联网媒体类型&#xff1b;也叫…...

太原有哪些做网站的公司/百度广告电话号码

阅读本文大概需要 30 分钟 。总是有读者说学不会数据库&#xff0c;今天就整理了一些基础&#xff0c;后续我可能会整理一些其他数据库的笔记分享出来&#xff0c;今天这篇认真看了基本日常操作是没一点问题的&#xff0c;有些非原创&#xff0c;是我早期学习的时候参考着博客用…...

用vue做pc端网站好吗/线上营销活动案例

经常有人问到oracle中的Where子句的条件书写顺序是否对SQL性能有影响&#xff0c;我的直觉是没有影响&#xff0c;因为如果这个顺序有影响&#xff0c;Oracle应该早就能够做到自动优化&#xff0c;但一直没有关于这方面的确凿证据。在网上查到的文章&#xff0c;一般认为在RBO优…...

网站上的办公网站怎么做/全网营销培训

HTML5中提供了跨域加载数据的方法&#xff0c;让我们得以从JSONP或者Flash中介等各种绕行方案中解脱出来&#xff0c;更加顺畅地与服务器交流。另一方面&#xff0c;因为PHP是最好的语言……所以在它与Node.js之间&#xff0c;我选择前者作为后端语言开发内容服务。这篇文章记录…...

免费建站平台哪个靠谱/web网页制作成品免费

转贴:http://bbs.langtech.org.cn/frame.php?frameonyes&refererhttp%3A//bbs.langtech.org.cn/forumdisplay.php%3Ffid%3D20感谢原创作者.ICDM2006-介绍&#xff1a;数据挖掘领域最有影响力的18个算法ICDM是数据挖掘领域的顶级会议之一&#xff0c;在数据挖掘理论与应用领…...