FFmpeg源代码简单分析:avformat_close_input()

本文简单分析FFmpeg的avformat_close_input()函数。该函数用于关闭一个AVFormatContext,一般情况下是和avformat_open_input()成对使用的。

/**
 * Close an opened input AVFormatContext. Free it and all its contents
 * and set *s to NULL.
 */
void avformat_close_input(AVFormatContext **s);

函数调用关系图

函数的调用关系如下图所示。
在这里插入图片描述

avformat_close_input()

下面看一下avformat_close_input()的源代码,位于libavformat\utils.c文件中。

void avformat_close_input(AVFormatContext **ps)
{
    AVFormatContext *s;
    AVIOContext *pb;

    if (!ps || !*ps)
        return;

    s  = *ps;
    pb = s->pb;

    if ((s->iformat && strcmp(s->iformat->name, "image2") && s->iformat->flags & AVFMT_NOFILE) ||
        (s->flags & AVFMT_FLAG_CUSTOM_IO))
        pb = NULL;

    flush_packet_queue(s);

    if (s->iformat)
        if (s->iformat->read_close)
            s->iformat->read_close(s);

    avformat_free_context(s);

    *ps = NULL;

    avio_close(pb);
}

从源代码中可以看出,avformat_close_input()主要做了以下几步工作:
(1)调用AVInputFormat的read_close()方法关闭输入流
(2)调用avformat_free_context()释放AVFormatContext
(3)调用avio_close()关闭并且释放AVIOContext
下面我们分别来看上述几个步骤。

AVInputFormat-> read_close()

AVInputFormat的read_close()是一个函数指针,指向关闭输入流的函数。不同的AVInputFormat包含有不同的read_close()方法。例如,FLV格式对应的AVInputFormat的定义如下,位于avformat/flvdec.c。

AVInputFormat ff_flv_demuxer = {
    .name           = "flv",
    .long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    .priv_data_size = sizeof(FLVContext),
    .read_probe     = flv_probe,
    .read_header    = flv_read_header,
    .read_packet    = flv_read_packet,
    .read_seek      = flv_read_seek,
    .read_close     = flv_read_close,
    .extensions     = "flv",
    .priv_class     = &flv_class,
};

从ff_flv_demuxer的定义中可以看出,read_close()指向的函数是flv_read_close()。我们可以看一下flv_read_close()的定义,如下所示。

static int flv_read_close(AVFormatContext *s)
{
    int i;
    FLVContext *flv = s->priv_data;
    for (i=0; i<FLV_STREAM_TYPE_NB; i++)
        av_freep(&flv->new_extradata[i]);
    av_freep(&flv->keyframe_times);
    av_freep(&flv->keyframe_filepositions);
    return 0;
}

从flv_read_close()的定义可以看出,该函数释放了FLVContext中的new_extradata数组中每个元素指向的内存。

avformat_free_context()

avformat_free_context()是一个FFmpeg的API函数,用于释放一个AVFormatContext。在这里要注意搞清楚avformat_free_context()和avformat_close_input()之间的区别与联系。
有关avformat_free_context()可以参考文章:

FFmpeg源代码简单分析:内存的分配和释放

avio_close()

avio_close()是一个FFmpeg的API函数,用于关闭和释放AVIOContext。它的声明位于libavformat\avio.h,如下所示。

/**
 * Close the resource accessed by the AVIOContext s and free it.
 * This function can only be used if s was opened by avio_open().
 *
 * The internal buffer is automatically flushed before closing the
 * resource.
 *
 * @return 0 on success, an AVERROR < 0 on error.
 * @see avio_closep
 */
int avio_close(AVIOContext *s);

avio_close()的定义位于libavformat\aviobuf.c,如下所示。

int avio_close(AVIOContext *s)
{
    URLContext *h;

    if (!s)
        return 0;

    avio_flush(s);
    h         = s->opaque;
    s->opaque = NULL;

    av_freep(&s->buffer);
    if (s->write_flag)
        av_log(s, AV_LOG_VERBOSE, "Statistics: %d seeks, %d writeouts\n", s->seek_count, s->writeout_count);
    else
        av_log(s, AV_LOG_VERBOSE, "Statistics: %"PRId64" bytes read, %d seeks\n", s->bytes_read, s->seek_count);
    av_opt_free(s);

    avio_context_free(&s);

    return ffurl_close(h);
}

