尝试使用ffmpeg C API取消执行并删除文件

问题描述 投票:1回答:1

下面的代码是一个类,用于通过add_frame()方法将多张图像转换为带有encode()的GIF。它还使用过滤器来生成和应用调色板。用法是这样的:

代码调用示例

std::unique_ptr<px::GIF::FFMPEG> gif_obj = nullptr;
try
{
    gif_obj = std::make_unique<px::GIF::FFMPEG>({1000,1000}, 12, "C:/out.gif",
              "format=pix_fmts=rgb24,split [a][b];[a]palettegen[p];[b][p]paletteuse");

    // Example: a simple vector of images (usually process internally)
    for(auto img : image_vector)
         gif_obj->add_frame(img);

    // Once all frame were added, encode the final GIF with the filter applied.
    gif_obj->encode();
}
catch(const std::exception& e)
{
    // An error occured! We must close FFMPEG properly and delete the created file.
    gif_obj->cancel();
}

我有以下问题。如果代码由于任何原因引发异常,我将调用ffmpeg->cancel(),它假定要删除磁盘上的GIF文件。但这永远都行不通,我认为文件上有一个锁或类似的东西。所以这是我的问题:

关闭/释放ffmpeg对象以便随后删除文件的正确方法是什么?


下面的完整课程代码

标题

// C++ Standard includes    
#include <memory>
#include <string>
#include <vector>


