FFmpeg源代码简单分析-通用-avio_open2()

参考链接

  • FFmpeg源代码简单分析:avio_open2()_雷霄骅的博客-CSDN博客_avio_open

avio_open2()

  • 该函数用于打开FFmpeg的输入输出文件
  • avio_open2()的声明位于libavformat\avio.h文件中,如下所示。
/*** Create and initialize a AVIOContext for accessing the* resource indicated by url.* @note When the resource indicated by url has been opened in* read+write mode, the AVIOContext can be used only for writing.** @param s Used to return the pointer to the created AVIOContext.* In case of failure the pointed to value is set to NULL.* @param url resource to access* @param flags flags which control how the resource indicated by url* is to be opened* @param int_cb an interrupt callback to be used at the protocols level* @param options  A dictionary filled with protocol-private options. On return* this parameter will be destroyed and replaced with a dict containing options* that were not found. May be NULL.* @return >= 0 in case of success, a negative value corresponding to an* AVERROR code in case of failure*/
int avio_open2(AVIOContext **s, const char *url, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options);

avio_open2()函数参数的含义如下:

  • s:函数调用成功之后创建的AVIOContext结构体。
  • url:输入输出协议的地址(文件也是一种“广义”的协议,对于文件来说就是文件的路径)
  • flags:打开地址的方式。可以选择只读,只写,或者读写。取值如下。
/*** @name URL open modes* The flags argument to avio_open must be one of the following* constants, optionally ORed with other flags.* @{*/
#define AVIO_FLAG_READ  1                                      /**< read-only */
#define AVIO_FLAG_WRITE 2                                      /**< write-only */
#define AVIO_FLAG_READ_WRITE (AVIO_FLAG_READ|AVIO_FLAG_WRITE)  /**< read-write pseudo flag */
  •  AVIO_FLAG_READ:只读。
  •  AVIO_FLAG_WRITE:只写。
  •  AVIO_FLAG_READ_WRITE:读写。
  • int_cb:目前还没有用过。
  • options:目前还没有用过
  • 具体使用的参考链接:最简单的基于FFMPEG的视频编码器(YUV编码为H.264)_雷霄骅的博客-CSDN博客_最简单的基于ffmpeg

函数调用结构图

 avio_open()

  • 有一个和avio_open2()“长得很像”的函数avio_open(),应该是avio_open2()的早期版本。
  • avio_open()比avio_open2()少了最后2个参数。而它前面几个参数的含义和avio_open2()是一样的。
  • 从源代码中可以看出,avio_open()内部调用了avio_open2(),并且把avio_open2()的后2个参数设置成了NULL,因此它的功能实际上和avio_open2()是一样的。
  • avio_open()源代码如下所示。
int avio_open(AVIOContext **s, const char *filename, int flags)
{return avio_open2(s, filename, flags, NULL, NULL);
}

avio_open2()

int avio_open2(AVIOContext **s, const char *filename, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options)
{return ffio_open_whitelist(s, filename, flags, int_cb, options, NULL, NULL);
}
  • 从avio_open2()的源代码可以看出,它主要调用了:ffio_open_whitelist
  • AVIOContext则是在URLContext的读写函数外面加上了一层“包装”(通过retry_transfer_wrapper()函数)

ffio_open_whitelist

  • ffio_open_whitelist 主要调用了ffurl_open_whitelist、ffio_fdopen()和ffurl_close()
  • 根据URLContext初始化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;*s = NULL;err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);if (err < 0)return err;err = ffio_fdopen(s, h);if (err < 0) {ffurl_close(h);return err;}return 0;
}

 ffurl_open_whitelist

  • 初始化URLContext
  • 替代原有的 ffurl_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;int ret = ffurl_alloc(puc, filename, flags, int_cb);if (ret < 0)return ret;if (parent) {ret = av_opt_copy(*puc, parent);if (ret < 0)goto fail;}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;ret = ffurl_connect(*puc, options);if (!ret)return 0;
fail:ffurl_closep(puc);return ret;
}
  • 从代码中可以看出,ffurl_open()主要调用了2个函数:ffurl_alloc()和ffurl_connect()。
  • ffurl_alloc()用于查找合适的URLProtocol,并创建一个URLContext;
  • ffurl_connect()用于打开获得的URLProtocol。 
  • ffurl_open() 已经被弃用 

 ffurl_alloc()

  • ffurl_alloc()的定义位于libavformat\avio.c中,如下所示。
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;return AVERROR_PROTOCOL_NOT_FOUND;
}
  • 从代码中可以看出,ffurl_alloc()主要调用了2个函数:
  • url_find_protocol()根据文件路径查找合适的URLProtocol
  • url_alloc_for_protocol()为查找到的URLProtocol创建URLContext。 

url_find_protocol()

  • 先来看一下url_find_protocol()函数,定义如下所示。
static const struct URLProtocol *url_find_protocol(const char *filename)
{const URLProtocol **protocols;char proto_str[128], proto_nested[128], *ptr;size_t proto_len = strspn(filename, URL_SCHEME_CHARS);int i;if (filename[proto_len] != ':' &&(strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) ||is_dos_path(filename))strcpy(proto_str, "file");elseav_strlcpy(proto_str, filename,FFMIN(proto_len + 1, sizeof(proto_str)));av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));if ((ptr = strchr(proto_nested, '+')))*ptr = '\0';protocols = ffurl_get_protocols(NULL, NULL);if (!protocols)return NULL;for (i = 0; protocols[i]; i++) {const URLProtocol *up = protocols[i];if (!strcmp(proto_str, up->name)) {av_freep(&protocols);return up;}if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&!strcmp(proto_nested, up->name)) {av_freep(&protocols);return up;}}av_freep(&protocols);if (av_strstart(filename, "https:", NULL) || av_strstart(filename, "tls:", NULL))av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with ""openssl, gnutls or securetransport enabled.\n");return NULL;
}
#define URL_SCHEME_CHARS                        \"abcdefghijklmnopqrstuvwxyz"                \"ABCDEFGHIJKLMNOPQRSTUVWXYZ"                \"0123456789+-."
  • url_find_protocol()函数表明了FFmpeg根据文件路径猜测协议的方法。
  • 该函数首先根据strspn()函数查找字符串中第一个“非字母或数字”的字符的位置,并保存在proto_len中。
  • 一般情况下,协议URL中都是包含“:”的,比如说RTMP的URL格式是“rtmp://xxx…”,UDP的URL格式是“udp://…”,HTTP的URL格式是“http://...”。因此,一般情况下proto_len的数值就是“:”的下标(代表了“:”前面的协议名称的字符的个数,例如rtmp://的proto_len为4)。
  • 接下来函数将filename的前proto_len个字节拷贝至proto_str字符串中。
  • 这个地方比较纠结,源代码中av_strlcpy()函数的第3个参数size写的字符串的长度是(proto_len+1),但是查了一下av_strlcpy()的定义,发现该函数至多拷贝(size-1)个字符。这么一涨一消,最终还是拷贝了proto_len个字节。例如RTMP协议就拷贝了“rtmp”,UDP协议就拷贝了“udp”。
  • av_strlcpy()是FFMpeg的一个工具函数,声明位于libavutil\avstring.h,如下所示。
size_t av_strlcpy(char *dst, const char *src, size_t size)
{size_t len = 0;while (++len < size && *src)*dst++ = *src++;if (len <= size)*dst = 0;return len + strlen(src) - 1;
}

  • 这里有一种例外,那就是文件路径。“文件”在FFmpeg中也是一种“协议”,并且前缀是“file”。也就是标准的文件路径应该是“file://...”格式的。但是这太不符合我们一般人的使用习惯,我们一般是不会在文件路径前面加上“file”协议名称的。所以该函数采取的方法是:一旦检测出来输入的URL是文件路径而不是网络协议,就自动向proto_str中拷贝“file”。
  • strcpy(proto_str, "file");
  • 其中判断文件路径那里有一个很复杂的if()语句。根据我的理解,“||”前面的语句用于判断是否是相对文件路径,“||”后面的语句用于判断是否是绝对路径。
  • 判断绝对路径的时候用到了一个函数is_dos_path(),定义位于libavformat\os_support.h,如下所示。
static inline int is_dos_path(const char *path)
{
#if HAVE_DOS_PATHSif (path[0] && path[1] == ':')return 1;
#endifreturn 0;
}
  •  注意“&&”优先级低于“==”。如果文件路径第1个字符不为空(一般情况下是盘符)而且第2个字符为“:”,就认为它是绝对文件路径。