从源代码可以看出,avio_close()按照顺序做了以下几个步骤:

(1)调用avio_flush()强制清除缓存中的数据
(2)调用av_freep()释放掉AVIOContext种的buffer
(3)调用av_opt_free()释放掉AVIOContext 所有分配的对象
(4)调用avio_context_free()释放掉AVIOContext结构体
(5)调用ffurl_close()关闭并且释放掉URLContext

下面按照顺序分别看看avio_flush()和ffurl_close()这两个函数。

avio_flush()

avio_flush()是一个FFmpeg的API函数,声明位于libavformat\avio.h,如下所示。

/**
 * Force flushing of buffered data.
 *
 * For write streams, force the buffered data to be immediately written to the output,
 * without to wait to fill the internal buffer.
 *
 * For read streams, discard all currently buffered data, and advance the
 * reported file position to that of the underlying stream. This does not
 * read new data, and does not perform any seeks.
 */
void avio_flush(AVIOContext *s);

avio_flush()的定义位于libavformat\aviobuf.c,如下所示。

void avio_flush(AVIOContext *s)
{
    int seekback = s->write_flag ? FFMIN(0, s->buf_ptr - s->buf_ptr_max) : 0;
    flush_buffer(s);
    if (seekback)
        avio_seek(s, seekback, SEEK_CUR);
}

可以看出avio_flush()简单调用了flush_buffer()函数。我们看一下flush_buffer()的定义。

static void flush_buffer(AVIOContext *s)
{
    s->buf_ptr_max = FFMAX(s->buf_ptr, s->buf_ptr_max);
    if (s->write_flag && s->buf_ptr_max > s->buffer) {
        writeout(s, s->buffer, s->buf_ptr_max - s->buffer);
        if (s->update_checksum) {
            s->checksum     = s->update_checksum(s->checksum, s->checksum_ptr,
                                                 s->buf_ptr_max - s->checksum_ptr);
            s->checksum_ptr = s->buffer;
        }
    }
    s->buf_ptr = s->buf_ptr_max = s->buffer;
    if (!s->write_flag)
        s->buf_end = s->buffer;
}

从flush_buffer()定义我们可以看出,该函数将当前缓存指针buf_ptr的位置重新设置到缓存buffer的首部,然后根据AVIOContext对应的流是否可写分别做不同的处理。如果AVIOContext对应的流是只读的(write_flag取值为0),就将缓存的尾部buf_end设定到缓存首部位置;如果AVIOContext对应的流如果是可写的(write_flag取值非0),则会调用writeout()函数输出缓存中剩余的数据。
在这里我们看一下writeout()函数的定义,如下所示。

static void writeout(AVIOContext *s, const uint8_t *data, int len)
{
    if (!s->error) {
        int ret = 0;
        if (s->write_data_type)
            ret = s->write_data_type(s->opaque, (uint8_t *)data,
                                     len,
                                     s->current_type,
                                     s->last_time);
        else if (s->write_packet)
            ret = s->write_packet(s->opaque, (uint8_t *)data, len);
        if (ret < 0) {
            s->error = ret;
        } else {
            if (s->pos + len > s->written)
                s->written = s->pos + len;
        }
    }
    if (s->current_type == AVIO_DATA_MARKER_SYNC_POINT ||
        s->current_type == AVIO_DATA_MARKER_BOUNDARY_POINT) {
        s->current_type = AVIO_DATA_MARKER_UNKNOWN;
    }
    s->last_time = AV_NOPTS_VALUE;
    s->writeout_count ++;
    s->pos += len;
}

ffurl_close()

ffurl_close()和ffurl_closep()是FFmpeg内部的两个函数,它们的声明位于libavformat\url.h,如下所示。

/**
 * Close the resource accessed by the URLContext h, and free the
 * memory used by it. Also set the URLContext pointer to NULL.
 *
 * @return a negative value if an error condition occurred, 0
 * otherwise
 */
int ffurl_closep(URLContext **h);
int ffurl_close(URLContext *h);

ffurl_close()定义如下:

int ffurl_close(URLContext *h)
{
    return ffurl_closep(&h);
}

ffurl_close()调用了ffurl_closep()函数,ffurl_closep()定义如下:

int ffurl_closep(URLContext **hh)
{
    URLContext *h= *hh;
    int ret = 0;
    if (!h)
        return 0;     /* can happen when ffurl_open fails */

    if (h->is_connected && h->prot->url_close)
        ret = h->prot->url_close(h);

    if (h->prot->priv_data_size) {
        if (h->prot->priv_data_class)
            av_opt_free(h->priv_data);
        av_freep(&h->priv_data);
    }
    av_opt_free(h);
    av_freep(hh);
    return ret;
}

从ffurl_closep()的定义可以看出,它主要做了两步工作:

(1)调用URLProtocol的url_close()
(2)调用av_freep()释放URLContext结构体

其中URLProtocol的url_close()是一个函数指针,其指向的函数与具体的URLProtocol有关,这里我们还是看一下file(文件)对应的URLProtocol,如下所示。

const URLProtocol ff_file_protocol = {
    .name                = "file",
    .url_open            = file_open,
    .url_read            = file_read,
    .url_write           = file_write,
    .url_seek            = file_seek,
    .url_close           = file_close,
    .url_get_file_handle = file_get_handle,
    .url_check           = file_check,
    .url_delete          = file_delete,
    .url_move            = file_move,
    .priv_data_size      = sizeof(FileContext),
    .priv_data_class     = &file_class,
    .url_open_dir        = file_open_dir,
    .url_read_dir        = file_read_dir,
    .url_close_dir       = file_close_dir,
    .default_whitelist   = "file,crypto,data"
};

从ff_file_protocol中可以看出,url_close()指向file_close()函数。我们再看一下file_close()的定义,如下所示。

static int file_close(URLContext *h)
{
    FileContext *c = h->priv_data;
    return close(c->fd);
}

可见file_close()最终调用了系统函数close()关闭了文件指针(不熟悉close()的可以简单把它理解为fclose())。
至此avio_close()函数分析完毕。

参考文献

