美文网首页
ffmpeg生成gif动图

ffmpeg生成gif动图

作者: 3c1b8ae8346c | 来源:发表于2021-08-20 17:54 被阅读0次

本编文章是将之前的解码mp4生成的yuv数据读出来编码生成gif格式的动图, 整体流程和
解码mp4的地址链接:https://www.jianshu.com/p/f4516e6df9f1

1. 主要函数的调用流程

avformat_alloc_output_context2 根据文件名创建视频封装上下文对象
avio_open 打开视频文件
avcodec_find_encoder 查找编码器, 我们使用的是h264,参数就是AV_CODEC_ID_H264
avcodec_alloc_context3 创建编码器的上下文对象并且设置相关参数
avcodec_open2 打开编码器
avformat_new_stream 在视频文件中创建一个视频流对象
avformat_write_header 写入文件头

avfilter_get_by_name获取滤波器
avfilter_inout_alloc 滤波器的输入输出对象
avfilter_graph_alloc 创建一个滤波器图filter graph
avfilter_graph_create_filter 创建滤波器的上下文
av_opt_set_int_list 给滤波器设置参数
avfilter_graph_parse_ptr 设置滤波器图的描述字符串
avfilter_graph_config 检查FilterGraph的配置
av_buffersrc_add_frame_flags 往源滤波器buffer中输入待处理的数据
av_buffersink_get_frame 从滤波器输出端h处理完的数据

avcodec_send_frame 将yuv数据封装成帧并送入编码器
avcodec_receive_packet 从编码器中获取编码好的h264帧
av_interleaved_write_frame 将h264帧写入视频文件中
flush_encoder 写入剩余的帧数据
av_write_trailer 写入文件尾部信息

2. 主要步骤

  1. 打开新的视频文件
  2. 创建视频编码器
  3. 写入文件头
  4. 初始化滤波器
  5. 循环将yuv数据帧编码, 写入滤波器的输入端,并从输出端读出, 最后将编码好的帧写入gif文件中
  6. 写入文件尾部

3.详细代码

