C++ FFmpeg实现将视频帧转换为JPG或PNG图片

山海之间
山海之间
发布时间:2026-10-15 | 4人浏览过
原创

在使用FFmpeg处理实时流或本地视频时,经常需要将解码后的画面以JPG或PNG格式保存到本地或上传服务器。这一过程核心在于利用FFmpeg的图像编码器对AVFrame数据进行重编码。本文将详细介绍在C++环境下,如何配置编码器上下文、执行像素格式转换,并最终提取图像数据的完整流程。

结果与目标

我们的目标是将输入的视频帧数据(通常为YUV格式)转换为内存中的JPEG或PNG二进制数据,并可进一步写入文件。整个过程复用FFmpeg的视频编解码API,关键在于正确选择图片编码器并处理像素格式差异。

准备条件

在进行开发前,需确保已集成FFmpeg C API相关头文件,并链接了libavcodec, libswscale, libavutil等库。核心数据结构为AVFrame(包含原始视频帧数据)和目标图片编码器ID(如AV_CODEC_ID_MJPEG对应JPG,AV_CODEC_ID_PNG对应PNG)。

真实操作顺序

第1步:查找图片编码器

首先需要获取目标图片格式的编码器句柄。例如,生成JPG图片需使用MJPEG编码器。

enum AVCodecID codecID = AV_CODEC_ID_MJPEG; // 或 AV_CODEC_ID_PNG
AVCodec* codec = avcodec_find_encoder(codecID);
if (!codec) {
    printf("Encoder not found\n");
    return;
}

第2步:构造编码器上下文

根据找到的编码器分配并初始化AVCodecContext。需设置分辨率、码率、帧率时间基数等参数。特别注意pix_fmt需设为编码器支持的像素格式,通常取编码器支持列表的第一个格式。

AVCodecContext* ctx = avcodec_alloc_context3(codec);
ctx->bit_rate = 3000000;
ctx->width = frame->width;
ctx->height = frame->height;
ctx->time_base.num = 1;
ctx->time_base.den = 25;
ctx->gop_size = 10;
ctx->max_b_frames = 0;
ctx->thread_count = 1;
ctx->pix_fmt = *codec->pix_fmts; // 选择编码器适配的像素格式

int ret = avcodec_open2(ctx, codec, NULL);
if (ret < 0) {
    printf("Failed to open encoder\n");
    return;
}

第3步:像素格式转换

若输入帧的像素格式(如AV_PIX_FMT_YUV420P)与编码器所需的像素格式不同,必须使用libswscale进行转换。通常图像编码器需要RGB格式数据。

AVFrame* rgbFrame = av_frame_alloc();
struct SwsContext* swsContext = sws_getContext(
    frame->width, frame->height, (enum AVPixelFormat)frame->format,
    frame->width, frame->height, ctx->pix_fmt, 
    SWS_BILINEAR, NULL, NULL, NULL
);

// 计算缓冲区大小并分配内存
int bufferSize = av_image_get_buffer_size(ctx->pix_fmt, frame->width, frame->height, 1);
uint8_t* buffer = (uint8_t*)av_malloc(bufferSize);

// 填充rgbFrame的数据指针
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, 
                     ctx->pix_fmt, frame->width, frame->height, 1);

// 执行转换
sws_scale(swsContext, frame->data, frame->linesize, 0, frame->height, 
          rgbFrame->data, rgbFrame->linesize);

rgbFrame->format = ctx->pix_fmt;
rgbFrame->width = ctx->width;
rgbFrame->height = ctx->height;

第4步:发送帧进行编码

将转换后的帧发送给编码器。如果无需转换,可直接发送原始帧(前提是格式匹配)。

// 假设已准备好rgbFrame
ret = avcodec_send_frame(ctx, rgbFrame); // 若无转换,可传frame

第5步:获取编码后的数据包

从编码器接收压缩后的数据包(AVPacket),其中包含实际的图片二进制数据。

AVPacket pkt;
av_init_packet(&pkt);
ret = avcodec_receive_packet(ctx, &pkt);

if (ret == 0 && pkt.size > 0) {
    // pkt.data 包含图片数据,pkt.size 为大小
    // 可将此数据保存文件或上传服务器
}
av_packet_unref(&pkt); // 记得解引用数据包

第6步:资源清理

操作完成后,务必按顺序释放所有申请的资源,防止内存泄漏。

if (swsContext) sws_freeContext(swsContext);
if (rgbFrame) {
    av_frame_unref(rgbFrame);
    av_frame_free(&rgbFrame);
}
if (buffer) av_free(buffer);
if (ctx) {
    avcodec_close(ctx);
    avcodec_free_context(&ctx);
}

完整代码封装与使用示例

为了方便复用,可以将上述逻辑封装为一个独立函数。该函数接受AVFrame、编码器ID以及外部提供的输出缓冲区,返回实际写入的图片大小。

