温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

C++ ffmpeg如何实现将视频帧转换成jpg或png等图片

发布时间:2023-03-28 16:15:04 来源:亿速云 阅读:246 作者:iii 栏目:开发技术

本篇内容介绍了“C++ ffmpeg如何实现将视频帧转换成jpg或png等图片”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    一、如何实现

    1、查找编码器

    首先需要查找图片编码器,比如jpg为AV_CODEC_ID_MJPEG,png为AV_CODEC_ID_PNG

    示例代码:

    enum AVCodecID codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);

    2、构造编码器上下文

    有了编码器就可以构造编码器上下文了。

    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;//使用编码器适配的像素格式
    //打开编码器
    avcodec_open2(ctx, codec, NULL);

    3、像素格式转换

    如果输入视频帧的像素和编码器的像素格式不相同则需要转换像素格式,我们采用SwsContext 转换即可

    AVFrame*rgbFrame = av_frame_alloc();//转换后的帧
    swsContext = sws_getContext(frame->width, frame->height, (enum AVPixelFormat)frame->format, frame->width, frame->height, ctx->pix_fmt, 1, NULL, NULL, NULL);
    int bufferSize = av_image_get_buffer_size(ctx->pix_fmt, frame->width, frame->height, 1) * 2;
    buffer = (unsigned char*)av_malloc(bufferSize);
    //构造帧的缓存
    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、编码

    得到转后的帧就可以编码

    ret = avcodec_send_frame(ctx, rgbFrame);

    5、获取图片数据

    获取解码后的包即可得到图片数据。

    uint8_t* outbuf;//输出图片的缓存
    size_t outbufSize;//缓存大小
    AVPacket pkt;
    av_init_packet(&pkt);
    //获取解码的包
    avcodec_receive_packet(ctx, &pkt);
    //将图片数据拷贝到缓存
    if (pkt.size > 0 && pkt.size <= outbufSize)
    memcpy(outbuf, pkt.data, pkt.size);

    6、销毁资源

    将上述步骤使用的对象销毁。

    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);
    }

    二、完整代码

    /// <summary>
    /// 帧转图片
    /// 如果外部提供的缓存长度不足则不会写入。
    /// </summary>
    /// <param name="frame">[in]视频帧</param>
    /// <param name="codecID">[in]图片编码器ID,如jpg:AV_CODEC_ID_MJPEG,png:AV_CODEC_ID_PNG</param>
    /// <param name="outbuf">[out]图片缓存,由外部提供</param>
    /// <param name="outbufSize">[in]图片缓存长度</param>
    /// <returns>返回图片实际长度</returns>
    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)
        {
            printf("avcodec_send_frame error %d", codecID);
            goto end;
        }
        if (!codec->pix_fmts)
        {
            printf("unsupport pix format with codec %s", codec->name);
            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;
        ret = avcodec_open2(ctx, codec, NULL);
        if (ret < 0)
        {
            printf("avcodec_open2 error %d", ret);
            goto end;
        }
        if (frame->format != ctx->pix_fmt)
        {
            rgbFrame = av_frame_alloc();
            if (rgbFrame == NULL)
            {
                printf("av_frame_alloc  fail:%d");
                goto end;
            }
            swsContext = sws_getContext(frame->width, frame->height, (enum AVPixelFormat)frame->format, frame->width, frame->height, ctx->pix_fmt, 1, NULL, NULL, NULL);
            if (!swsContext)
            {
                printf("sws_getContext  fail:%d");
                goto end;
            }
            int bufferSize = av_image_get_buffer_size(ctx->pix_fmt, frame->width, frame->height, 1) * 2;
            buffer = (unsigned char*)av_malloc(bufferSize);
            if (buffer == NULL)
            {
                printf("buffer alloc fail:%d", bufferSize);
                goto end;
            }
            av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, ctx->pix_fmt, frame->width, frame->height, 1);
            if ((ret = sws_scale(swsContext, frame->data, frame->linesize, 0, frame->height, rgbFrame->data, rgbFrame->linesize)) < 0)
            {
                printf("sws_scale error %d", ret);
            }
            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)
        {
            printf("avcodec_send_frame error %d", ret);
            goto end;
        }
        ret = avcodec_receive_packet(ctx, &pkt);
        if (ret < 0)
        {
            printf("avcodec_receive_packet error %d", ret);
            goto end;
        }
        if (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;
    }

    三、使用示例

    1、截取视频帧并保存文件

    void main() {
        AVFrame* frame;//视频解码得到的帧
        saveFrameToJpg(frame,"snapshot.jpg");
    }
    /// <summary>
    /// 将视频帧保存为jpg图片
    /// </summary>
    /// <param name="frame">视频帧</param>
    /// <param name="path">保存的路径</param>
    void saveFrameToJpg(AVFrame*frame,const char*path) {
        //确保缓冲区长度大于图片,使用brga像素格式计算。如果是bmp或tiff依然可能超出长度,需要加一个头部长度,或直接乘以2。
        int bufSize = av_image_get_buffer_size(AV_PIX_FMT_BGRA, frame->width, frame->height, 64);
        //申请缓冲区
        uint8_t* buf = (uint8_t*)av_malloc(bufSize);
        //将视频帧转换成图片
        int picSize = frameToImage(frame, AV_CODEC_ID_MJPEG, buf, bufSize);
        //写入文件
        auto f = fopen(path, "wb+");
        if (f)
        {
            fwrite(buf, sizeof(uint8_t), bufSize, f);
            fclose(f);
        }
        //释放缓冲区
        av_free(buf);
    }

    2、自定义数据构造AVFrame

    void main() {
        uint8_t*frameData;//解码得到的视频数据
        AVFrame* frame=allocFrame(frameData,640,360,AV_PIX_FMT_YUV420P);
        saveFrameToJpg(frame,"snapshot.jpg");//此方法定义在示例1中
        av_frame_free(&frame);
    }
    /// <summary>
    /// 通过裸数据生成avframe
    /// </summary>
    /// <param name="frameData">帧数据</param>
    /// <param name="width">帧宽</param>
    /// <param name="height">帧高</param>
    /// <param name="format">像素格式</param>
    /// <returns>avframe,使用完成后需要调用av_frame_free释放</returns>
    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, frame->width, frame->height, 64);
        return frame;
    }

    “C++ ffmpeg如何实现将视频帧转换成jpg或png等图片”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI