协议的相关结构:
协议操作的顶层结构是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,最后调用到io_open()然后调用到io_open_default()/options.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对象并赋初值。
从这里我们先从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;
}
---------------------
作者:工匠先生
来源:CSDN
原文:https://blog.csdn.net/u013470102/article/details/89630915
版权声明:本文为博主原创文章,转载请附上博文链接!