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,也就是代表这个报文采用了分块编码,不然设置为-1Used 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');//指定长度值11sock.write('01234567890\r\n');//数据sock.write('5\r\n');//执行下面长度值5sock.write('12345\r\n');//数据sock.write('0\r\n');//最后一个分块是0sock.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_AGENTchar *user_agent_deprecated;
#endifchar *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_ZLIBint 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指向HttpContextif (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) {//创建失败了,关闭URLContextffurl_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)//传递过来的是NULLav_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协议,需要自己移植SSLav_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 setav_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 whitelistif ((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打开失败了,直接返回errreturn 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赋值给AVIOContextif(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;
}

ffmpeg系列-协议操作解析-AVIOContext,URLContext,URLProtocol,HTTPContext相关推荐

  1. ffmpeg系列-解复用流程解析

    从我的笔记ffmpeg-mov格式与分离器实现详解一文中,我们已经知道了mov的demuxer相关实现.本文主要来分析demuxer的流程. 1.结构流程图 从上面的结构图中我们可以看到AVForma ...

  2. FFmpeg源码分析:AVIOContext、IO模型与协议

    FFmpeg的IO模型从avio_open()方法开始,核心结构体由AVIOContext和URLProtocol组成.如果需要读取缓冲区buffer数据进行播放,可以通过自定义AVIOContext ...

  3. 【网络系列】应用层HTTP协议格式解析、实现、HTTPS的加密流程

    应用层HTTP协议格式解析.实现.HTTPS的加密流程 什么是HTTP协议 HTTP协议格式 1.首行 2.头部 3.正文 实现HTTP协议服务器 HTTPS如何加密 什么是HTTP协议 应用层负责程 ...

  4. python应用系列教程——python使用scapy监听网络数据包、按TCP/IP协议进行解析

    分享一个朋友的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!大家可以看看是否对自己有帮助:点击打开 docker/kubernetes入门视频教程 全栈工程师开发手册 (作者:栾鹏) pyth ...

  5. ffmpeg的IO操作

    ffmpeg的IO操作主要在libavformat库中实现,部分实现用到了libavutl中的工具.网上有一些介绍ffmpeg的IO的文章,但是有些比较老了,并且一些现在ffmpeg结构已经一些变化, ...

  6. FFMPEG结构体分析:AVIOContext

    注:写了一系列的结构体的分析的文章,在这里列一个列表: FFMPEG结构体分析:AVFrame FFMPEG结构体分析:AVFormatContext FFMPEG结构体分析:AVCodecConte ...

  7. rtsp协议格式解析

    前言 网上关于rtsp的文章很多,但大多是抽象的理论介绍,从理论学习到实际上手开发往往还有一段距离.然而,没有实际开发经验的支撑,理论又很难理解到位. 本系列文章将从流媒体协议的基础原理开始,通过抓包 ...

  8. rtsp协议报文解析-首部字段解析

    前言 网上关于rtsp的文章很多,但大多是抽象的理论介绍,从理论学习到实际上手开发往往还有一段距离.然而,没有实际开发经验的支撑,理论又很难理解到位. 本系列文章将从流媒体协议的基础原理开始,通过抓包 ...

  9. iOS开发系列--UITableView全面解析

    iOS开发系列--UITableView全面解析 2014-08-23 23:20 by KenshinCui, 2202 阅读, 18 评论, 收藏,  编辑 --UIKit之UITableView ...

最新文章

  1. iptables防火墙过滤规则
  2. Ubuntu安装TensorFlow
  3. 计算机基础知识教程职称怎么计算,2017年职称计算机考试基础知识教程详解(二十)...
  4. sklearn API 文档
  5. Java:将JDBC ResultSet作为JSON流式传输
  6. 豆瓣电影 知识图谱 Neo4j
  7. 编写一个应用程序,给出汉字“你”“我”“他”在Unicode表中的位置。
  8. 金蝶k3 使用两台服务器的优势,金蝶k3报价系统优势如何,电商企业应用尤为明显...
  9. 机器学习项目失败最大的原因的有这7种,你认同吗?
  10. 字节跳动前端开发面试题总结,需要的小伙伴来看!
  11. 智能家居竞品分析:米家/HomeKit/美居/涂鸦智能的体验与思考
  12. 01 - vulhub - ActiveMq - CVE-2015-5254
  13. Deep Learning for UAV-based Object Detection andTracking: A Survey(论文翻译)
  14. android5版本主流手机,你达标没?最受欢迎安卓手机配置:5.5寸/6GB/安卓7.0...
  15. 部署本地thinkphp6(iis+php7)
  16. Linux(centos7.9)常用命令大全及基础知识
  17. 亿图图示--工业自动化模块--啤酒生产处理流程简图和热水冷凝处理架构
  18. C++ CMake入门和进阶(二):CMake语法
  19. amd k14主板参数_看U选主板:AMD A8-5600K主板怎么选
  20. Java字符串首尾显示,中间隐藏

热门文章

  1. 远离疲倦,告别非理性思维
  2. Bailian3248 最大公约数【数论】
  3. UVA10023 Square root【大数】
  4. HDU1229 还是A+B【水题】
  5. IDEA 编译构建等工程配置
  6. numpy 常用api(四)
  7. python一般的基础代码-Python入门经典练习题
  8. 爬虫python能做什么-Python 爬虫一 简介
  9. python工资这么高为什么不学-推崇Python这么多人,为什么他们找不到工作!
  10. python零基础自学教材-python萌新:从零基础入门到放弃