url_alloc_for_protocol()

  • url_alloc_for_protocol()的定义位于libavformat\avio.c中,如下所示。
static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,const char *filename, int flags,const AVIOInterruptCB *int_cb)
{URLContext *uc;int err;#if CONFIG_NETWORKif (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())return AVERROR(EIO);
#endifif ((flags & AVIO_FLAG_READ) && !up->url_read) {av_log(NULL, AV_LOG_ERROR,"Impossible to open the '%s' protocol for reading\n", up->name);return AVERROR(EIO);}if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {av_log(NULL, AV_LOG_ERROR,"Impossible to open the '%s' protocol for writing\n", up->name);return AVERROR(EIO);}uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);if (!uc) {err = AVERROR(ENOMEM);goto fail;}uc->av_class = &ffurl_context_class;uc->filename = (char *)&uc[1];strcpy(uc->filename, filename);uc->prot            = up;uc->flags           = flags;uc->is_streamed     = 0; /* default = not streamed */uc->max_packet_size = 0; /* default: stream file */if (up->priv_data_size) {uc->priv_data = av_mallocz(up->priv_data_size);if (!uc->priv_data) {err = AVERROR(ENOMEM);goto fail;}if (up->priv_data_class) {char *start;*(const AVClass **)uc->priv_data = up->priv_data_class;av_opt_set_defaults(uc->priv_data);if (av_strstart(uc->filename, up->name, (const char**)&start) && *start == ',') {int ret= 0;char *p= start;char sep= *++p;char *key, *val;p++;if (strcmp(up->name, "subfile"))ret = AVERROR(EINVAL);while(ret >= 0 && (key= strchr(p, sep)) && p<key && (val = strchr(key+1, sep))){*val= *key= 0;if (strcmp(p, "start") && strcmp(p, "end")) {ret = AVERROR_OPTION_NOT_FOUND;} elseret= av_opt_set(uc->priv_data, p, key+1, 0);if (ret == AVERROR_OPTION_NOT_FOUND)av_log(uc, AV_LOG_ERROR, "Key '%s' not found.\n", p);*val= *key= sep;p= val+1;}if(ret<0 || p!=key){av_log(uc, AV_LOG_ERROR, "Error parsing options string %s\n", start);av_freep(&uc->priv_data);av_freep(&uc);err = AVERROR(EINVAL);goto fail;}memmove(start, key+1, strlen(key));}}}if (int_cb)uc->interrupt_callback = *int_cb;*puc = uc;return 0;
fail:*puc = NULL;if (uc)av_freep(&uc->priv_data);av_freep(&uc);
#if CONFIG_NETWORKif (up->flags & URL_PROTOCOL_FLAG_NETWORK)ff_network_close();
#endifreturn err;
}
  • url_alloc_for_protocol()完成了以下步骤:首先,检查输入的URLProtocol是否支持指定的flag。
  • 比如flag中如果指定了AVIO_FLAG_READ,则URLProtocol中必须包含url_read();
  • 如果指定了AVIO_FLAG_WRITE,则URLProtocol中必须包含url_write()。
  • 在检查无误之后,接着就可以调用av_mallocz()为即将创建的URLContext分配内存了。接下来基本上就是各种赋值工作,在这里不再详细记录。 

