ffmpeg更改视频的帧率

note

视频帧率调整

帧率(fps-frame per second)

例如:原来帧率为30,调整后为1 现象:原来是每秒有30张图像,调整后每秒1张图像,看着图像很慢

实现:在每秒的时间区间里,取一张图像

version

#define LIBAVFILTER_VERSION_MINOR 12

#define LIBAVFILTER_VERSION_MICRO 100

#define LIBAVCODEC_VERSION_MINOR 31

#define LIBAVCODEC_VERSION_MICRO 102

code

void CFfmpegOps::ChangeVideoFPS(const char *in_mp4, const char *out_mp4)
{
    AVFormatContext *in_fmt_ctx = nullptr;
    const AVInputFormat *in_fmt = nullptr;
    AVFormatContext *out_fmt_ctx = nullptr;
    const AVOutputFormat *out_fmt = nullptr;
    int ret = -1;
    int video_stream_index = 0;
    const AVCodec *decoder = nullptr;
    AVCodecContext *decoder_ctx = nullptr;
    const AVCodec* encoder = nullptr;
    AVCodecContext* encoder_ctx = nullptr;
    AVStream* in_avstream = nullptr;
    AVStream* out_avstream = nullptr;
    const AVFilter * avfilter_buffer_src = nullptr; // 源,video buffer可设置的参数有
    AVFilterContext* avfilter_ctx_buffer_src = nullptr;
    const AVFilter * avfilter_fps = nullptr;    // 中间节点,video fps可设置的参数包含fps
    AVFilterContext* avfilter_ctx_fps = nullptr;
    const AVFilter* avfilter_buffer_sink = nullptr; // 终,video buffersink可设置的参数只有pixel formats
    AVFilterContext* avfilter_ctx_buffer_sink = nullptr;
    AVFilterGraph* avfiltergraph = nullptr;
    AVPacket* avpacket_src = nullptr;
    AVFrame* avframe_src = nullptr;
    AVFrame* avframe_dest = nullptr;
    AVPacket* avpacket_dest = nullptr;
    AVRational pixel_aspect = {.num = 1, .den = 1};

    ret = avformat_open_input(&in_fmt_ctx, in_mp4, nullptr, nullptr);
    if (ret < 0)
    {
        printf("avformat_open_input error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }
    in_fmt = in_fmt_ctx->iformat;

    ret = avformat_find_stream_info(in_fmt_ctx, nullptr);
    if (ret < 0)
    {
        printf("avformat_find_stream_info error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    ret = av_find_best_stream(in_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    if (ret < 0)
    {
        printf("av_find_best_stream error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }
    video_stream_index = ret;
    in_avstream = in_fmt_ctx->streams[video_stream_index];

    decoder = avcodec_find_decoder(in_avstream->codecpar->codec_id);
    if (!decoder)
    {
        printf("avcodec_find_decoder error\n");
        goto END;
    }

    decoder_ctx = avcodec_alloc_context3(decoder);
    if (!decoder_ctx)
    {
        printf("avcodec_alloc_context3 error\n");
        goto END;
    }

    ret = avcodec_parameters_to_context(decoder_ctx, in_avstream->codecpar);
    if (ret < 0)
    {
        printf("avcodec_parameters_to_context error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    ret = avcodec_open2(decoder_ctx, decoder, nullptr);
    if (ret < 0)
    {
        printf("avcodec_open2 error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    avfiltergraph = avfilter_graph_alloc();
    if (!avfiltergraph)
    {
        printf("avfilter_graph_alloc error\n");
        goto END;
    }

    avfilter_buffer_src = avfilter_get_by_name("buffer");   // buffer对应video,abuffer对应audio
    if (!avfilter_buffer_src)
    {
        printf("avfilter_get_by_name error\n");
        goto END;
    }
    printf("avfilter_buffer_src->desc:%s\n", avfilter_buffer_src->description);

    avfilter_ctx_buffer_src = avfilter_graph_alloc_filter(avfiltergraph, avfilter_buffer_src, avfilter_buffer_src->name);
    if (!avfilter_ctx_buffer_src)
    {
        printf("avfilter_graph_alloc_filter error\n");
        goto END;
    }

    // 设置avfilter_ctx_buffer_src的可选参数
    // 参照libavfilter/buffersrc.c文件中对video buffer的可选参数描述
    ret = av_opt_set(avfilter_ctx_buffer_src, 
                "width", std::to_string(in_avstream->codecpar->width).c_str(), 
                AV_OPT_SEARCH_CHILDREN);
    if (ret < 0)
    {
        printf("av_opt_set error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }
    ret = av_opt_set(avfilter_ctx_buffer_src, 
                "height", std::to_string(in_avstream->codecpar->height).c_str(), 
                AV_OPT_SEARCH_CHILDREN);
    if (ret < 0)
    {
        printf("av_opt_set error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }
    ret = av_opt_set(avfilter_ctx_buffer_src, 
                "pix_fmt", av_get_pix_fmt_name((AVPixelFormat)(in_avstream->codecpar->format)), 
                AV_OPT_SEARCH_CHILDREN);
    if (ret < 0)
    {
        printf("av_opt_set error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }
    ret = av_opt_set_q(avfilter_ctx_buffer_src,
                "time_base", in_avstream->time_base,
                AV_OPT_SEARCH_CHILDREN);
    if (ret < 0)
    {
        printf("av_opt_set error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }
    ret = av_opt_set_q(avfilter_ctx_buffer_src,
                "frame_rate", in_avstream->r_frame_rate,
                AV_OPT_SEARCH_CHILDREN);
    if (ret < 0)
    {
        printf("av_opt_set error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    ret = av_opt_set_q(avfilter_ctx_buffer_src,
                    "pixel_aspect", pixel_aspect,
                    AV_OPT_SEARCH_CHILDREN);
    if (ret < 0)
    {
        printf("av_opt_set error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    // 上面已经设置过参数,初始化就不传参了
    ret = avfilter_init_str(avfilter_ctx_buffer_src, nullptr);
    if (ret < 0)
    {
        printf("avfilter_init_str error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    avfilter_fps = avfilter_get_by_name("fps");
    if (!avfilter_fps)
    {
        printf("avfilter_get_by_name error\n");
        goto END;
    }
    printf("avfilter_fps->desc:%s\n", avfilter_fps->description);

    avfilter_ctx_fps = avfilter_graph_alloc_filter(avfiltergraph, avfilter_fps, avfilter_fps->name);
    if (!avfilter_ctx_fps)
    {
        printf("avfilter_graph_alloc_filter error\n");
        goto END;
    }

    ret = av_opt_set(avfilter_ctx_fps, 
                    "fps", "1",
                    AV_OPT_SEARCH_CHILDREN);
    if (ret < 0)
    {
        printf("av_opt_set error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }
    
    ret = avfilter_init_str(avfilter_ctx_fps, nullptr);
    if (ret < 0)
    {
        printf("avfilter_init_str error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    avfilter_buffer_sink = avfilter_get_by_name("buffersink");
    if (!avfilter_buffer_sink)
    {
        printf("avfilter_get_by_name error\n");
        goto END;
    }
    printf("avfilter_buffer_sink->desc:%s\n", avfilter_buffer_sink->description);

    avfilter_ctx_buffer_sink = avfilter_graph_alloc_filter(avfiltergraph, avfilter_buffer_sink, avfilter_buffer_sink->name);
    if (!avfilter_ctx_buffer_sink)
    {
        printf("avfilter_graph_alloc_filter error\n");
        goto END;
    }

    ret = avfilter_init_str(avfilter_ctx_buffer_sink, nullptr);
    if (ret < 0)
    {
        printf("avfilter_init_str error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    // 把avfilter组成链
    // avfilter_link的作用,在src和dest之间新建AVFilterLink实例
    // srcpad:src的输出通道编号
    // destpad:dest的输入通道编号
    ret = avfilter_link(avfilter_ctx_buffer_src, 0, avfilter_ctx_fps, 0);
    if (ret != 0)
    {
        printf("avfilter_link error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    ret = avfilter_link(avfilter_ctx_fps, 0, avfilter_ctx_buffer_sink, 0);
    if (ret != 0)
    {
        printf("avfilter_link error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    ret = avfilter_graph_config(avfiltergraph, nullptr);
    if (ret < 0)
    {
        printf("avfilter_graph_config error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    ret = avformat_alloc_output_context2(&out_fmt_ctx, nullptr, nullptr, out_mp4);
    if (ret < 0)
    {
        printf("avformat_alloc_output_context2 error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }
    out_fmt = out_fmt_ctx->oformat;

    encoder = avcodec_find_encoder(decoder->id);
    if (!encoder)
    {
        printf("avcodec_find_encoder error\n");
        goto END;
    }

    encoder_ctx = avcodec_alloc_context3(encoder);
    if (!encoder_ctx)
    {
        printf("avcodec_alloc_context3 error\n");
        goto END;
    }
    encoder_ctx->pix_fmt = (AVPixelFormat)(av_buffersink_get_format(avfilter_ctx_buffer_sink));
    encoder_ctx->width = av_buffersink_get_w(avfilter_ctx_buffer_sink);
    encoder_ctx->height = av_buffersink_get_h(avfilter_ctx_buffer_sink);
    encoder_ctx->time_base = av_buffersink_get_time_base(avfilter_ctx_buffer_sink);
    encoder_ctx->framerate = av_buffersink_get_frame_rate(avfilter_ctx_buffer_sink);
    encoder_ctx->gop_size = decoder_ctx->gop_size;
    encoder_ctx->max_b_frames = decoder_ctx->max_b_frames;

    if (out_fmt->flags & AVFMT_GLOBALHEADER)
    {
        encoder_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    ret = avcodec_open2(encoder_ctx, encoder, nullptr);
    if (ret < 0)
    {
        printf("avcodec_open2 error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    out_avstream = avformat_new_stream(out_fmt_ctx, encoder);
    if (!out_avstream)
    {
        printf("avformat_new_stream error\n");
        goto END;
    }
    out_avstream->time_base = av_buffersink_get_time_base(avfilter_ctx_buffer_sink);
    out_avstream->r_frame_rate = av_buffersink_get_frame_rate(avfilter_ctx_buffer_sink);
    out_avstream->avg_frame_rate = av_buffersink_get_frame_rate(avfilter_ctx_buffer_sink);

    ret = avcodec_parameters_from_context(out_avstream->codecpar, encoder_ctx);
    if (ret < 0)
    {
        printf("avcodec_parameters_from_context error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    ret = avio_open(&(out_fmt_ctx->pb), out_mp4, AVIO_FLAG_WRITE);
    if (ret < 0)
    {
        printf("avio_open error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    // 调用avformat_write_header后,out_avstream的时间基发生了变化(10->102400),容器要求?
    ret = avformat_write_header(out_fmt_ctx, nullptr);
    if (ret < 0)
    {
        printf("avformat_write_header error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

    avpacket_src = av_packet_alloc();
    if (!avpacket_src)
    {
        printf("av_packet_alloc error\n");
        goto END;
    }

    avpacket_dest = av_packet_alloc();
    if (!avpacket_dest)
    {
        printf("av_packet_alloc error\n");
        goto END;
    }

    avframe_src = av_frame_alloc();
    if (!avframe_src)
    {
        printf("av_frame_alloc error\n");
        goto END;
    }

    avframe_dest = av_frame_alloc();
    if (!avframe_dest)
    {
        printf("av_frame_alloc error\n");
        goto END;
    }

    while (1)
    {
        // 从输入文件读取压缩视频帧
        ret = av_read_frame(in_fmt_ctx, avpacket_src);
        if (ret < 0)
        {
            printf("av_read_frame error(%s)\n", GetFfmpegERR(ret));
            break;
        }

        if (avpacket_src->stream_index != video_stream_index)
        {
            av_packet_unref(avpacket_src);
            continue;
        }

        // 把压缩视频帧发送给解码器
        ret = avcodec_send_packet(decoder_ctx, avpacket_src);
        if (ret != 0)
        {
            printf("avcodec_send_packet error(%s)\n", GetFfmpegERR(ret));
        }
        else
        {
            // 接收解码器的输出视频帧
            ret = avcodec_receive_frame(decoder_ctx, avframe_src);
            if (ret != 0)
            {
                printf("avcodec_receive_frame error(%s)\n", GetFfmpegERR(ret));
            }
            else
            {
                // 把视频帧交给avfilter
                ret = av_buffersrc_write_frame(avfilter_ctx_buffer_src, avframe_src);
                if (ret != 0)
                {
                    printf("av_buffersrc_write_frame error(%s)\n", GetFfmpegERR(ret));
                }
                else
                {
                    // 从avfilter获取视频帧
                    ret = av_buffersink_get_frame(avfilter_ctx_buffer_sink, avframe_dest);
                    if (ret < 0)
                    {
                        printf("av_buffersink_get_frame error(%s)\n", GetFfmpegERR(ret));
                    }
                    else
                    {
                        // 把视频帧交给编码器
                        ret = avcodec_send_frame(encoder_ctx, avframe_dest);
                        if (ret != 0)
                        {
                            printf("avcodec_send_frame error(%s)\n", GetFfmpegERR(ret));
                        }
                        else
                        {
                            // 从编码器获取压缩视频帧
                            ret = avcodec_receive_packet(encoder_ctx, avpacket_dest);
                            if (ret != 0)
                            {
                                printf("avcodec_receive_packet error(%s)\n", GetFfmpegERR(ret));
                            }
                            else
                            {
                                av_packet_rescale_ts(avpacket_dest, encoder_ctx->time_base, out_avstream->time_base);

                                // 写入到输出文件
                                ret = av_write_frame(out_fmt_ctx, avpacket_dest);
                                if (ret < 0)
                                {
                                    printf("av_write_frame error(%s)\n", GetFfmpegERR(ret));
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    ret = av_write_trailer(out_fmt_ctx);
    if (ret < 0)
    {
        printf("av_write_trailer error(%s)\n", GetFfmpegERR(ret));
        goto END;
    }

END:
    if (avframe_dest)
    {
        av_frame_free(&avframe_dest);
        avframe_dest = nullptr;
    }
    if (avframe_src)
    {
        av_frame_free(&avframe_src);
        avframe_src = nullptr;
    }
    if (avpacket_src)
    {
        av_packet_free(&avpacket_src);
        avpacket_src = nullptr;
    }
    if (avpacket_dest)
    {
        av_packet_free(&avpacket_dest);
        avpacket_dest = nullptr;
    }
    if (avfilter_ctx_buffer_sink)
    {
        avfilter_free(avfilter_ctx_buffer_sink);
        avfilter_ctx_buffer_sink = nullptr;
    }
    if (avfilter_ctx_fps)
    {
        avfilter_free(avfilter_ctx_fps);
        avfilter_ctx_fps = nullptr;
    }
    if (avfilter_ctx_buffer_src)
    {
        avfilter_free(avfilter_ctx_buffer_src);
        avfilter_ctx_buffer_src = nullptr;
    }
    if (avfiltergraph)
    {
        avfilter_graph_free(&avfiltergraph);
        avfiltergraph = nullptr;
    }
    if (encoder_ctx)
    {
        avcodec_free_context(&encoder_ctx);
        encoder_ctx = nullptr;
    }
    if (decoder_ctx)
    {
        avcodec_free_context(&decoder_ctx);
        decoder_ctx = nullptr;
    }
    if (out_fmt_ctx)
    {
        avformat_free_context(out_fmt_ctx);
        out_fmt_ctx = nullptr;
    }
    if (in_fmt_ctx)
    {
        avformat_free_context(in_fmt_ctx);
        in_fmt_ctx = nullptr;
    }
}

performance

相关推荐
MustardJim几秒前
Visual Studio 引入外部静态库与动态库
c++·visual studio
挽月0017 分钟前
C++单例模式
开发语言·c++·单例模式
机器视觉知识推荐、就业指导15 分钟前
Qt/C++ TCP调试助手V1.1 新增图像传输与接收功能(附发布版下载链接)
c++·qt·tcp/ip
wx2004110229 分钟前
Codeforces Round 973 (Div. 2) - D题
数据结构·c++·算法
aqi0044 分钟前
FFmpeg开发笔记(五十三)移动端的国产直播录制工具EasyPusher
android·ffmpeg·音视频·直播·流媒体
DieSnowK1 小时前
[项目][WebServer][CGI机制 && 设计]详细讲解
linux·开发语言·c++·http·项目·webserver·cgi机制
Q186000000001 小时前
在HTML中添加视频
前端·html·音视频
学习前端的小z1 小时前
【AI视频】Runway:Gen-2 运镜详解
人工智能·aigc·音视频
归寻太乙2 小时前
C++函数重载完成日期类相关计算
开发语言·c++
尽蝶叙2 小时前
C++:分苹果【排列组合】
开发语言·c++·算法