You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 03:32:36