// 3rd Party incldues
#ifdef __cplusplus
extern "C" {
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
#include "libavutil/opt.h"
#include "libavfilter/buffersrc.h"
#include "libavfilter/buffersink.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
#endif

#define FFMPEG_MSG_LEN 2000

namespace px
{
    namespace GIF
    {
        class FFMPEG
        {
        public:
            FFMPEG(const px::Point2D<int>& dim,
                   const int framerate,
                   const std::string& filename,
                   const std::string& filter_cmd);

            ~FFMPEG();

            void add_frame(pxImage * const img);
            void encode();
            void cancel();

        private:

            void init_filters();            // Init everything that needed to filter the input frame.
            void init_muxer();              // The muxer that creates the output file.
            void muxing_one_frame(AVFrame* frame);
            void release();

            int _ret = 0;                   // status code from FFMPEG.
            char _err_msg[FFMPEG_MSG_LEN];  // Error message buffer.


            int m_width = 0;                // The width that all futur images must have to be accepted.
            int m_height = 0;               // The height that all futur images must have to be accepted.

            int m_framerate = 0;            // GIF Framerate.
            std::string m_filename = "";    // The GIF filename (on cache?)
            std::string m_filter_desc = ""; // The FFMPEG filter to apply over the frames.

            bool as_frame = false;

            AVFrame* picture_rgb24 = nullptr;           // Temporary frame that will hold the pxImage in an RGB24 format (NOTE: TOP-LEFT origin)

            AVFormatContext* ofmt_ctx = nullptr;        // ouput format context associated to the 
            AVCodecContext* o_codec_ctx = nullptr;      // output codec for the GIF

            AVFilterGraph* filter_graph = nullptr;      // filter graph associate with the string we want to execute
            AVFilterContext* buffersrc_ctx = nullptr;   // The buffer that will store all the frames in one place for the palette generation.
            AVFilterContext* buffersink_ctx = nullptr;  // The buffer that will store the result afterward (once the palette are used).

            int64_t m_pts_increment = 0;
        };
    };
};

ctor

px::GIF::FFMPEG::FFMPEG(const px::Point2D<int>& dim,
                        const int framerate,
                        const std::string& filename,
                        const std::string& filter_cmd) :
    m_width(dim.x()),
    m_height(dim.y()),
    m_framerate(framerate),
    m_filename(filename),
    m_filter_desc(filter_cmd)
{
#if !_DEBUG
    av_log_set_level(AV_LOG_QUIET); // Set the FFMPEG log to quiet to avoid too much logs.
#endif

    // Allocate the temporary buffer that hold the ffmpeg image (pxImage to AVFrame conversion).
    picture_rgb24 = av_frame_alloc();
    picture_rgb24->pts = 0;
    picture_rgb24->data[0] = NULL;
    picture_rgb24->linesize[0] = -1;
    picture_rgb24->format = AV_PIX_FMT_RGB24;
    picture_rgb24->height = m_height;
    picture_rgb24->width = m_width; 

    if ((_ret = av_image_alloc(picture_rgb24->data, picture_rgb24->linesize, m_width, m_height, (AVPixelFormat)picture_rgb24->format, 24)) < 0)
        throw px::GIF::Error("Failed to allocate the AVFrame for pxImage conversion with error: " +
                             std::string(av_make_error_string(_err_msg, FFMPEG_MSG_LEN, _ret)),
                             "GIF::FFMPEG CTOR");   

    //printf("allocated picture of size %d, linesize %d %d %d %d\n", _ret, picture_rgb24->linesize[0], picture_rgb24->linesize[1], picture_rgb24->linesize[2], picture_rgb24->linesize[3]);

    init_muxer();   // Prepare the GIF encoder (open it on disk).
    init_filters(); // Prepare the filter that will be applied over the frame.

    // Instead of hardcoder {1,100} which is the GIF tbn, we collect it from its stream.
    // This will avoid future problem if the codec change in ffmpeg.
    if (ofmt_ctx && ofmt_ctx->nb_streams > 0)
        m_pts_increment = av_rescale_q(1, { 1, m_framerate }, ofmt_ctx->streams[0]->time_base);
    else
        m_pts_increment = av_rescale_q(1, { 1, m_framerate }, { 1, 100 });
}

FFMPEG初始化(过滤器和多路复用器)

void px::GIF::FFMPEG::init_filters()
{
    const AVFilter* buffersrc = avfilter_get_by_name("buffer");
    const AVFilter* buffersink = avfilter_get_by_name("buffersink");

    AVRational time_base = { 1, m_framerate };
    AVRational aspect_pixel = { 1, 1 };

    AVFilterInOut* inputs = avfilter_inout_alloc();
    AVFilterInOut* outputs = avfilter_inout_alloc();

    filter_graph = avfilter_graph_alloc();

    try
    {
        if (!outputs || !inputs || !filter_graph)
            throw px::GIF::Error("Failed to 'init_filters' could not allocated the graph/filters.", "GIF::FFMPEG init_filters");

        char args[512];
        snprintf(args, sizeof(args),
                 "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
                 m_width, m_height,
                 picture_rgb24->format,
                 time_base.num, time_base.den,
                 aspect_pixel.num, aspect_pixel.den);

        if (avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, nullptr, filter_graph) < 0)
            throw px::GIF::Error("Failed to create the 'source buffer' in init_filer method.", "GIF::FFMPEG init_filters");


        if (avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", nullptr, nullptr, filter_graph) < 0)
            throw px::GIF::Error("Failed to create the 'sink buffer' in init_filer method.", "GIF::FFMPEG init_filters");

        // GIF has possible output of PAL8.
        enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_PAL8, AV_PIX_FMT_NONE };

        if (av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN) < 0)
            throw px::GIF::Error("Failed to set the output pixel format.", "GIF::FFMPEG init_filters");

        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;

        // GIF has possible output of PAL8. 
        if (avfilter_graph_parse_ptr(filter_graph, m_filter_desc.c_str(), &inputs, &outputs, nullptr) < 0)
            throw px::GIF::Error("Failed to parse the filter graph (bad string!).", "GIF::FFMPEG init_filters");

        if (avfilter_graph_config(filter_graph, nullptr) < 0)
            throw px::GIF::Error("Failed to configure the filter graph (bad string!).", "GIF::FFMPEG init_filters");

        avfilter_inout_free(&inputs);
        avfilter_inout_free(&outputs);
    }
    catch (const std::exception& e)
    {
        // Catch exception to delete element.
        avfilter_inout_free(&inputs);
        avfilter_inout_free(&outputs);
        throw e; // re-throuw
    }
}