核心转换函数

static int frameToImage(AVFrame* frame, enum AVCodecID codecID, uint8_t* outbuf, size_t outbufSize)
{
    int ret = 0;
    AVPacket pkt;
    AVCodec* codec;
    AVCodecContext* ctx = NULL;
    AVFrame* rgbFrame = NULL;
    uint8_t* buffer = NULL;
    struct SwsContext* swsContext = NULL;

    av_init_packet(&pkt);
    codec = avcodec_find_encoder(codecID);
    if (!codec || !codec->pix_fmts) {
        printf("Encoder not found or unsupported format\n");
        goto end;
    }

    ctx = avcodec_alloc_context3(codec);
    ctx->bit_rate = 3000000;
    ctx->width = frame->width;
    ctx->height = frame->height;
    ctx->time_base.num = 1;
    ctx->time_base.den = 25;
    ctx->gop_size = 10;
    ctx->max_b_frames = 0;
    ctx->thread_count = 1;
    ctx->pix_fmt = *codec->pix_fmts;

    if (avcodec_open2(ctx, codec, NULL) < 0) goto end;

    // 判断是否需要格式转换
    if (frame->format != ctx->pix_fmt) {
        rgbFrame = av_frame_alloc();
        if (!rgbFrame) goto end;

        swsContext = sws_getContext(frame->width, frame->height, (enum AVPixelFormat)frame->format,
                                    frame->width, frame->height, ctx->pix_fmt, 
                                    SWS_BILINEAR, NULL, NULL, NULL);
        if (!swsContext) goto end;

        int bufferSize = av_image_get_buffer_size(ctx->pix_fmt, frame->width, frame->height, 1);
        buffer = (uint8_t*)av_malloc(bufferSize);
        if (!buffer) goto end;

        av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, 
                             ctx->pix_fmt, frame->width, frame->height, 1);
        
        if (sws_scale(swsContext, frame->data, frame->linesize, 0, frame->height, 
                      rgbFrame->data, rgbFrame->linesize) < 0) goto end;
        
        rgbFrame->format = ctx->pix_fmt;
        rgbFrame->width = ctx->width;
        rgbFrame->height = ctx->height;
        
        ret = avcodec_send_frame(ctx, rgbFrame);
    } else {
        ret = avcodec_send_frame(ctx, frame);
    }

    if (ret < 0) goto end;

    ret = avcodec_receive_packet(ctx, &pkt);
    if (ret < 0) goto end;

    // 拷贝到外部缓冲区
    if (outbuf && pkt.size > 0 && pkt.size <= outbufSize) {
        memcpy(outbuf, pkt.data, pkt.size);
    }
    ret = pkt.size;

end:
    if (swsContext) sws_freeContext(swsContext);
    if (rgbFrame) { av_frame_unref(rgbFrame); av_frame_free(&rgbFrame); }
    if (buffer) av_free(buffer);
    av_packet_unref(&pkt);
    if (ctx) { avcodec_close(ctx); avcodec_free_context(&ctx); }
    return ret;
}

保存为JPG文件示例

结合上述函数,将帧保存为本地JPG文件需要注意缓冲区大小的计算。建议使用BRGA格式估算最大容量,以防溢出。

void saveFrameToJpg(AVFrame* frame, const char* path) {
    // 预估缓冲大小,考虑到JPG压缩比波动,适当预留空间
    int bufSize = av_image_get_buffer_size(AV_PIX_FMT_BGRA, frame->width, frame->height, 64) * 2;
    uint8_t* buf = (uint8_t*)av_malloc(bufSize);

    // 执行转换,目标为MJPEG (JPG)
    int picSize = frameToImage(frame, AV_CODEC_ID_MJPEG, buf, bufSize);

    if (picSize > 0) {
        FILE* f = fopen(path, "wb");
        if (f) {
            fwrite(buf, 1, picSize, f);
            fclose(f);
        }
    }
    av_free(buf);
}

自定义构造AVFrame示例

若只有裸数据而无现成AVFrame,可通过辅助函数快速创建:

AVFrame* allocFrame(uint8_t* frameData, int width, int height, AVPixelFormat format) {
    AVFrame* frame = av_frame_alloc();
    frame->width = width;
    frame->height = height;
    frame->format = format;
    av_image_fill_arrays(frame->data, frame->linesize, frameData, 
                         format, width, height, 64);
    return frame;
}

简要总结

以上就是C++利用FFmpeg将视频帧转换为JPG或PNG图片的详细内容。核心逻辑遵循标准的编码流程:查编码器 → 建上下文 → 像素格式转换 → 送帧 → 收包。由于图片数据始终存在于内存中,开发者可根据实际需求灵活选择直接传输或落盘保存。更多关于FFmpeg高级应用技巧的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表