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
|