zoukankan      html  css  js  c++  java
  • 1.FFmpeg通用函数解析

    参考链接:https://blog.csdn.net/leixiaohua1020/article/details/8661601

    本文函数目录:

    1.avformat_open_input()

     2.内存的分配与释放av_malloc()与av_free()等

     3.常见结构体的初始化和销毁(AVFormatContext,AVFrame等)

    4.avio_open2()

    5.avcodec_close()

     

    函数备注说明:

      avcodec_register_all()注册了和编解码器有关的组件:硬件加速器,解码器,编码器,Parser,Bitstream Filter。av_register_all()除了调用avcodec_register_all()之外,还注册了复用器,解复用器,协议处理器。使用ffmpeg4.0以上的版本,av_register_all()这个函数已经废弃,不用加了

      avcodec_register_all()

      av_register_all()调用了avcodec_register_all()。因此如果调用过av_register_all()的话就不需要再调用avcodec_register_all()了。

    1.avformat_open_input()

    函数的原型:

    int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options);

    AVFormatContext结构体的定义在avformat.h文件中1133行,包含有duration时长等参数,

    视频流的其他相关变量参数定义大多在avcode.c.h  avio.h  avformat.h  等文件中。

    显示视频流中相关参数可参考解码test工程decoder

    2.内存的分配与释放av_malloc()与av_free()

      av_malloc()是FFmpeg中最常见的内存分配函数。如果不考虑上述代码中的一大堆宏定义(即类似CONFIG_MEMALIGN_HACK这类的宏都采用默认值0),av_malloc()的代码可以简化成如下形式。

    void *av_malloc(size_t size)
    {
        void *ptr = NULL;
        /* let's disallow possibly ambiguous cases */
        if (size > (max_alloc_size - 32))
            return NULL;
        ptr = malloc(size);
        if(!ptr && !size) {
            size = 1;
            ptr= av_malloc(1);
        }
        return ptr;
    }

    可以看出,此时的av_malloc()就是简单的封装了系统函数malloc(),并做了一些错误检查工作。

    为了提高处理器对ffmpeg函数库的处理效率,需要使ffmpeg内存齐

      av_realloc()用于对申请的内存的大小进行调整。其简单封装了系统的realloc()函数。

      av_mallocz()可以理解为av_malloc()+zeromemory。

    void *av_mallocz(size_t size)
    {
        void *ptr = av_malloc(size);
        if (ptr)
            memset(ptr, 0, size);
        return ptr;
    }

    从源代码可以看出av_mallocz()中调用了av_malloc()之后,又调用memset()将分配的内存设置为0。

      av_calloc()简单封装了av_mallocz(),它调用av_mallocz()分配了nmemb*size个字节的内存。

      av_free()用于释放申请的内存。释放后要记得将目标指针指向设置为NULL(可直接通过调用av_freep()实现)

      av_freep()简单封装了av_free()。并且在释放内存之后将目标指针设置为NULL。

    3.常见结构体的初始化和销毁(AVFormatContext,AVFrame等)

    AVFormatContext:统领全局的基本结构体。主要用于处理封装格式(FLV/MKV/RMVB等)。

    AVIOContext:输入输出对应的结构体,用于输入输出(读写文件,RTMP协议等)。

    AVStream,AVCodecContext:视音频流对应的结构体,用于视音频编解码。

    AVFrame:存储非压缩的数据(视频对应RGB/YUV像素数据,音频对应PCM采样数据)

    AVPacket:存储压缩数据(视频对应H.264等码流数据,音频对应AAC/MP3等码流数据)

    下文简单分析一下上述几个结构体的初始化和销毁函数。这些函数列表如下。

    AVFrame的初始化函数是av_frame_alloc(),销毁函数是av_frame_free()。在这里有一点需要注意,旧版的FFmpeg都是使用avcodec_alloc_frame()初始化AVFrame的,而现在avcodec_alloc_frame()已经被标记为“过时的”了,为了保证与时俱进,决定分析新的API——av_frame_alloc()。
      av_frame_alloc()的定义位于libavutilframe.c。从代码可以看出,av_frame_alloc()首先调用av_mallocz()为AVFrame结构体分配内存。而后调用了一个函数get_frame_defaults()用于设置一些默认参数。

      补充下吧,AVPacket 是一个 struct, 里面的成员可能包含了分配了内存的指针(或者指向 ref counted buffer)。av_free_packet (已过时)已被 av_packet_unref 取代。这两个函数本质上都是释放 AVPacket 里的指针变量成员所指向的内存。当然,av_free_packet / av_packet_unref 这两个函数的操作都和 AVPacket 结构体自身占用的内存无关。

    4.avio_open2()

      函数原型为  int avio_open2(AVIOContext **s, const char *url, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options);

    关于此函数的经典应用如下:https://blog.csdn.net/leixiaohua1020/article/details/25430425  函数参数解释如下:  

    s:函数调用成功之后创建的AVIOContext结构体。
    url:输入输出协议的地址(文件也是一种“广义”的协议,对于文件来说就是文件的路径)。
    flags:打开地址的方式。可以选择只读,只写,或者读写。取值如下。
    AVIO_FLAG_READ:只读。
    AVIO_FLAG_WRITE:只写。
    AVIO_FLAG_READ_WRITE:读写。
    int_cb:目前还没有用过。
    options:目前还没有用过。

      有一个和avio_open2()“长得很像”的函数avio_open(),应该是avio_open2()的早期版本。avio_open()比avio_open2()少了最后2个参数。而它前面几个参数的含义和avio_open2()是一样的。从源代码中可以看出,avio_open()内部调用了avio_open2(),并且把avio_open2()的后2个参数设置成了NULL,因此它的功能实际上和avio_open2()是一样的。
      avio_open2()的源代码,位于libavformataviobuf.c文件中,

    从avio_open2()的源代码可以看出,它主要调用了2个函数:ffurl_open()和ffio_fdopen()。其中ffurl_open()用于初始化URLContext,ffio_fdopen()用于根据URLContext初始化AVIOContext。URLContext中包含的URLProtocol完成了具体的协议读写等工作。AVIOContext则是在URLContext的读写函数外面加上了一层“包装”(通过retry_transfer_wrapper()函数)。
      在查看ffurl_open()和ffio_fdopen()函数之前,首先查看一下URLContext和URLProtocol的定义。这两个结构体在FFmpeg的早期版本的SDK中是定义在头文件中可以直接使用的。但是近期的FFmpeg的SDK中已经找不到这两个结构体的定义了。FFmpeg把这两个结构体移动到了源代码的内部,变成了内部结构体。URLProtocol的定义位于libavformaturl.h中

      从URLProtocol的定义可以看出,其中包含了用于协议读写的函数指针。例如:

    url_open():打开协议。
    url_read():读数据。
    url_write():写数据。
    url_close():关闭协议。
    每种具体的协议都包含了一个URLProtocol结构体,例如:
    FILE协议(“文件”在FFmpeg中也被当做一种协议)的结构体ff_file_protocol的定义如下所示(位于libavformatfile.c)
      
    在使用FILE协议进行读写的时候,调用url_open()实际上就是调用了file_open()函数,这里限于篇幅不再对file_open()的源代码进行分析。file_open()函数实际上调用了系统的打开文件函数open()。同理,调用url_read()实际上就是调用了file_read()函数;file_read()函数实际上调用了系统的读取文件函数read()。url_write(),url_seek()等函数的道理都是一样的。

      

    ffurl_open()
      前文提到AVIOContext中主要调用了2个函数:ffurl_open()和ffio_fdopen()。其中ffurl_open()用于初始化URLContext,ffio_fdopen()用于根据URLContext初始化AVIOContext。下面首先看一下初始化URLContext的函数ffurl_open()。
    ffurl_open()的函数定义位于libavformatavio.c中,

      从代码中可以看出,ffurl_open()主要调用了2个函数:ffurl_alloc()和ffurl_connect()。ffurl_alloc()用于查找合适的URLProtocol,并创建一个URLContext;ffurl_connect()用于打开获得的URLProtocol。

    ffurl_alloc()

      ffurl_alloc()的定义位于libavformatavio.c中,ffurl_alloc()主要调用了2个函数:url_find_protocol()根据文件路径查找合适的URLProtocol,url_alloc_for_protocol()为查找到的URLProtocol创建URLContext。

      这里有一种例外,那就是文件路径。“文件”在FFmpeg中也是一种“协议”,并且前缀是“file”。也就是标准的文件路径应该是“file://...”格式的。但是这太不符合我们一般人的使用习惯,我们一般是不会在文件路径前面加上“file”协议名称的。所以该函数采取的方法是:一旦检测出来输入的URL是文件路径而不是网络协议,就自动向proto_str中拷贝“file”。其中判断文件路径那里有一个很复杂的if()语句。根据我的理解,“||”前面的语句用于判断是否是相对文件路径,“||”后面的语句用于判断是否是绝对路径。判断绝对路径的时候用到了一个函数is_dos_path(),定义位于libavformatos_support.h,

      注意“&&”优先级低于“==”。如果文件路径第1个字符不为空(一般情况下是盘符)而且第2个字符为“:”,就认为它是绝对文件路径。

    此外url_find_protocol()函数中还涉及到一个函数ffurl_protocol_next()。该函数用于获得下一个URLProtocol(所有的URLProtocol在FFmpeg初始化注册的时候形成一个链表结构)。

      url_alloc_for_protocol()的定义位于libavformatavio.c中,

    url_alloc_for_protocol()完成了以下步骤:首先,检查输入的URLProtocol是否支持指定的flag。比如flag中如果指定了AVIO_FLAG_READ,则URLProtocol中必须包含url_read();如果指定了AVIO_FLAG_WRITE,则URLProtocol中必须包含url_write()。在检查无误之后,接着就可以调用av_mallocz()为即将创建的URLContext分配内存了。
    接下来基本上就是各种赋值工作。

      

    ffurl_connect()

      ffurl_connect()用于打开获得的URLProtocol。该函数的定义位于libavformatavio.c中,

    int ffurl_connect(URLContext *uc, AVDictionary **options)
    {
        int err =
            uc->prot->url_open2 ? uc->prot->url_open2(uc,
                                                      uc->filename,
                                                      uc->flags,
                                                      options) :
            uc->prot->url_open(uc, uc->filename, uc->flags);
        if (err)
            return err;
        uc->is_connected = 1;
        /* We must be careful here as ffurl_seek() could be slow,
         * for example for http */
        if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file"))
            if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0)
                uc->is_streamed = 1;
        return 0;
    }

      该函数中最重要的函数就是它的第一句:URLProtocol中是否包含url_open2()?如果包含的话,就调用url_open2(),否则就调用url_open()。

    url_open()本身是URLProtocol的一个函数指针,这个地方根据不同的协议调用的url_open()具体实现函数也是不一样的,例如file协议的url_open()对应的是file_open(),而file_open()最终调用了_wsopen(),_sopen()(Windows下)或者open()(Linux下,类似于fopen())这样的系统中打开文件的API函数;而libRTMP的url_open()对应的是rtmp_open(),而rtmp_open()最终调用了libRTMP的API函数RTMP_Init(),RTMP_SetupURL(),RTMP_Connect() 以及RTMP_ConnectStream()。
      

    ffio_fdopen()

      ffio_fdopen()使用已经获得的URLContext初始化AVIOContext。它的函数定义位于libavformataviobuf.c中,ffio_fdopen()函数首先初始化AVIOContext中的Buffer。如果URLContext中设置了max_packet_size,则将Buffer的大小设置为max_packet_size。如果没有设置的话(似乎大部分URLContext都没有设置该值),则会分配IO_BUFFER_SIZE个字节给Buffer。IO_BUFFER_SIZE取值为32768。

      

    avio_alloc_context()

      ffio_fdopen()中接下来会调用avio_alloc_context()初始化一个AVIOContext。avio_alloc_context()本身是一个FFmpeg的API函数。它的声明位于libavformatavio.h中,

      函数原型为:

    AVIOContext *avio_alloc_context(
    unsigned char *buffer,
    int buffer_size,
    int write_flag,
    void *opaque,
    int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
    int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
    int64_t (*seek)(void *opaque, int64_t offset, int whence));

      avio_alloc_context()看上去参数很多,但实际上并不复杂。先简单解释一下它各个参数的含义:

    buffer:AVIOContext中的Buffer。
    buffer_size:AVIOContext中的Buffer的大小。
    write_flag:设置为1则Buffer可写;否则Buffer只可读。
    opaque:用户自定义数据。
    read_packet():读取外部数据,填充Buffer的函数。
    write_packet():向Buffer中写入数据的函数。
    seek():用于Seek的函数。

      该函数成功执行的话则会返回一个创建好的AVIOContext。
    下面看一下avio_alloc_context()的定义,位于libavformataviobuf.c,该函数代码很简单:首先调用av_mallocz()为AVIOContext分配一块内存空间,然后基本上将所有输入参数传递给ffio_init_context()。ffio_init_context()这个函数的工作就是各种赋值,不算很有“技术含量”,不再详述。

      

    ffurl_read(),ffurl_write(),ffurl_seek()
      现在我们再回到ffio_fdopen(),会发现它初始化AVIOContext的结构体的时候,首先将自己分配的Buffer设置为该AVIOContext的Buffer;然后将URLContext作为用户自定义数据(对应AVIOContext的opaque变量)提供给该AVIOContext;最后分别将3个函数作为该AVIOContext的读,写,跳转函数:ffurl_read(),ffurl_write(),ffurl_seek()。下面我们选择一个ffurl_read()看看它的定义。
    ffurl_read()的定义位于libavformatavio.c,如下所示。

    int ffurl_read(URLContext *h, unsigned char *buf, int size)
    {
    if (!(h->flags & AVIO_FLAG_READ))
    return AVERROR(EIO);
    return retry_transfer_wrapper(h, buf, size, 1, h->prot->url_read);
    }

    该函数先判断了一下输入的URLContext是否支持“读”操作,接着调用了一个函数:retry_transfer_wrapper()。
    如果我们看ffurl_write()的代码,如下所示。

    int ffurl_write(URLContext *h, const unsigned char *buf, int size)
    {
    if (!(h->flags & AVIO_FLAG_WRITE))
    return AVERROR(EIO);
    /* avoid sending too big packets */
    if (h->max_packet_size && size > h->max_packet_size)
    return AVERROR(EIO);
    
    
    return retry_transfer_wrapper(h, (unsigned char *)buf, size, size, (void*)h->prot->url_write);
    }

    会发现他也调用了同样的一个函数retry_transfer_wrapper()。唯一的不同在于ffurl_read()调用retry_transfer_wrapper()的时候,最后一个参数是URLProtocol的url_read(),而ffurl_write()调用retry_transfer_wrapper()的时候,最后一个参数是URLProtocol的url_write()。
    下面我们看一下retry_transfer_wrapper()的定义,位于libavformatavio.c,如下所示。

    static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,
    int size, int size_min,
    int (*transfer_func)(URLContext *h,
    uint8_t *buf,
    int size))
    {
    int ret, len;
    int fast_retries = 5;
    int64_t wait_since = 0;
    
    
    len = 0;
    while (len < size_min) {
    if (ff_check_interrupt(&h->interrupt_callback))
    return AVERROR_EXIT;
    ret = transfer_func(h, buf + len, size - len);
    if (ret == AVERROR(EINTR))
    continue;
    if (h->flags & AVIO_FLAG_NONBLOCK)
    return ret;
    if (ret == AVERROR(EAGAIN)) {
    ret = 0;
    if (fast_retries) {
    fast_retries--;
    } else {
    if (h->rw_timeout) {
    if (!wait_since)
    wait_since = av_gettime_relative();
    else if (av_gettime_relative() > wait_since + h->rw_timeout)
    return AVERROR(EIO);
    }
    av_usleep(1000);
    }
    } else if (ret < 1)
    return (ret < 0 && ret != AVERROR_EOF) ? ret : len;
    if (ret)
    fast_retries = FFMAX(fast_retries, 2);
    len += ret;
    }
    return len;
    }

    从代码中可以看出,它的核心实际上是调用了一个名称为transfer_func()的函数。而该函数就是retry_transfer_wrapper()的第四个参数。该函数实际上是对URLProtocol的读写操作中的错误进行了一些“容错”处理,可以让数据的读写更加的稳定。

    avio_alloc_context()执行完毕后,ffio_fdopen()函数的工作就基本完成了,avio_open2()的工作也就做完了。

    5.avcodec_close()

      avcodec_close()的定义位于libavcodecutils.c,该函数只有一个参数,就是需要关闭的编码器的AVCodecContext。

      从avcodec_close()的定义可以看出,该函数释放AVCodecContext中有关的变量,并且调用了AVCodec的close()关闭了解码器。

    AVCodec->close()
    AVCodec的close()是一个函数指针,指向了特定编码器的关闭函数。在这里我们以libx264为例,看一下它对应的AVCodec的结构体的定义,如下所示。

    AVCodec ff_libx264_encoder = {
    .name = "libx264",
    .long_name = NULL_IF_CONFIG_SMALL("libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .type = AVMEDIA_TYPE_VIDEO,
    .id = AV_CODEC_ID_H264,
    .priv_data_size = sizeof(X264Context),
    .init = X264_init,
    .encode2 = X264_frame,
    .close = X264_close,
    .capabilities = CODEC_CAP_DELAY | CODEC_CAP_AUTO_THREADS,
    .priv_class = &x264_class,
    .defaults = x264_defaults,
    .init_static_data = X264_init_static,
    };

    从ff_libx264_encoder的定义可以看出:close()函数对应的是X264_close()函数。继续看一下X264_close()函数的定义,如下所示。

    static av_cold int X264_close(AVCodecContext *avctx)
    {
    X264Context *x4 = avctx->priv_data;
    
    av_freep(&avctx->extradata);
    av_freep(&x4->sei);
    //关闭编码器
    if (x4->enc)
    x264_encoder_close(x4->enc);
    
    av_frame_free(&avctx->coded_frame);
    
    return 0;
    }

    从X264_close()的定义可以看出,该函数调用了libx264的x264_encoder_close()关闭了libx264编码器。

      

     

     

      

      

      

  • 相关阅读:
    关于Java常见的误解
    Java程序设计概述
    是结束,更是开始!
    从零开始单排学设计模式「简单工厂设计模式」黑铁 III
    某神秘公司 RESTful、共用接口、前后端分离、接口约定的实践
    这40张图送给单身程序员,情人节请一笑而过!
    科技圈晒开工福利!2019一起定个小目标!
    IDEA一定要懂的32条快捷键
    假期结束了,我相信未来会更好!
    有一种痛,叫 “今年没有年终奖”!!!
  • 原文地址:https://www.cnblogs.com/wddx5/p/13297759.html
Copyright © 2011-2022 走看看