參考鏈接
- 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結構體進行詳細研究,有關該結構體的代碼不再做過多分析。
?