ffurl_connect()

  • ffurl_connect()用于打开获得的URLProtocol。该函数的定义位于libavformat\avio.c中,如下所示。 
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;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)return err;uc->is_connected = 1;/* 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;
}
  • URLProtocol中是否包含url_open2()?如果包含的话,就调用url_open2(),否则就调用url_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);
  • url_open()本身是URLProtocol的一个函数指针,这个地方根据不同的协议调用的url_open()具体实现函数也是不一样的,例如file协议的url_open()对应的是file_open(),而file_open()最终调用了_wsopen(),_sopen()(Windows下)或者open()(Linux下,类似于fopen())这样的系统中打开文件的API函数;
  • 而libRTMP的url_open()对应的是rtmp_open(),而rtmp_open()最终调用了libRTMP的API函数RTMP_Init(),RTMP_SetupURL(),RTMP_Connect() 以及RTMP_ConnectStream()。
  • 通用 转向与 偏特化

ffio_fdopen()

  • ffio_fdopen()使用已经获得的URLContext初始化AVIOContext。
  • 它的函数定义位于libavformat\aviobuf.c中,如下所示。
int ffio_fdopen(AVIOContext **s, URLContext *h)
{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;}if (!(h->flags & AVIO_FLAG_WRITE) && h->is_streamed) {if (buffer_size > INT_MAX/2)return AVERROR(EINVAL);buffer_size *= 2;}buffer = av_malloc(buffer_size);if (!buffer)return AVERROR(ENOMEM);*s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,(int (*)(void *, uint8_t *, int))  ffurl_read,(int (*)(void *, uint8_t *, int))  ffurl_write,(int64_t (*)(void *, int64_t, int))ffurl_seek);if (!*s) {av_freep(&buffer);return AVERROR(ENOMEM);}(*s)->protocol_whitelist = av_strdup(h->protocol_whitelist);if (!(*s)->protocol_whitelist && h->protocol_whitelist) {avio_closep(s);return AVERROR(ENOMEM);}(*s)->protocol_blacklist = av_strdup(h->protocol_blacklist);if (!(*s)->protocol_blacklist && h->protocol_blacklist) {avio_closep(s);return AVERROR(ENOMEM);}(*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;if(h->prot) {(*s)->read_pause = (int (*)(void *, int))h->prot->url_read_pause;(*s)->read_seek  =(int64_t (*)(void *, int, int64_t, int))h->prot->url_read_seek;if (h->prot->url_read_seek)(*s)->seekable |= AVIO_SEEKABLE_TIME;}((FFIOContext*)(*s))->short_seek_get = (int (*)(void *))ffurl_get_short_seek;(*s)->av_class = &ff_avio_class;return 0;
}
  • ffio_fdopen()函数首先初始化AVIOContext中的Buffer。
  • 如果URLContext中设置了max_packet_size,则将Buffer的大小设置为max_packet_size。如果没有设置的话(似乎大部分URLContext都没有设置该值),则会分配IO_BUFFER_SIZE个字节给Buffer。
  • IO_BUFFER_SIZE取值为32768。

avio_alloc_context()

  • ffio_fdopen()接下来会调用avio_alloc_context()初始化一个AVIOContext。
  • avio_alloc_context()本身是一个FFmpeg的API函数。它的声明位于libavformat\avio.h中,如下所示。
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))
{FFIOContext *s = av_malloc(sizeof(*s));if (!s)return NULL;ffio_init_context(s, buffer, buffer_size, write_flag, opaque,read_packet, write_packet, seek);return &s->pub;
}
  • avio_alloc_context()看上去参数很多,但实际上并不复杂。先简单解释一下它各个参数的含义:
    • buffer:AVIOContext中的Buffer。
    • buffer_size:AVIOContext中的Buffer的大小。
    • write_flag:设置为1则Buffer可写;否则Buffer只可读。
    • opaque:用户自定义数据。
    • read_packet():读取外部数据,填充Buffer的函数。
    • write_packet():向Buffer中写入数据的函数。
    • seek():用于Seek的函数。
  • 该函数成功执行的话则会返回一个创建好的AVIOContext。
  • 该函数代码很简单:首先调用av_mallocz()为AVIOContext分配一块内存空间,然后基本上将所有输入参数传递给ffio_init_context()。

ffio_init_context()

  • ffio_init_context()的定义如下。 
void ffio_init_context(FFIOContext *ctx,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 *const s = &ctx->pub;memset(ctx, 0, sizeof(*ctx));s->buffer      = buffer;ctx->orig_buffer_size =s->buffer_size = buffer_size;s->buf_ptr     = buffer;s->buf_ptr_max = buffer;s->opaque      = opaque;s->direct      = 0;url_resetbuf(s, write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ);s->write_packet    = write_packet;s->read_packet     = read_packet;s->seek            = seek;s->pos             = 0;s->eof_reached     = 0;s->error           = 0;s->seekable        = seek ? AVIO_SEEKABLE_NORMAL : 0;s->min_packet_size = 0;s->max_packet_size = 0;s->update_checksum = NULL;ctx->short_seek_threshold = SHORT_SEEK_THRESHOLD;if (!read_packet && !write_flag) {s->pos     = buffer_size;s->buf_end = s->buffer + buffer_size;}s->read_pause = NULL;s->read_seek  = NULL;s->write_data_type       = NULL;s->ignore_boundary_point = 0;ctx->current_type        = AVIO_DATA_MARKER_UNKNOWN;ctx->last_time           = AV_NOPTS_VALUE;ctx->short_seek_get      = NULL;
#if FF_API_AVIOCONTEXT_WRITTEN
FF_DISABLE_DEPRECATION_WARNINGSs->written               = 0;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
}

ffurl_read(),ffurl_write(),ffurl_seek()

  • 现在我们再回到ffio_fdopen(),会发现它初始化AVIOContext的结构体的时候,首先将自己分配的Buffer设置为该AVIOContext的Buffer;
  • 然后将URLContext作为用户自定义数据(对应AVIOContext的opaque变量)提供给该AVIOContext;
  • 最后分别将3个函数作为该AVIOContext的读,写,跳转函数:
  • ffurl_read(),ffurl_write(),ffurl_seek()。
  • 下面我们选择一个ffurl_read()看看它的定义。
  • ffurl_read()的定义位于libavformat\avio.c,如下所示。 
int ffurl_read(URLContext *h, unsigned char *buf, int size)
{if (!(h->flags & AVIO_FLAG_READ))return AVERROR(EIO);return retry_transfer_wrapper(h, buf, size, 1, h->prot->url_read);
}
  • 该函数先判断了一下输入的URLContext是否支持“读”操作,接着调用了一个函数:retry_transfer_wrapper()。
  • 如果我们看ffurl_write()的代码,如下所示。
int ffurl_write(URLContext *h, const unsigned char *buf, int size)
{if (!(h->flags & AVIO_FLAG_WRITE))return AVERROR(EIO);/* avoid sending too big packets */if (h->max_packet_size && size > h->max_packet_size)return AVERROR(EIO);return retry_transfer_wrapper(h, (unsigned char *)buf, size, size,(int (*)(struct URLContext *, uint8_t *, int))h->prot->url_write);
}
  • 会发现他也调用了同样的一个函数retry_transfer_wrapper()。
  • 唯一的不同在于ffurl_read()调用retry_transfer_wrapper()的时候,最后一个参数是URLProtocol的url_read(),而ffurl_write()调用retry_transfer_wrapper()的时候,最后一个参数是URLProtocol的url_write()。
  • 下面我们看一下retry_transfer_wrapper()的定义,位于libavformat\avio.c,如下所示。 