void gifEncode() {
    char *path = "d:/new_test.gif";
    int fps = 25;

    //注册组件
    av_register_all();

    int width = 1920;
    int height = 1080;

    AVFormatContext* avformat_context = NULL;
    //初始化封装格式上下文
    avformat_alloc_output_context2(&avformat_context, NULL, NULL, path);

    //打开输出文件
    if (avio_open(&avformat_context->pb, path, AVIO_FLAG_WRITE) < 0) {
        return;
    }
    //查找编码器
    AVCodec * codec = avcodec_find_encoder(AV_CODEC_ID_GIF);
    //创建编码器的上下文
    AVCodecContext *avcodec_context = avcodec_alloc_context3(codec);
    //时间基,pts,dts的时间单位  pts(解码后帧被显示的时间), dts(视频帧送入解码器的时间)的时间单位,是两帧之间的时间间隔
    avcodec_context->time_base.den = fps;//pts
    avcodec_context->time_base.num = 1;//1秒
    avcodec_context->codec_id = AV_CODEC_ID_GIF;

    avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO;//表示视频类型
    avcodec_context->pix_fmt = AV_PIX_FMT_PAL8;//视频数据像素格式

    avcodec_context->width = width;//视频宽高
    avcodec_context->height = height;

    //在gif文件中
    AVStream* avvideo_stream = avformat_new_stream(avformat_context, NULL);//创建一个流
    avvideo_stream->codec = avcodec_context;
    avvideo_stream->time_base = avcodec_context->time_base;
    avvideo_stream->codec->codec_tag = 0;

    //初始化编解码器
    int ret = avcodec_open2(avcodec_context, codec, nullptr);
    if (ret) {
        return;
    }

    int avformat_write_header_result = avformat_write_header(avformat_context, NULL);
    if (avformat_write_header_result != AVSTREAM_INIT_IN_WRITE_HEADER) {
            return;
    }
    //这个滤镜字符串的大概意思是设置输入的视频帧格式和fps,然后将帧分成两份o1, o2, 使用palettegen滤镜将o1生成gif调色板p,  最后使用paletteuse滤镜、利用调色板p采样,将o2生成为gif
    char *format = "format=pix_fmts=rgb565,fps=%d,split [o1] [o2];[o1] palettegen=stats_mode=diff [p]; [o2] [p] paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle";
    char *str = (char *)malloc(strlen(format) + 10);
    sprintf(str, format, 25);

    AVFilterGraph* filter_graph;
    AVFilterContext* buffersrc_ctx;
    AVFilterContext* buffersink_ctx;

    initFilter(avcodec_context, &filter_graph, &buffersrc_ctx, &buffersink_ctx, str);

    int buffer_size = av_image_get_buffer_size(avcodec_context->pix_fmt,
                                                   avcodec_context->width,
                                                   avcodec_context->height,
                                                   1);
    uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);

    AVFrame *frame = av_frame_alloc();

    AVPacket *av_packet = av_packet_alloc();
    av_init_packet(av_packet);

    uint8_t * file_buffer = (uint8_t *)av_malloc(width * height * 3 / 2);

    FILE *in_file = fopen("d:/yuv4201.yuv", "rb");
    int i= 0;

    SwsContext * swsContext = sws_getContext(width, height, AV_PIX_FMT_YUV420P, width, height, AV_PIX_FMT_RGB565, SWS_FAST_BILINEAR, NULL, NULL, NULL);

    if(swsContext == nullptr){
        return;
    }

    AVFrame *rgbFrame = av_frame_alloc();
    rgbFrame->linesize[0] = width * 2;
    rgbFrame->format = AV_PIX_FMT_RGB565;
    rgbFrame->width = width;
    rgbFrame->height = height;
    unsigned char * out_buffer1 = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_RGB565, avcodec_context->width, avcodec_context->height));
    avpicture_fill((AVPicture *)rgbFrame, out_buffer1, AV_PIX_FMT_RGB565, avcodec_context->width, avcodec_context->height);


    while (true) {

        //读取yuv帧数据  注意yuv420p的长度  width * height * 3 / 2
        if (fread(file_buffer, 1, width * height * 3 / 2, in_file) <= 0) {
            break;
        } else if (feof(in_file)) {
            break;
        }

        //封装yuv帧数据
        frame->data[0] = file_buffer;
        frame->data[1] = file_buffer + width * height;
        frame->data[2] = file_buffer + width * height * 5 / 4;
        frame->linesize[0] = width;
        frame->linesize[1] = width / 2;
        frame->linesize[2] = width / 2;
        //frame->pts = i;
        frame->pts = i;
        frame->width = width;
        frame->height = height;
        i++;

        sws_scale(
            swsContext,
            (uint8_t const * const *) frame->data,
            frame->linesize,
            0,
            height,
            rgbFrame->data,
            rgbFrame->linesize
            );
        rgbFrame->pts = i;
        if (i == 1) {
            continue;
        }
        // 往源滤波器buffer中输入待处理的数据
        av_buffersrc_add_frame_flags(buffersrc_ctx, rgbFrame, AV_BUFFERSRC_FLAG_KEEP_REF);
    }

    sws_freeContext(swsContext);

    // 最后写入一个空帧
    if (av_buffersrc_add_frame_flags(buffersrc_ctx, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF) >= 0) {
        do {
            AVFrame *gifFrame = av_frame_alloc();
            // 从目的滤波器buffersink中输出处理完的数据
            int ret = av_buffersink_get_frame(buffersink_ctx, gifFrame);
//                    ret = av_buffersink_get_frame(buffersink_ctx, filter_frame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                // av_log(nullptr, AV_LOG_ERROR, "error get frame from buffer sink %s\n", av_err2str(ret));
                break;
            }

            // write the filter frame to output file
            ret = avcodec_send_frame(avcodec_context, gifFrame);
            AVPacket *pkt = av_packet_alloc();
            av_init_packet(pkt);

            while (ret >= 0) {
                ret = avcodec_receive_packet(avcodec_context, pkt);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                }

                AVRational codecTimebase = avcodec_context->time_base;

                AVRational streamTimebase = avformat_context->streams[0]->time_base;

                av_packet_rescale_ts(pkt, codecTimebase, streamTimebase);

                //pkt.RescaleTs(encoderTimebase, streamTimebase);

                av_write_frame(avformat_context, pkt);
            }

            av_packet_unref(pkt);

        } while (ret >= 0);
    }

    fclose(in_file);
    avcodec_close(avcodec_context);
    av_free(frame);
    av_free(out_buffer);
    av_free_packet(av_packet);

    av_write_trailer(avformat_context);
    avio_close(avformat_context->pb);
    avformat_free_context(avformat_context);
    
    avfilter_free(buffersrc_ctx);
    avfilter_free(buffersink_ctx);
    avfilter_graph_free(&filter_graph);
}