1、https://blog.csdn.net/leixiaohua1020/article/details/44110683

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 为视频加水印可以使用FFmpeg中的`overlay`过滤器。以下是使用纯C代码实现的示例: ``` AVFilterContext *buffersrc_ctx; AVFilterContext *buffersink_ctx; AVFilterGraph *filter_graph; AVFilter *buffersrc = avfilter_get_by_name("buffer"); AVFilter *buffersink = avfilter_get_by_name("buffersink"); AVFilterInOut *outputs = avfilter_inout_alloc(); AVFilterInOut *inputs = avfilter_inout_alloc(); const char *filter_descr = "overlay=10:10"; int ret; filter_graph = avfilter_graph_alloc(); if (!outputs || !inputs || !filter_graph) { ret = AVERROR(ENOMEM); goto end; } // 添加 buffer 过滤器到过滤器图中 ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", "video_size=1920x1080:pix_fmt=0:time_base=1/25", NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n"); goto end; } // 添加 buffersink 过滤器到过滤器图中 ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n"); goto end; } // 设置 filter_graph 的输入输出链 outputs->name = av_strdup("in"); outputs->filter_ctx = buffersrc_ctx; outputs->pad_idx = 0; outputs->next = NULL; inputs->name = av_strdup("out"); inputs->filter_ctx = buffersink_ctx; inputs->pad_idx = 0; inputs->next = NULL; // 添加 filter 到 filter_graph 中 ret = avfilter_graph_parse_ptr(filter_graph, filter_descr, &inputs, &outputs, NULL); if (ret < 0) { goto end; } // 链接 filter ret = avfilter_graph_config(filter_graph, NULL); if (ret < 0) { goto end; } end: avfilter_inout_free(&inputs); avfilter_inout_free(&outputs); avfilter_graph_free(&filter_graph); ``` 以上代码将在视频的左上角添加一个10像素的水印。可以通过修改`filter_descr`来调整水印的位置和大小。 ### 回答2: 在使用 Ffmpeg 为视频加水印时,可以通过 Filter 部分的代码来实现。 首先,需要引入 Ffmpeg 头文件和相关的库文件: ```c extern "C" { #include <libavfilter/avfilter.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> } #pragma comment(lib, "libavfilter.lib") #pragma comment(lib, "libavdevice.lib") #pragma comment(lib, "libavformat.lib") #pragma comment(lib, "libavcodec.lib") #pragma comment(lib, "libswscale.lib") ``` 然后,可以定义一个函数来实现添加水印的功能,如下所示: ```c void addWatermark(const char* inputFileName, const char* watermarkFileName, const char* outputFileName) { AVFilterContext* buffersink_ctx; AVFilterContext* buffersrc_ctx; AVFilterGraph* filter_graph; AVFormatContext* format_ctx; AVPacket packet; int ret; // 打开输入文件 ret = avformat_open_input(&format_ctx, inputFileName, 0, 0); if (ret < 0) { // 处理打开输入文件失败的情况 return; } // 查找流信息 ret = avformat_find_stream_info(format_ctx, 0); if (ret < 0) { // 处理查找流信息失败的情况 avformat_close_input(&format_ctx); return; } // 创建图像过滤器核心 filter_graph = avfilter_graph_alloc(); if (!filter_graph) { // 处理创建图像过滤器核心失败的情况 avformat_close_input(&format_ctx); return; } // 打开输入过滤器源(Source) const AVFilter* buffersrc = avfilter_get_by_name("buffer"); buffersrc_ctx = avfilter_graph_alloc_filter(filter_graph, buffersrc, "src"); if (!buffersrc_ctx) { // 处理打开输入过滤器源失败的情况 avfilter_graph_free(&filter_graph); avformat_close_input(&format_ctx); return; } // 设置输入过滤器源的参数 av_opt_set_int_list(buffersrc_ctx, "pix_fmts", fmts, -1, AV_OPT_SEARCH_CHILDREN); av_opt_set_int(buffersrc_ctx, "width", format_ctx->streams[0]->codecpar->width, AV_OPT_SEARCH_CHILDREN); av_opt_set_int(buffersrc_ctx, "height", format_ctx->streams[0]->codecpar->height, AV_OPT_SEARCH_CHILDREN); // 打开输出过滤器接收器(Sink) const AVFilter* buffersink = avfilter_get_by_name("buffersink"); buffersink_ctx = avfilter_graph_alloc_filter(filter_graph, buffersink, "sink"); if (!buffersink_ctx) { // 处理打开输出过滤器接收器失败的情况 avfilter_graph_free(&filter_graph); avformat_close_input(&format_ctx); return; } // 初始化图像过滤器核心 ret = avfilter_graph_parse_ptr(filter_graph, filter_desc, &buffersrc_ctx, &buffersink_ctx, NULL); if (ret < 0) { // 处理初始化图像过滤器核心失败的情况 avfilter_graph_free(&filter_graph); avformat_close_input(&format_ctx); return; } // 链接图像过滤器核心 ret = avfilter_graph_config(filter_graph, NULL); if (ret < 0) { // 处理链接图像过滤器核心失败的情况 avfilter_graph_free(&filter_graph); avformat_close_input(&format_ctx); return; } // 读取水印文件 AVFormatContext* watermark_format_ctx; ret = avformat_open_input(&watermark_format_ctx, watermarkFileName, 0, 0); if (ret < 0) { // 处理读取水印文件失败的情况 avfilter_graph_free(&filter_graph); avformat_close_input(&format_ctx); return; } // 从水印文件中读取数据包 while (av_read_frame(watermark_format_ctx, &packet) >= 0) { // 对水印的数据包进行过滤 if (packet.stream_index == 0) { av_buffersrc_add_frame_flags(buffersrc_ctx, packet.data, AV_BUFFERSRC_FLAG_PUSH); } av_packet_unref(&packet); } // 处理输出过滤器接收器(Sink)的输出帧 while (1) { AVFrame* frame = av_frame_alloc(); ret = av_buffersink_get_frame(buffersink_ctx, frame); if (ret < 0) { // 处理处理输出过滤器接收器的输出帧失败的情况 av_frame_unref(frame); break; } // 对输出帧进行处理,例如进行水印贴图 // ... av_frame_unref(frame); } // 释放资源 avfilter_graph_free(&filter_graph); avformat_close_input(&format_ctx); avformat_close_input(&watermark_format_ctx); } ``` 以上是一个参考的基本代码示例,其中需要替换部分代码来符合实际需求。此外,添加水印的具体处理方法,例如进行水印贴图,需要根据实际情况实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值