static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,int size, int size_min,int (*transfer_func)(URLContext *h,uint8_t *buf,int size))
{int ret, len;int fast_retries = 5;int64_t wait_since = 0;len = 0;while (len < size_min) {if (ff_check_interrupt(&h->interrupt_callback))return AVERROR_EXIT;ret = transfer_func(h, buf + len, size - len);if (ret == AVERROR(EINTR))continue;if (h->flags & AVIO_FLAG_NONBLOCK)return ret;if (ret == AVERROR(EAGAIN)) {ret = 0;if (fast_retries) {fast_retries--;} else {if (h->rw_timeout) {if (!wait_since)wait_since = av_gettime_relative();else if (av_gettime_relative() > wait_since + h->rw_timeout)return AVERROR(EIO);}av_usleep(1000);}} else if (ret == AVERROR_EOF)return (len > 0) ? len : AVERROR_EOF;else if (ret < 0)return ret;if (ret) {fast_retries = FFMAX(fast_retries, 2);wait_since = 0;}len += ret;}return len;
}
  • 从代码中可以看出,它的核心实际上是调用了一个名称为transfer_func()的函数。而该函数就是retry_transfer_wrapper()的第四个参数。该函数实际上是对URLProtocol的读写操作中的错误进行了一些“容错”处理,可以让数据的读写更加的稳定
  • avio_alloc_context()执行完毕后,ffio_fdopen()函数的工作就基本完成了,avio_open2()的工作也就做完了。

 URLProtocol和URLContext

  • 这两个结构体在FFmpeg的早期版本的SDK中是定义在头文件中可以直接使用的。但是近期的FFmpeg的SDK中已经找不到这两个结构体的定义了。FFmpeg把这两个结构体移动到了源代码的内部,变成了内部结构体。
  • URLProtocol的定义位于libavformat\url.h,如下所示。
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_whitelist()* or 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);const AVClass *priv_data_class;int priv_data_size;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;
  • 从URLProtocol的定义可以看出,其中包含了用于协议读写的函数指针。例如:
    • url_open():打开协议。
    • url_read():读数据。
    • url_write():写数据。
    • url_close():关闭协议。
  • 每种具体的协议都包含了一个URLProtocol结构体,例如:

FILE

  • FILE协议(“文件”在FFmpeg中也被当做一种协议)的结构体ff_file_protocol的定义如下所示(位于libavformat\file.c)
const URLProtocol ff_file_protocol = {.name                = "file",.url_open            = file_open,.url_read            = file_read,.url_write           = file_write,.url_seek            = file_seek,.url_close           = file_close,.url_get_file_handle = file_get_handle,.url_check           = file_check,.url_delete          = file_delete,.url_move            = file_move,.priv_data_size      = sizeof(FileContext),.priv_data_class     = &file_class,.url_open_dir        = file_open_dir,.url_read_dir        = file_read_dir,.url_close_dir       = file_close_dir,.default_whitelist   = "file,crypto,data"
};
  • 在使用FILE协议进行读写的时候,调用url_open()实际上就是调用了file_open()函数,这里限于篇幅不再对file_open()的源代码进行分析。
  • file_open()函数实际上调用了系统的打开文件函数open()。
  • 同理,调用url_read()实际上就是调用了file_read()函数;file_read()函数实际上调用了系统的读取文件函数read()。
  • url_write(),url_seek()等函数的道理都是一样的。

LibRTMP

  • LibRTMP协议的结构体ff_librtmp_protocol的定义如下所示(位于libavformat\librtmp.c)
RTMP_CLASS(rtmp)
const URLProtocol ff_librtmp_protocol = {.name                = "rtmp",.url_open            = rtmp_open,.url_read            = rtmp_read,.url_write           = rtmp_write,.url_close           = rtmp_close,.url_read_pause      = rtmp_read_pause,.url_read_seek       = rtmp_read_seek,.url_get_file_handle = rtmp_get_file_handle,.priv_data_size      = sizeof(LibRTMPContext),.priv_data_class     = &librtmp_class,.flags               = URL_PROTOCOL_FLAG_NETWORK,
};RTMP_CLASS(rtmpt)
const URLProtocol ff_librtmpt_protocol = {.name                = "rtmpt",.url_open            = rtmp_open,.url_read            = rtmp_read,.url_write           = rtmp_write,.url_close           = rtmp_close,.url_read_pause      = rtmp_read_pause,.url_read_seek       = rtmp_read_seek,.url_get_file_handle = rtmp_get_file_handle,.priv_data_size      = sizeof(LibRTMPContext),.priv_data_class     = &librtmpt_class,.flags               = URL_PROTOCOL_FLAG_NETWORK,
};RTMP_CLASS(rtmpe)
const URLProtocol ff_librtmpe_protocol = {.name                = "rtmpe",.url_open            = rtmp_open,.url_read            = rtmp_read,.url_write           = rtmp_write,.url_close           = rtmp_close,.url_read_pause      = rtmp_read_pause,.url_read_seek       = rtmp_read_seek,.url_get_file_handle = rtmp_get_file_handle,.priv_data_size      = sizeof(LibRTMPContext),.priv_data_class     = &librtmpe_class,.flags               = URL_PROTOCOL_FLAG_NETWORK,
};RTMP_CLASS(rtmpte)
const URLProtocol ff_librtmpte_protocol = {.name                = "rtmpte",.url_open            = rtmp_open,.url_read            = rtmp_read,.url_write           = rtmp_write,.url_close           = rtmp_close,.url_read_pause      = rtmp_read_pause,.url_read_seek       = rtmp_read_seek,.url_get_file_handle = rtmp_get_file_handle,.priv_data_size      = sizeof(LibRTMPContext),.priv_data_class     = &librtmpte_class,.flags               = URL_PROTOCOL_FLAG_NETWORK,
};RTMP_CLASS(rtmps)
const URLProtocol ff_librtmps_protocol = {.name                = "rtmps",.url_open            = rtmp_open,.url_read            = rtmp_read,.url_write           = rtmp_write,.url_close           = rtmp_close,.url_read_pause      = rtmp_read_pause,.url_read_seek       = rtmp_read_seek,.url_get_file_handle = rtmp_get_file_handle,.priv_data_size      = sizeof(LibRTMPContext),.priv_data_class     = &librtmps_class,.flags               = URL_PROTOCOL_FLAG_NETWORK,
};