void px::GIF::FFMPEG::init_muxer()
{
    AVOutputFormat* o_fmt = av_guess_format("gif", m_filename.c_str(), "video/gif");

    if ((_ret = avformat_alloc_output_context2(&ofmt_ctx, o_fmt, "gif", m_filename.c_str())) < 0)
        throw px::GIF::Error(std::string(av_make_error_string(_err_msg, FFMPEG_MSG_LEN, _ret)) + " allocate output format.", "GIF::FFMPEG init_muxer");

    AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_GIF);
    if (!codec) throw px::GIF::Error("Could to find the 'GIF' codec.", "GIF::FFMPEG init_muxer");

#if 0
    const AVPixelFormat* p = codec->pix_fmts;
    while (p != NULL && *p != AV_PIX_FMT_NONE) {
        printf("supported pix fmt: %s\n", av_get_pix_fmt_name(*p));
        ++p;
    }
#endif

    AVStream* stream = avformat_new_stream(ofmt_ctx, codec);

    AVCodecParameters* codec_paramters = stream->codecpar;
    codec_paramters->codec_tag = 0;
    codec_paramters->codec_id = codec->id;
    codec_paramters->codec_type = AVMEDIA_TYPE_VIDEO;
    codec_paramters->width = m_width;
    codec_paramters->height = m_height;
    codec_paramters->format = AV_PIX_FMT_PAL8;

    o_codec_ctx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(o_codec_ctx, codec_paramters);

    o_codec_ctx->time_base = { 1, m_framerate };

    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
        o_codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    if ((_ret = avcodec_open2(o_codec_ctx, codec, NULL)) < 0)
        throw px::GIF::Error(std::string(av_make_error_string(_err_msg, FFMPEG_MSG_LEN, _ret)) + " open output codec.", "GIF::FFMPEG init_muxer");

    if ((_ret = avio_open(&ofmt_ctx->pb, m_filename.c_str(), AVIO_FLAG_WRITE)) < 0)
        throw px::GIF::Error(std::string(av_make_error_string(_err_msg, FFMPEG_MSG_LEN, _ret)) + " avio open error.", "GIF::FFMPEG init_muxer");

    if ((_ret = avformat_write_header(ofmt_ctx, NULL)) < 0)
        throw px::GIF::Error(std::string(av_make_error_string(_err_msg, FFMPEG_MSG_LEN, _ret)) + " write GIF header", "GIF::FFMPEG init_muxer");

#if _DEBUG
    // This print the stream/output format.
    av_dump_format(ofmt_ctx, -1, m_filename.c_str(), 1);
#endif
}

添加帧(通常是在循环中)

