分享

ffmpeg系列-协议操作解析-AVIOContext,URLContext,URLProtocol,HTTPContext

 开花结果 2020-03-27

1.协议操作对象结构

协议操作对象结构:

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

AVIOContext的opaque实际指向一个URLContext对象,这个对象封装了协议对象及协议操作对象,其中prot指向具体的协议操作对象(如URLProtocol),priv_data指向具体的协议对象(如HTTPContext或者FileContext)。

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

2.代码分析

2.1初始化AVIOContext函数调用关系

初始化AVIOFormat函数调用关系:

我们先从下面到上面来分析源码。先分析协议中具体实现以及URLProtocol以及URLContext。

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

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

URLProtocol结构体的定义为:

typedef struct URLProtocol {

    const char *name;//协议的名称

    //各种协议对应的回调函数

    int     (*url_open)( URLContext *h, const char *url, int flags);

    /**

     * This callback is to be used by protocols which open further nested

     * protocols. options are then to be passed to ffurl_open()/ffurl_connect()

     * for those nested protocols.

     */

    int     (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);

    int     (*url_accept)(URLContext *s, URLContext **c);

    int     (*url_handshake)(URLContext *c);

    /**

     * Read data from the protocol.

     * If data is immediately available (even less than size), EOF is

     * reached or an error occurs (including EINTR), return immediately.

     * Otherwise:

     * In non-blocking mode, return AVERROR(EAGAIN) immediately.

     * In blocking mode, wait for data/EOF/error with a short timeout (0.1s),

     * and return AVERROR(EAGAIN) on timeout.

     * Checking interrupt_callback, looping on EINTR and EAGAIN and until

     * enough data has been read is left to the calling function; see

     * retry_transfer_wrapper in avio.c.

     */

    int     (*url_read)( URLContext *h, unsigned char *buf, int size);

    int     (*url_write)(URLContext *h, const unsigned char *buf, int size);

    int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);

    int     (*url_close)(URLContext *h);

    int (*url_read_pause)(URLContext *h, int pause);

    int64_t (*url_read_seek)(URLContext *h, int stream_index,

                             int64_t timestamp, int flags);

    int (*url_get_file_handle)(URLContext *h);

    int (*url_get_multi_file_handle)(URLContext *h, int **handles,

                                     int *numhandles);

    int (*url_get_short_seek)(URLContext *h);

    int (*url_shutdown)(URLContext *h, int flags);

    int priv_data_size;

    const AVClass *priv_data_class;

    int flags;

    int (*url_check)(URLContext *h, int mask);

    int (*url_open_dir)(URLContext *h);

    int (*url_read_dir)(URLContext *h, AVIODirEntry **next);

    int (*url_close_dir)(URLContext *h);

    int (*url_delete)(URLContext *h);

    int (*url_move)(URLContext *h_src, URLContext *h_dst);

    const char *default_whitelist;//默认白名单

} URLProtocol;

以HTTP协议为例,ff_http_protocol

const URLProtocol ff_http_protocol = {

    .name                = "http",

    .url_open2           = http_open,

    .url_accept          = http_accept,

    .url_handshake       = http_handshake,

    .url_read            = http_read,

    .url_write           = http_write,

    .url_seek            = http_seek,

    .url_close           = http_close,

    .url_get_file_handle = http_get_file_handle,

    .url_get_short_seek  = http_get_short_seek,

    .url_shutdown        = http_shutdown,

    .priv_data_size      = sizeof(HTTPContext),

    .priv_data_class     = &http_context_class,

    .flags               = URL_PROTOCOL_FLAG_NETWORK,

    .default_whitelist   = "http,https,tls,rtp,tcp,udp,crypto,httpproxy"

};

从中可以看出,.priv_data_size的值为sizeof(HTTPContext),即ff_http_protocol和HttpContext相关联。

HttpContext对象的定义为:

typedef struct HTTPContext {

    const AVClass *class;

    URLContext *hd;

    unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end;

    int line_count;

    int http_code;

    /** 

    如果使用Transfer-Encoding:chunked,也就是代表这个报文采用了分块编码,不然设置为-1

Used if "Transfer-Encoding: chunked" otherwise -1. 

    分块编码:

    报文中的实体需要改为用一系列的分块来传输,每个分块包含十六进制的长度值和数据,

    长度值独占一行,长度不包括它结尾的 CRLF(\r\n),也不包括分块数据结尾的 CRLF。

    最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束

    如:服务端

    sock.write('HTTP/1.1 200 OK\r\n');

        sock.write('Transfer-Encoding: chunked\r\n');

        sock.write('\r\n');

        sock.write('b\r\n');//指定长度值11

        sock.write('01234567890\r\n');//数据

        sock.write('5\r\n');//执行下面长度值5

        sock.write('12345\r\n');//数据

        sock.write('0\r\n');//最后一个分块是0

        sock.write('\r\n');

    **/

    uint64_t chunksize;

//off  buf偏移   end_off->请求头:range的结尾值

    uint64_t off, end_off, filesize;

    char *location;

    HTTPAuthState auth_state;

    HTTPAuthState proxy_auth_state;

    char *http_proxy;

    char *headers;

    char *mime_type;

    char *user_agent;

#if FF_API_HTTP_USER_AGENT

    char *user_agent_deprecated;

#endif

    char *content_type;

    /* 

如果服务器设置正确处理连接:关闭并将关闭,也就是处理了请求头中的Connetion

如果Connection是close的话,处理完后断开连接。willclose设置1,在解析请求头中

也就是这个变量代表Connection是否是close  

    */

    int willclose;

    int seekable;           /**< Control seekability, 0 = disable, 1 = enable, -1 = probe. */

    int chunked_post;

    /* A flag which indicates if the end of chunked encoding has been sent. */

    int end_chunked_post;

    /* A flag which indicates we have finished to read POST reply. */

    int end_header;

    /* A flag which indicates if we use persistent connections. */

    int multiple_requests;

    uint8_t *post_data;

    int post_datalen;

    int is_akamai;

    int is_mediagateway;

    char *cookies;          ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name)

    /* A dictionary containing cookies keyed by cookie name */

    AVDictionary *cookie_dict;

    int icy;

    /* how much data was read since the last ICY metadata packet */

    uint64_t icy_data_read;

    /* after how many bytes of read data a new metadata packet will be found */

    uint64_t icy_metaint;

    char *icy_metadata_headers;

    char *icy_metadata_packet;

    AVDictionary *metadata;

#if CONFIG_ZLIB

    int compressed;//如果服务器返回客户端的内容正文压缩的话

    z_stream inflate_stream;

    uint8_t *inflate_buffer;

#endif /* CONFIG_ZLIB */

    AVDictionary *chained_options;

    int send_expect_100;

    char *method;

    int reconnect;

    int reconnect_at_eof;

    int reconnect_streamed;

    int reconnect_delay;

    int reconnect_delay_max;

    int listen;

    char *resource;

    int reply_code;

    int is_multi_client;

    HandshakeState handshake_step;

    int is_connected_server;

} HTTPContext;

返回去看ff_http_protocol里的函数指针,以url_read举例,它指向http_read函数,看一下这个函数。

/**

*流数据读取

**/

static int http_read(URLContext *h, uint8_t *buf, int size)

{

    HTTPContext *s = h->priv_data;//URLContext的priv_data指向HttpContext

    if (s->icy_metaint > 0) {

        size = store_icy(h, size);

        if (size < 0)

            return size;

    }

    size = http_read_stream(h, buf, size);

    if (size > 0)

        s->icy_data_read += size;

    return size;

}

从上面的分析中,我们知道了协议的具体实现以及URLProtocol以及URLContext结构之间的关系。下面我们开始从上面到下面的流程来分析:

avformat_open_input()函数调用init_input/utils.c/libavformat

,最后调用到avio_open()然后调用到avio_open2()/aviobuf.c/libavformat函数,然后调用到ffio_open_whitelist/aviobuf.c,下面看一下这个函数。

/**

* 按照协议名单打开协议,并初始化一个AVIOContext并赋值

**/

int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags,

                         const AVIOInterruptCB *int_cb, AVDictionary **options,

                         const char *whitelist, const char *blacklist

                        )

{

    URLContext *h;

    int err;

    //调用avio.c中的按照白名单打开协议

    //申请协议空间与协议查找以及建立连接(调用协议中open函数,如http_open)

    //调用ffurl_open()申请创建了一个URLContext对象并打开了文件(也就是建立连接等)

    err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);

    if (err < 0)

        return err;

//调用ffio_fdopen()申请一个AVIOContext对象并赋初值(部分初值来自ffurl_open_whitelist中创建的URLContext)

    err = ffio_fdopen(s, h);

    if (err < 0) {//创建失败了,关闭URLContext

        ffurl_close(h);

        return err;

    }

    return 0;

}

这个函数调用ffurl_open_whitelist来创建一个URLContext,申请协议内存以及查找协议,同时建立连接,调用ffio_fdopen函数来申请一个AVIOContext对象并赋初值。

我们看下URLContext的结构体:

typedef struct URLContext {

    const AVClass *av_class;    /**< information for av_log(). Set by url_open(). */

    const struct URLProtocol *prot;//URLProtocol结构体

    void *priv_data;

    char *filename;             /**< specified URL */

    int flags;

    int max_packet_size;        /**< if non zero, the stream is packetized with this max packet size */

    int is_streamed;            /**< true if streamed (no seek possible), default = false */

    int is_connected;

    AVIOInterruptCB interrupt_callback;

    int64_t rw_timeout;         /**< 最大等待时间(网络)读/写操作完成,在mcs,maximum time to wait for (network) read/write operation completion, in mcs */

    const char *protocol_whitelist;

    const char *protocol_blacklist;

    int min_packet_size;        /**< if non zero, the stream is packetized with this min packet size */

} URLContext;

从这里我们先从ffurl_open_whitelist/avio.c函数开始看

/**

*按照白名单打开协议

*申请协议空间与协议查找以及建立连接(调用协议中open函数,如http_open)

**/

int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,

                         const AVIOInterruptCB *int_cb, AVDictionary **options,

                         const char *whitelist, const char* blacklist,

                         URLContext *parent)

{

    AVDictionary *tmp_opts = NULL;

    AVDictionaryEntry *e;

//协议内存申请以及查找filename中对应的协议

    int ret = ffurl_alloc(puc, filename, flags, int_cb);

    if (ret < 0)

        return ret;

    if (parent)//传递过来的是NULL

        av_opt_copy(*puc, parent);

    if (options &&

        (ret = av_opt_set_dict(*puc, options)) < 0)

        goto fail;

    if (options && (*puc)->prot->priv_data_class &&

        (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)

        goto fail;

    if (!options)

        options = &tmp_opts;

    av_assert0(!whitelist ||

               !(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||

               !strcmp(whitelist, e->value));

    av_assert0(!blacklist ||

               !(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||

               !strcmp(blacklist, e->value));

    if ((ret = av_dict_set(options, "protocol_whitelist", whitelist, 0)) < 0)

        goto fail;

    if ((ret = av_dict_set(options, "protocol_blacklist", blacklist, 0)) < 0)

        goto fail;

    if ((ret = av_opt_set_dict(*puc, options)) < 0)

        goto fail;

//建立连接,打开协议(如,调http_open函数)

    ret = ffurl_connect(*puc, options);

    if (!ret)

        return 0;

fail:

    ffurl_close(*puc);

    *puc = NULL;

    return ret;

}

跟踪ffurl_alloc/avio.c函数,这里主要是协议内存申请以及查找filename中对应的协议。

/**

*协议空间申请与协议查找

**/

int ffurl_alloc(URLContext **puc, const char *filename, int flags,

                const AVIOInterruptCB *int_cb)

{

    const URLProtocol *p = NULL;

//查找协议

    p = url_find_protocol(filename);

    if (p)//如果找到协议

       return url_alloc_for_protocol(puc, p, filename, flags, int_cb);

    *puc = NULL;//如果没有找到,协议不可用

    if (av_strstart(filename, "https:", NULL))//如果文件名开头为https,ffmpeg目前不支持https协议,需要自己移植SSL

        av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "

                                     "openssl, gnutls "

                                     "or securetransport enabled.\n");

    return AVERROR_PROTOCOL_NOT_FOUND;

}

下面看下ffurl_connect/avio.c函数,这里主要是建立连接,打开协议(如,调http_open函数)

/**

*建立连接,打开协议

**/

int ffurl_connect(URLContext *uc, AVDictionary **options)

{

    int err;

    AVDictionary *tmp_opts = NULL;

    AVDictionaryEntry *e;

    if (!options)

        options = &tmp_opts;

//黑名单,白名单检测

    // Check that URLContext was initialized correctly and lists are matching if set

    av_assert0(!(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||

               (uc->protocol_whitelist && !strcmp(uc->protocol_whitelist, e->value)));

    av_assert0(!(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||

               (uc->protocol_blacklist && !strcmp(uc->protocol_blacklist, e->value)));

    if (uc->protocol_whitelist && av_match_list(uc->prot->name, uc->protocol_whitelist, ',') <= 0) {

        av_log(uc, AV_LOG_ERROR, "Protocol '%s' not on whitelist '%s'!\n", uc->prot->name, uc->protocol_whitelist);

        return AVERROR(EINVAL);

    }

    if (uc->protocol_blacklist && av_match_list(uc->prot->name, uc->protocol_blacklist, ',') > 0) {

        av_log(uc, AV_LOG_ERROR, "Protocol '%s' on blacklist '%s'!\n", uc->prot->name, uc->protocol_blacklist);

        return AVERROR(EINVAL);

    }

    if (!uc->protocol_whitelist && uc->prot->default_whitelist) {

        av_log(uc, AV_LOG_DEBUG, "Setting default whitelist '%s'\n", uc->prot->default_whitelist);

        uc->protocol_whitelist = av_strdup(uc->prot->default_whitelist);

        if (!uc->protocol_whitelist) {

            return AVERROR(ENOMEM);

        }

    } else if (!uc->protocol_whitelist)

        av_log(uc, AV_LOG_DEBUG, "No default whitelist set\n"); // This should be an error once all declare a default whitelist

    if ((err = av_dict_set(options, "protocol_whitelist", uc->protocol_whitelist, 0)) < 0)

        return err;

    if ((err = av_dict_set(options, "protocol_blacklist", uc->protocol_blacklist, 0)) < 0)

        return err;

//调用port->url_open(也就是协议中的open函数,如http_open,打开了协议

    err =

        uc->prot->url_open2 ? uc->prot->url_open2(uc,

                                                  uc->filename,

                                                  uc->flags,

                                                  options) :

        uc->prot->url_open(uc, uc->filename, uc->flags);

    av_dict_set(options, "protocol_whitelist", NULL, 0);

    av_dict_set(options, "protocol_blacklist", NULL, 0);

    if (err)//如果协议打开失败了,如http_open打开失败了,直接返回err

        return err;

    uc->is_connected = 1;//设置is_conneced为true,设置协议已经建立连接

    /* 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;

}

我们回到ffio_open_whitelist继续从ffio_fdopen函数开始看,这里主要是申请一个AVIOContext对象并赋初值(部分初值来自ffurl_open_whitelist中创建的URLContext)。

/**

*申请一个AVIOContext对象并赋值

*/

int ffio_fdopen(AVIOContext **s, URLContext *h)

{

    AVIOInternal *internal = NULL;

    uint8_t *buffer = NULL;

    int buffer_size, max_packet_size;

    max_packet_size = h->max_packet_size;

    if (max_packet_size) {

        buffer_size = max_packet_size; /* no need to bufferize more than one packet */

    } else {

        buffer_size = IO_BUFFER_SIZE;

    }

//申请了一个读写缓冲buffer(结合文件协议,buffer大小为IO_BUFFER_SIZE)

    buffer = av_malloc(buffer_size);//buffer_size的值取自哪里?

    if (!buffer)

        return AVERROR(ENOMEM);

    internal = av_mallocz(sizeof(*internal));

    if (!internal)

        goto fail;

    internal->h = h;

    //对AVIOContext赋值,同时把申请到的buffer以及ffurl_read,ffurl_write,ffurl_seek的地址作为入参被传入

    *s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE,

                            internal, io_read_packet, io_write_packet, io_seek);

    if (!*s)//创建AVIOContext失败的话,直接报错

        goto fail;

    (*s)->protocol_whitelist = av_strdup(h->protocol_whitelist);

    if (!(*s)->protocol_whitelist && h->protocol_whitelist) {

        avio_closep(s);

        goto fail;

    }

    (*s)->protocol_blacklist = av_strdup(h->protocol_blacklist);

    if (!(*s)->protocol_blacklist && h->protocol_blacklist) {

        avio_closep(s);

        goto fail;

    }

    (*s)->direct = h->flags & AVIO_FLAG_DIRECT;

    (*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;

    (*s)->max_packet_size = max_packet_size;

    (*s)->min_packet_size = h->min_packet_size;//协议中获取的urlcontext赋值给AVIOContext

    if(h->prot) {//如果URLProtStruct不为空

        (*s)->read_pause = io_read_pause;//把ffurl_pause赋值给AVIOContext的read_pause函数

        (*s)->read_seek  = io_read_seek;//把ffurl_seek赋值给AVIOContext的read_seek函数

        if (h->prot->url_read_seek)

            (*s)->seekable |= AVIO_SEEKABLE_TIME;

    }

    (*s)->short_seek_get = io_short_seek;//根据协议调用prot(protocol)中的short_seek函数

    (*s)->av_class = &ff_avio_class;

    return 0;

fail:

    av_freep(&internal);

    av_freep(&buffer);

    return AVERROR(ENOMEM);

}

从上面可以看到调用av_malloc(buffer_size)申请了一个读写缓冲buffer(结合文件协议,buffer大小为IO_BUFFER_SIZE)。

调用avio_alloc_context,来对AVIOContext赋值,同时把申请到的buffer以及ffurl_read,ffurl_write,ffurl_seek的地址作为入参被传入。

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))

{

    AVIOContext *s = av_mallocz(sizeof(AVIOContext));//申请AVIOContext内存

    if (!s)

        return NULL;

//把传进来的参数(函数指针)指向刚创建的AVIOContext结构体对应的变量(函数指针)中

    ffio_init_context(s, buffer, buffer_size, write_flag, opaque,

                  read_packet, write_packet, seek);

    return s;

}


原文链接:https://blog.csdn.net/u013470102/article/details/89630915

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多