URLContext

  • URLContext的定义也位于libavformat\url.h,如下所示。
typedef struct URLContext {const AVClass *av_class;    /**< information for av_log(). Set by url_open(). */const struct URLProtocol *prot;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;         /**< 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;
  • 从代码中可以看出,URLProtocol结构体是URLContext结构体的一个成员。
  • 由于还没有对URLContext结构体进行详细研究,有关该结构体的代码不再做过多分析。
     

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445974.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用Tomcat构建一个简单图片服务器

前提条件 Tomcat 7.0.90 方法一&#xff1a;修改配置文件 在TOMCAT_HOME/conf/server.xml配置文件内的<Host>内添加一子标签&#xff1a; <Context docBase"C:\exambase\" path"/img"/>方法二&#xff1a;添加Servlet 新建一应用&#xf…

FFmpeg源代码简单分析-通用-av_find_decoder()和av_find_encoder()

参考链接 FFmpeg源代码简单分析&#xff1a;av_find_decoder()和av_find_encoder()_雷霄骅的博客-CSDN博客_avcodec_find_encoder avcodec_find_encoder avcodec_find_encoder()用于查找FFmpeg的编码器avcodec_find_encoder()的声明位于libavcodec\codec.h 版本差异avcode…

post方法就反回了一个string字符串前台怎么接_Golang Web入门(2):如何实现一个RESTful风格的路由...

摘要在上一篇文章中&#xff0c;我们聊了聊在Golang中怎么实现一个Http服务器。但是在最后我们可以发现&#xff0c;固然DefaultServeMux可以做路由分发的功能&#xff0c;但是他的功能同样是不完善的。由DefaultServeMux做路由分发&#xff0c;是不能实现RESTful风格的API的&a…

FFmpeg源代码简单分析-通用-avcodec_open2()

参考链接 FFmpeg源代码简单分析&#xff1a;avcodec_open2()_雷霄骅的博客-CSDN博客 avcodec_open2() 该函数用于初始化一个音视频编解码器的AVCodecContextavcodec_open2()的声明位于libavcodec\avcodec.h&#xff0c;如下所示。 /*** Initialize the AVCodecContext to use…

统计MySQL中某数据库硬盘占用量大小

放码过来 select TABLE_NAME, concat(truncate(data_length/1024/1024,2), MB) as data_size, concat(truncate(index_length/1024/1024,2), MB) as index_size from information_schema.tables where TABLE_SCHEMA your_db_name order by data_length desc;运行结果 参考…

FFmpeg源代码简单分析-解码-打开媒体的函数avformat_open_input

参考链接 图解FFMPEG打开媒体的函数avformat_open_input_雷霄骅的博客-CSDN博客_avformat_open_input 使用FFmpeg源代码简单分析&#xff1a;avformat_open_input()_雷霄骅的博客-CSDN博客_avformat_open_input() avformat_open_input FFmpeg打开媒体的的过程开始于avformat_…

redis session java获取attribute_面试题:给我说说你能想到几种分布式session实现?...

作者&#xff1a;yanglbme 来源&#xff1a;https://github.com/doocs/advanced-java/blob/master/docs/distributed-system/distributed-session.md# 面试官心理分析面试官问了你一堆 dubbo 是怎么玩儿的&#xff0c;你会玩儿 dubbo 就可以把单块系统弄成分布式系统&#xff0…

FFmpeg源代码简单分析-解码-avformat_find_stream_info()

参考链接 FFmpeg源代码简单分析&#xff1a;avformat_find_stream_info()_雷霄骅的博客-CSDN博客_avformat_find_stream_info avformat_find_stream_info() ​该函数可以读取一部分视音频数据并且获得一些相关的信息avformat_find_stream_info()的声明位于libavformat\avform…

Tail Recursion尾递归

什么是尾递归 Tail Recursion /teɪl rɪˈkɜːrʒn/ In traditional recursion, the typical model is that you perform your recursive calls first, and then you take the return value of the recursive call and calculate the result. In this manner, you don’t g…

FFmpeg源代码简单分析-解码-av_read_frame()

参考链接 ffmpeg 源代码简单分析 &#xff1a; av_read_frame()_雷霄骅的博客-CSDN博客_ffmpeg frame av_read_frame() ffmpeg中的av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧。例如&#xff0c;解码视频的时候&#xff0c;每解码一个视频帧&#xff0c;需要…

FFmpeg源代码简单分析-解码-avformat_close_input()

参考链接 FFmpeg源代码简单分析&#xff1a;avformat_close_input()_雷霄骅的博客-CSDN博客_avformat_close_input avformat_close_input() 本文简单分析FFmpeg的avformat_close_input()函数。该函数用于关闭一个AVFormatContext&#xff0c;一般情况下是和avformat_open_inp…

FFmpeg源代码简单分析-编码-avformat_alloc_output_context2()

参考链接 FFmpeg源代码简单分析&#xff1a;avformat_alloc_output_context2()_雷霄骅的博客-CSDN博客_avformat_alloc_context avformat_alloc_output_context2() 在基于FFmpeg的视音频编码器程序中&#xff0c;该函数通常是第一个调用的函数&#xff08;除了组件注册函数av…

FFmpeg源代码简单分析-编码-avformat_write_header()

参考链接 FFmpeg源代码简单分析&#xff1a;avformat_write_header()_雷霄骅的博客-CSDN博客_avformat_write_header avformat_write_header() FFmpeg写文件用到的3个函数&#xff1a;avformat_write_header()&#xff0c;av_write_frame()以及av_write_trailer()其中av_writ…

《深入理解JVM.2nd》笔记(二):Java内存区域与内存溢出异常

文章目录概述运行时数据区域程序计数器Java虚拟机栈本地方法栈Java堆方法区运行时常量池直接内存HotSpot虚拟机对象探秘对象的创建第一步第二步第三步第四步最后一脚对象的内存布局对象头Header第一部分第二部分实例数据Instance对齐填充Padding对象的访问定位句柄直接指针对象…

《深入理解JVM.2nd》笔记(三):垃圾收集器与垃圾回收策略

文章目录概述对象已死吗引用计数算法可达性分析算法再谈引用finalize()&#xff1a;生存还是死亡回收方法区垃圾收集算法标记-清除算法复制算法标记-整理算法分代收集算法HotSpot的算法实现枚举根结点安全点安全区域垃圾收集器SerialParNewParallel ScavengeSerial OldParallel…

FFmpeg源代码简单分析-编码-av_write_frame()

参考链接 FFmpeg源代码简单分析&#xff1a;av_write_frame()_雷霄骅的博客-CSDN博客_av_write_frame av_write_frame() av_write_frame()用于输出一帧视音频数据&#xff0c;它的声明位于libavformat\avformat.h&#xff0c;如下所示。 /*** Write a packet to an output me…

《深入理解JVM.2nd》笔记(四):虚拟机性能监控与故障处理工具

文章目录概述JDK的命令行工具jps&#xff1a;虚拟机进程状况工具jstat&#xff1a;虚拟机统计信息监视工具jinfo&#xff1a;Java配置信息工具jmap&#xff1a;Java内存映像工具jhat&#xff1a;虚拟机堆转储快照分析工具jstack&#xff1a;Java堆栈跟踪工具HSDIS&#xff1a;J…

FFmpeg源代码简单分析-编码-av_write_trailer()

参考链接&#xff1a; FFmpeg源代码简单分析&#xff1a;av_write_trailer()_雷霄骅的博客-CSDN博客_av_malloc av_write_trailer() av_write_trailer()用于输出文件尾&#xff0c;它的声明位于libavformat\avformat.h&#xff0c;如下所示 /*** Write the stream trailer to…

FFmpeg源代码简单分析-其他-日志输出系统(av_log()等)

参考链接 FFmpeg源代码简单分析&#xff1a;日志输出系统&#xff08;av_log()等&#xff09;_雷霄骅的博客-CSDN博客_ffmpeg源码分析 日志输出系统&#xff08;av_log()等&#xff09; 本文分析一下FFmpeg的日志&#xff08;Log&#xff09;输出系统的源代码。日志输出部分的…

FFmpeg源代码简单分析-其他-AVClass和AVoption

参考链接 FFmpeg源代码简单分析&#xff1a;结构体成员管理系统-AVClass_雷霄骅的博客-CSDN博客FFmpeg源代码简单分析&#xff1a;结构体成员管理系统-AVOption_雷霄骅的博客-CSDN博客 概述 AVOption用于在FFmpeg中描述结构体中的成员变量。它最主要的作用可以概括为两个字&a…