void px::GIF::FFMPEG::add_frame(pxImage * const img)
{
    if (img->getImageType() != PXT_BYTE || img->getNChannels() != 4)
        throw px::GIF::Error("Failed to 'add_frame' since image is not PXT_BYTE and 4-channels.", "GIF::FFMPEG add_frame");

    if (img->getWidth() != m_width || img->getHeight() != m_height)
        throw px::GIF::Error("Failed to 'add_frame' since the size is not same to other inputs.", "GIF::FFMPEG add_frame");

    const int pitch = picture_rgb24->linesize[0];
    auto px_ptr = getImageAccessor<pxUChar_C4>(img);

    for (int y = 0; y < m_height; y++)
    {
        const int px_row = img->getOrigin() == ORIGIN_BOT_LEFT ? m_height - y - 1 : y;
        for (int x = 0; x < m_width; x++)
        {
            const int idx = y * pitch + 3 * x;
            picture_rgb24->data[0][idx] = px_ptr[px_row][x].ch[PX_RE];
            picture_rgb24->data[0][idx + 1] = px_ptr[px_row][x].ch[PX_GR];
            picture_rgb24->data[0][idx + 2] = px_ptr[px_row][x].ch[PX_BL];
        }
    }

    // palettegen need a whole stream, just add frame to buffer.
    if ((_ret = av_buffersrc_add_frame_flags(buffersrc_ctx, picture_rgb24, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0)
        throw px::GIF::Error("Failed to 'add_frame' to global buffer with error: " +
                             std::string(av_make_error_string(_err_msg, FFMPEG_MSG_LEN, _ret)),
                             "GIF::FFMPEG add_frame");

    // Increment the FPS of the picture for the next add-up to the buffer.      
    picture_rgb24->pts += m_pts_increment;

    as_frame = true;
}    

编码器(最后一步)

void px::GIF::FFMPEG::encode()
{
    if (!as_frame)
        throw px::GIF::Error("Please 'add_frame' before running the Encoding().", "GIF::FFMPEG encode");

    // end of buffer
    if ((_ret = av_buffersrc_add_frame_flags(buffersrc_ctx, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0)
        throw px::GIF::Error("error add frame to buffer source: " + std::string(av_make_error_string(_err_msg, FFMPEG_MSG_LEN, _ret)), "GIF::FFMPEG encode");

    do {
        AVFrame* filter_frame = av_frame_alloc();
        _ret = av_buffersink_get_frame(buffersink_ctx, filter_frame);
        if (_ret == AVERROR(EAGAIN) || _ret == AVERROR_EOF) {
            av_frame_unref(filter_frame);
            break;
        }

        // write the filter frame to output file
        muxing_one_frame(filter_frame);

        av_frame_unref(filter_frame);
    } while (_ret >= 0);

    av_write_trailer(ofmt_ctx);
}

void px::GIF::FFMPEG::muxing_one_frame(AVFrame* frame)
{
    int ret = avcodec_send_frame(o_codec_ctx, frame);
    AVPacket *pkt = av_packet_alloc();
    av_init_packet(pkt);

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

        av_write_frame(ofmt_ctx, pkt);
    }
    av_packet_unref(pkt);
}

DTOR,释放并取消

px::GIF::FFMPEG::~FFMPEG()
{
    release();
}


void px::GIF::FFMPEG::release()
{
    // Muxer stuffs
    if (ofmt_ctx != nullptr) avformat_free_context(ofmt_ctx);
    if (o_codec_ctx != nullptr) avcodec_close(o_codec_ctx);
    if (o_codec_ctx != nullptr) avcodec_free_context(&o_codec_ctx);

    ofmt_ctx = nullptr;
    o_codec_ctx = nullptr;

    // Filter stuffs
    if (buffersrc_ctx != nullptr) avfilter_free(buffersrc_ctx);
    if (buffersink_ctx != nullptr) avfilter_free(buffersink_ctx);
    if (filter_graph != nullptr) avfilter_graph_free(&filter_graph);

    buffersrc_ctx = nullptr;
    buffersink_ctx = nullptr;
    filter_graph = nullptr;

    // Conversion image.
    if (picture_rgb24 != nullptr) av_frame_free(&picture_rgb24);
    picture_rgb24 = nullptr;
}

void px::GIF::FFMPEG::cancel()
{
    // In-case of failure we must close ffmpeg and exit.
    av_write_trailer(ofmt_ctx);

    // Release and close all elements.
    release();

    // Delete the file on disk.
    if (remove(m_filename.c_str()) != 0)
        PX_LOG0(PX_LOGLEVEL_ERROR, "GIF::FFMPEG - On 'cancel' failed to remove the file.");
}
c++ c ffmpeg memory-leaks io
1个回答
0
投票

花了我一段时间,但终于明白了!

我在我的取消方法中缺少一个avio_close(ofmt_ctx->pb);

一旦从ffmpeg释放文件,std::remove()就像一个超级按钮。

注意,只有在成功执行av_write_trailer时才应调用avio_closeinit_muxer,因此我有一个成员变量来标记成功与否。然后,我在取消中进行适当的调用。

© www.soinside.com 2019 - 2024. All rights reserved.