int initFilter(AVCodecContext *context, AVFilterGraph** filter_graph, AVFilterContext** buffersrc_ctx, AVFilterContext** buffersink_ctx, const char* filter_desc)
{
    char args[512];
    int ret = 0;

    //分别获取输入和输出的滤波器
    const AVFilter* buffersrc = avfilter_get_by_name("buffer");
    const AVFilter* buffersink = avfilter_get_by_name("buffersink");

    //创建滤波器的输入端
    AVFilterInOut* inputs = avfilter_inout_alloc();
    //创建滤波器的输出端
    AVFilterInOut* outputs = avfilter_inout_alloc();
    //MBAVRational avrational;
    //encoder->GetTimeBase(avrational);

    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_PAL8, AV_PIX_FMT_NONE };

    // 创建一个滤波器图filter graph
    *filter_graph = avfilter_graph_alloc();
    if (!outputs || !inputs || !*filter_graph) {
        ret = AVERROR(ENOMEM);
        return ret;
    }

    snprintf(args, sizeof(args),
             "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
             context->width, context->height, AV_PIX_FMT_RGB565,
             context->time_base.num, context->time_base.den,
             context->sample_aspect_ratio.num, context->sample_aspect_ratio.den);

    //创建滤波器的上下文
    ret = avfilter_graph_create_filter(buffersrc_ctx, buffersrc, "in", args, nullptr, *filter_graph);
    if (ret < 0) {
        av_log(nullptr, AV_LOG_ERROR, "Cannot create buffer source\n");
        return ret;
    }

    ret = avfilter_graph_create_filter(buffersink_ctx, buffersink, "out", nullptr, nullptr, *filter_graph);
    if (ret < 0) {
        av_log(nullptr, AV_LOG_ERROR, "Cannot create buffer sink\n");
        return ret;
    }

    //给滤波器设置参数
    av_opt_set_int_list(*buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
    if (ret < 0) {
        av_log(nullptr, AV_LOG_ERROR, "can not set output pixel format\n");
        return ret;
    }

    outputs->name = av_strdup("in");
    outputs->filter_ctx = *buffersrc_ctx;
    outputs->pad_idx = 0;
    outputs->next = nullptr;

    inputs->name = av_strdup("out");
    inputs->filter_ctx = *buffersink_ctx;
    inputs->pad_idx = 0;
    inputs->next = nullptr;

    //设置滤波器图的描述字符串和输入端,输出端
    if ((ret = avfilter_graph_parse_ptr(*filter_graph, filter_desc, &inputs, &outputs, nullptr)) < 0) {
        av_log(nullptr, AV_LOG_ERROR, "parse filter graph error\n");
        return ret;
    }

    //检查FilterGraph的配置。
    if ((ret = avfilter_graph_config(*filter_graph, nullptr)) < 0) {
        av_log(nullptr, AV_LOG_ERROR, "config graph error\n");
    }


    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);

    return 0;
}

相关文章

  • ffmpeg生成gif动图

    本编文章是将之前的解码mp4生成的yuv数据读出来编码生成gif格式的动图, 整体流程和解码mp4的地址链接:ht...

  • Gif君技术支持

    Gif君是一款强大的gif动图编辑及生成工具

  • 工具之gif生成工具

    LICEcap是一款很小的gif生成工具,用来录屏,然后生成gif动图使用方式 : 界面 点击Record,会打开...

  • PS生成gif动图

    1.准备叠加好的图层; 2.打开动画制作窗口,选择创建帧动画; 3.关闭其他图层,保留一个图层,新建关键帧,修改第...

  • 【python实战】生成个性二维码

    一、需求 根据现有的网址、图片或gif,生成二维码,其中,包括根据多张图片生成gif动图,简单易学,上手就会! 二...

  • 11.9【看全球】

    1:在线gif编辑工具 http://ezgif.com/ 这个网站提供了一系列gif动图生成、编辑、压缩工具,方...

  • 2020-05-15

    gif动图制作工具集合: 电脑端:screentogif、QQ、giphy 手机端:gif神器 gif动图网站推荐...

  • ffmpeg制作动图(包括gif和webp),ffmpeg使用s

    2passes法生成高质量动图 ffmpeg开发者有提供一种2passes生成高质量动图的方法 不过个人来说有一些...

  • GIF动图制作全攻略!不用PS简单快速制作GIF动图!

    今天我们分享的内容主要为怎样将视频转为GIF动图?怎样修改GIF动图的尺寸?怎样压缩GIF动图?怎样将多张图片合成...

  • GIF图的最高奥义

    目录1.关于如何制作GIF动图2.如何优化GIF图3.使用PS作GIF图的心得 1.关于如何制作gif动图 这期文...

网友评论

      本文标题:ffmpeg生成gif动图

      本文链接:https://www.haomeiwen.com/subject/xzluvltx.html