公司网站链接怎么弄,怎么制作软件app教程,腾讯企业邮箱入口网址,电子商务网站建设是什么意思本文目录 1.环境配置2.ffmpeg编解码的主要逻辑#xff1a;3. 捕获屏幕帧与写入输出文件4. 释放资源 在录制结束时#xff0c;释放所有分配的资源。5.自定义I/O上下文6.对于ACC编码器注意事项 1.环境配置
下载并安装FFmpeg库 在Windows上 从FFmpeg官方网站下载预编译的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编解码的主要逻辑
初始化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 三个初始化函数的详细解释 av_register_all() 含义 av_register_all 函数用于注册所有的编解码器、文件格式和协议。它是FFmpeg库的初始化函数之一确保在使用FFmpeg的任何功能之前所有的编解码器和格式都已注册。 2.av_register_all(); 作用 注册所有的编解码器如H.264、AAC等。 注册所有的文件格式如MP4、AVI等。 注册所有的协议如HTTP、RTMP等。 avcodec_register_all() avcodec_register_all 函数用于注册所有的编解码器。虽然 av_register_all 已经包含了这个步骤但在某些情况下你可能只需要注册编解码器而不需要注册其他组件。 avcodec_register_all(); 主要作用注册所有的编解码器如H.264、AAC等。 avformat_network_init() avformat_network_init 函数用于初始化网络组件。它在使用任何网络协议如HTTP、RTMP等之前调用以确保网络功能已正确初始化。 主要作用初始化网络组件确保网络协议如HTTP、RTMP等可以正常使用。 一般我们在构造进行库的初始化之后我们回去在一个初始化接口中去进行ffmpeg的准备工作 第一个准备工作就是去设置编解码器 设置编解码器 —参数设置
// 设置选项
AVDictionary *options nullptr;
av_dict_set(options, rtbufsize, 100M, 0); // 设置实时缓冲区大小
av_dict_set(options, framerate, 30, 0); // 设置帧率为30 FPS//在打开输入文件打开编码器写入输出文件时可以设置这些参数通过 AVDictionary 设置选项来配置输入设备的参数例如帧率缓冲区大小防止在写入时崩溃。 初始化
主要是对与FFmpeg的以下成员进行设置初始化
AVFormatContext格式上下文用于管理输出文件的格式信息AVCodecContextFFmpeg的编解码器上下文用于管理视频编码器的参数和状态
AVStream FFmpeg的视频流用于表示输出文件中的视频流
SwsContext FFmpeg的图像转换上下文用于将捕获的图像转换为编码器需要的格式AVFrame FFmpeg的视频帧用于存储要编码的视频数据
int frame帧计数器用于跟踪已捕获的帧数常见的资源设置有如下这些 AVCodecContext 资源AVCodecContext 是编码器或解码器的上下文包含了编解码器的所有状态信息和参数。 释放函数avcodec_free_context AVFrame 资源AVFrame 用于存储解码后的帧数据或编码前的帧数据。 释放函数av_frame_free SwsContext 资源SwsContext 是用于图像缩放和像素格式转换的上下文。 释放函数sws_freeContext AVFormatContext 资源AVFormatContext 是多媒体文件的上下文包含了文件格式、流信息等。 释放函数avformat_free_context AVPacket 资源AVPacket 用于存储编码后的数据包。 释放函数av_packet_unref AVCodecParameters 资源AVCodecParameters 用于存储编解码器的参数。 释放函数avcodec_parameters_free AVIOContext 资源AVIOContext 是用于输入输出操作的上下文。 释放函数avio_context_free AVFilterGraph 资源AVFilterGraph 是用于音视频过滤操作的图。 释放函数avfilter_graph_free AVFilterContext 资源AVFilterContext 是用于音视频过滤操作的上下文。 释放函数avfilter_free AVDictionary 资源AVDictionary 是用于存储键值对的字典常用于传递选项。 释放函数av_dict_free 设置编解码器的主要步骤: 创建输出上下文
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设置GOPGroup 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;
}打开编码器
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函数捕获屏幕帧并进行像素格式转换。具体步骤如下
捕获屏幕
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源图像数据和行字节数。–根据格式转化上下文转换对应的输入到输出---- 输入数据属性swsContextdata为图像数据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_castvoid*(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_castint(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_castvoid*(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_castvoid*(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 上下文来实现更复杂或特定的功能。以下是一些可能的原因 更灵活的控制 通过自定义 AVIOContext你可以完全控制数据的读写过程。例如你可以在写入数据之前对其进行处理或者在读取数据之后对其进行处理。处理非标准数据源或目标 如果你的数据源或目标不是标准的文件而是内存缓冲区、网络流或其他非标准的数据源或目标自定义 AVIOContext 是必要的。性能优化 在某些情况下自定义 I/O 上下文可以带来性能上的优化。例如你可以使用更高效的缓冲区管理策略减少 I/O 操作的次数从而提高性能。兼容性 某些平台或环境可能不支持标准的文件 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