如何使用FFmpeg将帧写入内存缓冲区而非文件?
将FFmpeg编码的视频帧存入内存缓冲区的实现方案
当然可以!FFmpeg提供了自定义IO上下文的机制,完美支持把编码后的帧写入内存缓冲区,而不是直接存到本地文件。我来一步步给你拆解具体怎么操作,包括需要用到的API和关键步骤:
核心思路
FFmpeg默认的IO是针对文件的,但它允许我们替换成自定义的读写逻辑——通过实现AVIOContext的回调函数,把原本要写入文件的数据包转存到我们自己管理的内存缓冲区里。
步骤1:定义内存缓冲区管理结构
首先我们需要一个结构体来跟踪内存缓冲区的状态,比如当前数据长度、缓冲区总大小、缓冲区指针:
typedef struct { uint8_t *buffer; // 内存缓冲区指针 size_t buffer_size; // 缓冲区总容量 size_t current_pos; // 当前写入的位置(已用长度) } MemoryBuffer;
步骤2:实现自定义IO回调函数
FFmpeg需要我们提供写回调和seek回调(如果不需要seek可以简化,但部分格式需要seek来写入头/尾信息):
写回调(将数据包写入内存)
这个函数负责把FFmpeg输出的编码数据拷贝到我们的内存缓冲区,还需要处理缓冲区扩容的逻辑:
static int write_packet(void *opaque, uint8_t *buf, int buf_size) { MemoryBuffer *mb = (MemoryBuffer *)opaque; // 如果当前缓冲区不够容纳新数据,进行扩容(这里用翻倍策略,你可以按需调整) if (mb->current_pos + buf_size > mb->buffer_size) { size_t new_size = mb->buffer_size * 2; if (new_size < mb->current_pos + buf_size) { new_size = mb->current_pos + buf_size; } uint8_t *new_buf = realloc(mb->buffer, new_size); if (!new_buf) return AVERROR(ENOMEM); // 返回内存不足错误 mb->buffer = new_buf; mb->buffer_size = new_size; } // 拷贝数据到缓冲区 memcpy(mb->buffer + mb->current_pos, buf, buf_size); mb->current_pos += buf_size; return buf_size; // 返回成功写入的字节数 }
Seek回调(调整内存缓冲区的读写位置)
这个函数处理FFmpeg的seek请求,比如写入文件尾时需要回到文件头更新信息:
static int64_t seek_packet(void *opaque, int64_t offset, int whence) { MemoryBuffer *mb = (MemoryBuffer *)opaque; int64_t new_pos = 0; switch (whence) { case SEEK_SET: new_pos = offset; break; case SEEK_CUR: new_pos = mb->current_pos + offset; break; case SEEK_END: new_pos = mb->buffer_size + offset; break; case AVSEEK_SIZE: return mb->buffer_size; // 返回缓冲区总大小 default: return AVERROR(EINVAL); // 不支持的seek类型 } // 检查位置是否合法 if (new_pos < 0 || new_pos > mb->buffer_size) { return AVERROR(EINVAL); } mb->current_pos = new_pos; return new_pos; }
步骤3:初始化FFmpeg的自定义IO上下文
接下来把我们的内存缓冲区和回调函数关联到FFmpeg的AVIOContext,再绑定到AVFormatContext:
// 初始化内存缓冲区(初始分配4KB,可按需调整) MemoryBuffer mb = {0}; mb.buffer_size = 4096; mb.buffer = malloc(mb.buffer_size); if (!mb.buffer) { // 处理内存分配失败 return -1; } // 创建AVIOContext:参数分别是缓冲区、缓冲区大小、是否可写、自定义数据、读回调、写回调、seek回调 AVIOContext *avio_ctx = avio_alloc_context( mb.buffer, mb.buffer_size, 1, // 1表示可写模式 &mb, // 传递给回调的自定义数据(就是我们的内存缓冲区结构) NULL, // 读回调,这里不需要,设为NULL write_packet, seek_packet ); if (!avio_ctx) { free(mb.buffer); return -1; } // 创建并配置AVFormatContext AVFormatContext *fmt_ctx = avformat_alloc_context(); fmt_ctx->pb = avio_ctx; // 绑定自定义IO上下文 fmt_ctx->flags |= AVFMT_FLAG_CUSTOM_IO; // 标记使用自定义IO // 设置输出格式(比如H.263或MPEG-4,按需调整) AVOutputFormat *fmt = av_guess_format("h263", NULL, NULL); // 或者"mpeg4" if (!fmt) { // 处理格式查找失败 avio_context_free(&avio_ctx); free(mb.buffer); return -1; } fmt_ctx->oformat = fmt;
步骤4:正常编码并写入内存
到这里,后续的编码流程和你之前写文件的逻辑几乎一致:添加流、配置编码器参数、打开编码器、写入头信息,然后用av_write_frame()写入帧——此时av_write_frame()会自动调用我们的写回调,把编码后的数据包存入内存缓冲区:
// 添加视频流(这里省略了编码器参数配置,比如像素格式、分辨率、码率等,和你之前的逻辑一致) AVStream *stream = avformat_new_stream(fmt_ctx, NULL); if (!stream) { // 错误处理 } // 配置stream->codecpar的参数(比如codec_id、width、height、pix_fmt等) // ... // 打开编码器(和你之前的逻辑一致) // ... // 写入文件头 if (avformat_write_header(fmt_ctx, NULL) < 0) { // 错误处理 } // 循环处理OpenCV采集的帧:转成AVFrame -> 编码成AVPacket -> 写入内存 AVPacket pkt = {0}; av_init_packet(&pkt); while (/* 还有帧需要处理 */) { // 1. 从OpenCV获取帧,转成FFmpeg需要的格式(比如BGR转YUV420P) // 2. 编码帧得到AVPacket // ... // 写入内存缓冲区(和写文件的调用完全一样!) int ret = av_write_frame(fmt_ctx, &pkt); if (ret < 0) { // 错误处理 } av_packet_unref(&pkt); } // 写入文件尾信息 av_write_trailer(fmt_ctx);
步骤5:清理资源并使用内存数据
编码完成后,记得正确释放资源,避免内存泄漏:
// 关键:先把AVIOContext的buffer设为NULL,防止FFmpeg自动释放我们的内存缓冲区 avio_ctx->buffer = NULL; // 释放AVIOContext avio_context_free(&avio_ctx); // 释放AVFormatContext avformat_free_context(fmt_ctx); // 现在mb.buffer里就是完整的编码后视频数据,mb.current_pos是数据的总长度 // 你可以用这些数据做任何事情:比如发送到网络、存储到其他介质、解析等 // 最后别忘了释放内存缓冲区 free(mb.buffer);
注意事项
- 内存扩容策略:上面的例子用了翻倍扩容,你可以根据自己的场景调整(比如固定大小、按增量扩容),避免频繁realloc影响性能。
- 线程安全:如果是多线程环境下操作内存缓冲区,记得给读写逻辑加锁,避免数据竞争。
- 像素格式转换:OpenCV采集的帧通常是BGR格式,而FFmpeg编码器大多需要YUV格式(比如YUV420P),记得用
sws_scale()完成转换。 - Seek必要性:如果你的输出格式不需要seek(比如某些流媒体格式),可以把seek回调返回
AVERROR(ENOSYS),但部分容器格式(比如MP4)需要seek来更新头信息,建议实现基本的seek逻辑。
内容的提问来源于stack exchange,提问作者Mohsen Ghahremani Manesh
相关产品推荐
相关产品推荐

