zoukankan      html  css  js  c++  java
  • FFMPEG之协议(文件)操作----AVIOContext, URLContext, URLProtocol

    版权声明:本文为博主原创文章,未经博主允许不得转载。

    协议操作对象结构:

     协议(文件)操作的顶层结构是AVIOContext,这个对象实现了带缓冲的读写操作;FFMPEG的输入对象AVFormat的pb字段指向一个AVIOContext。

    AVIOContext的opaque实际指向一个URLContext对象,这个对象封装了协议对象及协议操作对象,其中prot指向具体的协议操作对象,priv_data指向具体的协议对象。

    URLProtocol为协议操作对象,针对每种协议,会有一个这样的对象,每个协议操作对象和一个协议对象关联,比如,文件操作对象为ff_file_protocol,它关联的结构体是FileContext。


    代码分析:

    初始化AVIOFormat函数调用关系:

    我们采用从底至上的方法分析源码。

    URLProtocol是FFMPEG操作文件的结构(包括文件,网络数据流等等),包括open、close、read、write、seek等操作。

    在在av_register_all()函数中,通过调用REGISTER_PROTOCOL()宏,所有的URLProtocol都保存在以first_protocol为链表头的链表中。

    URLProtocol结构体的定义为(简化版,未完全列出所有成员):

    1. typedef struct URLProtocol {  
    2.    const char *name;   
    3.     int     (*url_open)( URLContext *h, const char *url, int flags);      
    4.     int     (*url_read)( URLContext *h, unsigned char *buf, int size);  
    5.     int     (*url_write)(URLContext *h, const unsigned char *buf, int size);  
    6.     int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);  
    7.     int     (*url_close)(URLContext *h);  
    8.     int (*url_get_file_handle)(URLContext *h);  
    9.     struct URLProtocol *next;  // 指向下一个URLProtocol对象(所有URLProtocol以链表链接在一起)  
    10.     int priv_data_size;     // 和该URLProtocol对象关联的对象的大小  
    11.     const AVClass *priv_data_class; 
    12. } URLProtocol;

    以文件协议为例,ff_file_protocol变量定义为:

    1. URLProtocol ff_file_protocol = {  
    2.     .name                = "file",  
    3.     .url_open            = file_open,  
    4.     .url_read            = file_read,  
    5.     .url_write           = file_write,  
    6.     .url_seek            = file_seek,  
    7.     .url_close           = file_close,  
    8.     .url_get_file_handle = file_get_handle,  
    9.     .url_check           = file_check,  
    10.     .priv_data_size      = sizeof(FileContext),  
    11.     .priv_data_class     = &file_class,  
    12. };

    从中可以看出,.priv_data_size的值为sizeof(FileContext),即ff_file_protocol和FileContext想关联。


    FileContext对象的定义为:

    1. typedef struct FileContext {  
    2.     const AVClass *class;  
    3.     int fd; <span style="white-space:pre">    </span>// 文件描述符  
    4.     int trunc;      // 截断属性  
    5.     int blocksize;  // 块大小,每次读写文件最大字节数  
    6. } FileContext;

    返回去看ff_file_protocol里的函数指针,以url_read成员为例,它指向file_read()函数,该函数的定义为:

    1. static int file_read(URLContext *h, unsigned char *buf, int size)  
    2. {  
    3.     FileContext *c = h->priv_data;  
    4.     int r;  
    5.     size = FFMIN(size, c->blocksize);  
    6.     r = read(c->fd, buf, size);  
    7.     return (-1 == r)?AVERROR(errno):r;  
    8. }

    从代码可以看出:

    1. 调用此函数时,URLContext的priv_data指向一个FileContext对象;

    2. 该函数每次最大只读取FileContext.blocksize大小的数据。

    从上面的代码中,还可发现一个重要的对象:URLContext,根据代码推测,这个对象的priv_data指向一个FileContext,下面来看看这个对象的定义及初始化:

    定义:

    1. typedef struct URLContext {  
    2.     const AVClass *av_class;    /**< information for av_log(). Set by url_open(). */  
    3.     struct URLProtocol *prot;  
    4.     void *priv_data;  
    5.     char *filename;             /**< specified URL */  
    6.     int flags;  
    7.     int max_packet_size;        /**< if non zero, the stream is packetized with this max packet size */  
    8.     int is_streamed;            /**< true if streamed (no seek possible), default = false */  
    9.     int is_connected;  
    10.     AVIOInterruptCB interrupt_callback;  
    11.     int64_t rw_timeout;         /**< maximum time to wait for (network) read/write operation completion, in mcs */  
    12. } URLContext; 

    注释已经很明了,接下来看看这个结构体的初始化,在url.h(URLContext定义的地方)文件中很容易发现有一个ffurl_open()函数,猜测这应该是个初始化函数,从

    该函数的内容可知,它首先调用了ffurl_alloc()申请空间,然后调用了ffurl_connect()建立连接。因此我们的目标转向这两个函数

    先看ffurl_alloc()函数,该函数先调用url_find_protocol()查找和输入文件名称对应的协议对象,然后调用url_alloc_for_protocol()申请空间。

    url_find_protocol()易知:filename和协议匹配的规则是finename的前缀名(本地文件被统一处理为file前缀)和协议的name字段所指向的字符串相等。

    再看url_alloc_for_protocol()函数:

    1. static int url_alloc_for_protocol(URLContext **puc, struct URLProtocol *up,  
    2.                                   const char *filename, int flags,  
    3.                                   const AVIOInterruptCB *int_cb)  
    4. {  
    5.     URLContext *uc;  
    6.     int err;  
    7.   
    8.     // ...  
    9.       
    10.     uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1); // 注意申请的空间大小  
    11.     if (!uc) {  
    12.         err = AVERROR(ENOMEM);  
    13.         goto fail;  
    14.     }  
    15.     uc->av_class = &ffurl_context_class;  
    16.     uc->filename = (char *)&uc[1];   // 指向uc+sizeof(URLContext)的低昂  
    17.     strcpy(uc->filename, filename);  
    18.     uc->prot            = up;  
    19.     uc->flags           = flags;  
    20.     uc->is_streamed     = 0; /* default = not streamed */  
    21.     uc->max_packet_size = 0; /* default: stream file */  
    22.     if (up->priv_data_size) {  
    23.         uc->priv_data = av_mallocz(up->priv_data_size);  
    24.         if (!uc->priv_data) {  
    25.             err = AVERROR(ENOMEM);  
    26.             goto fail;  
    27.         }  
    28.         if (up->priv_data_class) {  
    29.             int proto_len= strlen(up->name);  
    30.             char *start = strchr(uc->filename, ',');  
    31.             *(const AVClass **)uc->priv_data = up->priv_data_class;  
    32.             av_opt_set_defaults(uc->priv_data);  
    33.     // ...  
    34.     }  

    由上代码可以看出,URLContext的filename保存了输入文件名,prot字段保存了查找到的协议操作对象指针,flags由入参指定,is_streamed及max_packet_size默认值为0,

    priv_data指向了一个由协议操作对象的priv_data_size指定大小的空间(考虑ff_file_protocol,即指向了一个sizeof(FileContext)大小的空间),且该空间的被初始化为0。

    至此,URLContext的大部分内容已初始化,ffurl_alloc()工作已经完成,接着看ffurl_connect(),从代码易知,它调用了prot->url_open()函数打开协议,并设置了URLContext的

    is_connected和is_streamed为1。

    注意,av_opt_set_defaults(uc->priv_data)为为priv_data所指向的结构也进行了默认赋值操作,针对ff_file_protocol,赋值字段及值可参考file_options[]的定义:

    1. static const AVOption file_options[] = {  
    2.     { "truncate", "truncate existing files on write", offsetof(FileContext, trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },  
    3.     { "blocksize", "set I/O operation maximum block size", offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },  
    4.     { NULL }  
    5. };

    即,truncate字段赋值为1,blocksize字段赋值为INT_MAX。

    最后看看file_open()函数,从代码容易看出,该函数打开了文件,并将文件句柄保存在FileContext的fd字段。并且,该函数还根据文件属性设置了FileContext的is_streamed的值。

    URLContext再往上一层是AVIOContext,该结构体的定义为:

    1. typedef struct AVIOContext {  
    2.     const AVClass *av_class;  
    3.     unsigned char *buffer;  /**< 读写缓冲buffer起始地址 */  
    4.     int buffer_size;        /**< buffer大小 */  
    5.     unsigned char *buf_ptr; /**< Current position in the buffer */  
    6.     unsigned char *buf_end; /**< End of the data */  
    7.     void *opaque;           /**< 指向URLContext */  
    8.     int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);   /* 指向ffurl_read() */  
    9.     int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);  /* 指向ffurl_write() */  
    10.     int64_t (*seek)(void *opaque, int64_t offset, int whence);      /* 指向ffurl_seek() */  
    11.     int64_t pos;            /**< 当前buffer对应的文件内容中的位置 */  
    12.     int must_flush;         /**< true if the next seek should flush */  
    13.     int eof_reached;        /**< true if eof reached */  
    14.     int write_flag;         /**< true if open for writing */  
    15.     int max_packet_size;  
    16.     unsigned long checksum;  
    17.     unsigned char *checksum_ptr;  
    18.     unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);  
    19.     int error;              /**< contains the error code or 0 if no error happened */  
    20.     int (*read_pause)(void *opaque, int pause);/  
    21.     int64_t (*read_seek)(void *opaque, int stream_index,  
    22.                          int64_t timestamp, int flags);  
    23.       
    24.     int seekable;   // 是否可seek,0表示不可搜索  
    25.     int64_t maxsize;  
    26.     int direct;  
    27.     int64_t bytes_read;  
    28.     int seek_count;  
    29.     int writeout_count;  
    30.     int orig_buffer_size;  
    31. }AVIOContext;

    avio_open2()负责初始化AVIOContext,从代码易知,该函数首先调用ffurl_open()申请了一个URLContext对象并打开了文件。然后调用ffio_fdopen()申请一个AVIOContext对象并赋初值。

    ffio_fdopen()先申请了一个读写缓冲buffer(结合文件协议,buffer大小为IO_BUFFER_SIZE),然后调用avio_alloc_context()赋值,注意ffurl_read,ffurl_write,ffurl_seek的地址作为入参被传入。

    最终调用的是ffio_init_context赋值,结合入参可知:

    AVIOContext.buffer指向申请到的buffer, AVIOContext.orig_buffer_size和AVIOContext.buffer_size值为buffer_size(I_BUFFER_SIZE),buf_ptr字段初始化为buffer的开始地址,opaque指向URLContext对象。

    再关注下avio_read()函数:

    1. int avio_read(AVIOContext *s, unsigned char *buf, int size)  
    2. {  
    3.     int len, size1;  
    4.   
    5.     size1 = size;  
    6.     while (size > 0) {  
    7.         len = s->buf_end - s->buf_ptr;  
    8.         if (len > size)  
    9.             len = size;  
    10.         if (len == 0 || s->write_flag) {  
    11.             if((s->direct || size > s->buffer_size) && !s->update_checksum){  
    12.                 if(s->read_packet)  
    13.                     len = s->read_packet(s->opaque, buf, size);  
    14.                 if (len <= 0) {  
    15.                     /* do not modify buffer if EOF reached so that a seek back can 
    16.                     be done without rereading data */  
    17.                     s->eof_reached = 1;  
    18.                     if(len<0)  
    19.                         s->error= len;  
    20.                     break;  
    21.                 } else {  
    22.                     s->pos += len;  
    23.                     s->bytes_read += len;  
    24.                     size -= len;  
    25.                     buf += len;  
    26.                     s->buf_ptr = s->buffer;  
    27.                     s->buf_end = s->buffer/* + len*/;  
    28.                 }  
    29.             } else {  
    30.                 fill_buffer(s);  
    31.                 len = s->buf_end - s->buf_ptr;  
    32.                 if (len == 0)  
    33.                     break;  
    34.             }  
    35.         } else {  
    36.             memcpy(buf, s->buf_ptr, len);  
    37.             buf += len;  
    38.             s->buf_ptr += len;  
    39.             size -= len;  
    40.         }  
    41.     }  
    42.     if (size1 == size) {  
    43.         if (s->error)      return s->error;  
    44.         if (url_feof(s))   return AVERROR_EOF;  
    45.     }  
    46.     return size1 - size;  
    47. }

    从代码易知,该函数实现了缓冲读写功能,即调用者传递的size比buffer_size大,则buffer_size的倍数部分会直接读取到buf,余数部分会首先读取buffer_size大小的数据到buffer(fill_buffer()函数),然后再拷贝到入参buf指向的地址中。

    AVIOContext再往上一层是AVFormatContext对象,也是ffmpeg的顶层对象,AVFormatContext的pb字段指向一个AVIOContext对象。

  • 相关阅读:
    api1
    录像时调用MediaRecorder的start()时发生start failed: -19错误
    继承AppCompatActivity的Activity隐藏标题栏
    Android 6.0 运行时权限处理完全解析
    Android开发用过的十大框架
    Lite Your Android English
    2015最流行的Android组件、工具、框架大全
    C#调用C++函数入口点的问题 z
    C#调用C++的DLL函数另一则(delegate) z
    C#调用C++编写的DLL函数, 以及各种类型的参数传递 z
  • 原文地址:https://www.cnblogs.com/meiwubiao/p/7300718.html
Copyright © 2011-2022 走看看