FFmpeg原始碼簡單分析 avio open2
=====================================================
FFmpeg的庫函式原始碼分析文章列表:
【架構圖】
【通用】
【解碼】
【編碼】
【其它】
【指令碼】
【H.264】
=====================================================
本文簡單分析FFmpeg中一個常用的函式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:開啟地址的方式。可以選擇只讀,只寫,或者讀寫。取值如下。AVIO_FLAG_READ:只讀。AVIO_FLAG_WRITE:只寫。AVIO_FLAG_READ_WRITE:讀寫。int_cb:目前還沒有用過。options:目前還沒有用過。
函式呼叫結構圖
首先貼出來最終分析得出的函式呼叫結構圖,如下所示。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()
下面看一下avio_open2()的原始碼,位於libavformat\aviobuf.c檔案中。int avio_open2(AVIOContext **s, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options){ URLContext *h; int err; err = ffurl_open(&h, filename, flags, int_cb, options); if (err < 0) return err; err = ffio_fdopen(s, h); if (err < 0) { ffurl_close(h); return err; } return 0;}
從avio_open2()的原始碼可以看出,它主要呼叫了2個函式:ffurl_open()和ffio_fdopen()。其中ffurl_open()用於初始化URLContext,ffio_fdopen()用於根據URLContext初始化AVIOContext。URLContext中包含的URLProtocol完成了具體的協議讀寫等工作。AVIOContext則是在URLContext的讀寫函式外面加上了一層“包裝”(通過retry_transfer_wrapper()函式)。
URLProtocol和URLContext
在檢視ffurl_open()和ffio_fdopen()函式之前,首先檢視一下URLContext和URLProtocol的定義。這兩個結構體在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()/ffurl_connect() * for those nested protocols. */ int (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options); /** * 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); struct URLProtocol *next; 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_shutdown)(URLContext *h, int flags); int priv_data_size; const AVClass *priv_data_class; int flags; int (*url_check)(URLContext *h, int mask);} URLProtocol;
從URLProtocol的定義可以看出,其中包含了用於協議讀寫的函式指標。例如:url_open():開啟協議。url_read():讀資料。url_write():寫資料。url_close():關閉協議。每種具體的協議都包含了一個URLProtocol結構體,例如:FILE協議(“檔案”在FFmpeg中也被當做一種協議)的結構體ff_file_protocol的定義如下所示(位於libavformat\file.c)。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, .priv_data_size = sizeof(FileContext), .priv_data_class = &file_class,};
在使用FILE協議進行讀寫的時候,呼叫url_open()實際上就是呼叫了file_open()函式,這裡限於篇幅不再對file_open()的原始碼進行分析。file_open()函式實際上呼叫了系統的開啟檔案函式open()。同理,呼叫url_read()實際上就是呼叫了file_read()函式;file_read()函式實際上呼叫了系統的讀取檔案函式read()。url_write(),url_seek()等函式的道理都是一樣的。LibRTMP協議的結構體ff_librtmp_protocol的定義如下所示(位於libavformat\librtmp.c)。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,};
UDP協議的結構體ff_udp_protocol的定義如下所示(位於libavformat\udp.c)。URLProtocol ff_udp_protocol = { .name = "udp", .url_open = udp_open, .url_read = udp_read, .url_write = udp_write, .url_close = udp_close, .url_get_file_handle = udp_get_file_handle, .priv_data_size = sizeof(UDPContext), .priv_data_class = &udp_context_class, .flags = URL_PROTOCOL_FLAG_NETWORK,};
上文中簡單介紹了URLProtocol結構體。下面看一下URLContext結構體。URLContext的定義也位於libavformat\url.h,如下所示。typedef struct URLContext { const AVClass *av_class; /**< information for av_log(). Set by url_open(). */ 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 */} URLContext;
從程式碼中可以看出,URLProtocol結構體是URLContext結構體的一個成員。由於還沒有對URLContext結構體進行詳細研究,有關該結構體的程式碼不再做過多分析。ffurl_open()
前文提到AVIOContext中主要呼叫了2個函式:ffurl_open()和ffio_fdopen()。其中ffurl_open()用於初始化URLContext,ffio_fdopen()用於根據URLContext初始化AVIOContext。下面首先看一下初始化URLContext的函式ffurl_open()。ffurl_open()的函式定義位於libavformat\avio.c中,如下所示。int ffurl_open(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options){ int ret = ffurl_alloc(puc, filename, flags, int_cb); if (ret < 0) return ret; if (options && (*puc)->prot->priv_data_class && (ret = av_opt_set_dict((*puc)->priv_data, options)) < 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_close(*puc); *puc = NULL; return ret;}
從程式碼中可以看出,ffurl_open()主要呼叫了2個函式:ffurl_alloc()和ffurl_connect()。ffurl_alloc()用於查詢合適的URLProtocol,並建立一個URLContext;ffurl_connect()用於開啟獲得的URLProtocol。
ffurl_alloc()
ffurl_alloc()的定義位於libavformat\avio.c中,如下所示。int ffurl_alloc(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb){ URLProtocol *p = NULL; if (!first_protocol) { av_log(NULL, AV_LOG_WARNING, "No URL Protocols are registered. " "Missing call to av_register_all()?\n"); } 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)) av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile with openssl or gnutls enabled.\n"); return AVERROR_PROTOCOL_NOT_FOUND;}
從程式碼中可以看出,ffurl_alloc()主要呼叫了2個函式:url_find_protocol()根據檔案路徑查詢合適的URLProtocol,url_alloc_for_protocol()為查詢到的URLProtocol建立URLContext。
url_find_protocol()
先來看一下url_find_protocol()函式,定義如下所示。#define URL_SCHEME_CHARS \ "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "0123456789+-."static struct URLProtocol *url_find_protocol(const char *filename){ URLProtocol *up = NULL; char proto_str[128], proto_nested[128], *ptr; size_t proto_len = strspn(filename, URL_SCHEME_CHARS); if (filename[proto_len] != ':' && (filename[proto_len] != ',' || !strchr(filename + proto_len + 1, ':')) || is_dos_path(filename)) strcpy(proto_str, "file"); else av_strlcpy(proto_str, filename, FFMIN(proto_len + 1, sizeof(proto_str))); if ((ptr = strchr(proto_str, ','))) *ptr = '\0'; av_strlcpy(proto_nested, proto_str, sizeof(proto_nested)); if ((ptr = strchr(proto_nested, '+'))) *ptr = '\0'; while (up = ffurl_protocol_next(up)) { if (!strcmp(proto_str, up->name)) break; if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME && !strcmp(proto_nested, up->name)) break; } return up;}
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字串中。PS:
這個地方比較糾結,原始碼中av_strlcpy()函式的第3個引數size寫的字串的長度是(proto_len+1),但是查了一下av_strlcpy()的定義,發現該函式至多拷貝(size-1)個字元。這麼一漲一消,最終還是拷貝了proto_len個位元組。例如RTMP協議就拷貝了“rtmp”,UDP協議就拷貝了“udp”。
av_strlcpy()是FFMpeg的一個工具函式,宣告位於libavutil\avstring.h,如下所示。
/** * Copy the string src to dst, but no more than size - 1 bytes, and * null-terminate dst. * * This function is the same as BSD strlcpy(). * * @param dst destination buffer * @param src source string * @param size size of destination buffer * @return the length of src * * @warning since the return value is the length of src, src absolutely * _must_ be a properly 0-terminated string, otherwise this will read beyond * the end of the buffer and possibly crash. */size_t av_strlcpy(char *dst, const char *src, size_t size);
這裡有一種例外,那就是檔案路徑。“檔案”在FFmpeg中也是一種“協議”,並且字首是“file”。也就是標準的檔案路徑應該是“file://...”格式的。但是這太不符合我們一般人的使用習慣,我們一般是不會在檔案路徑前面加上“file”協議名稱的。所以該函式採取的方法是:一旦檢測出來輸入的URL是檔案路徑而不是網路協議,就自動向proto_str中拷貝“file”。
其中判斷檔案路徑那裡有一個很複雜的if()語句。根據我的理解,“||”前面的語句用於判斷是否是相對檔案路徑,“||”後面的語句用於判斷是否是絕對路徑。判斷絕對路徑的時候用到了一個函式is_dos_path(),定義位於libavformat\os_support.h,如下所示。static inline int is_dos_path(const char *path){#if HAVE_DOS_PATHS if (path[0] && path[1] == ':') return 1;#endif return 0;}
注意“&&”優先順序低於“==”。如果檔案路徑第1個字元不為空(一般情況下是碟符)而且第2個字元為“:”,就認為它是絕對檔案路徑。此外url_find_protocol()函式中還涉及到一個函式ffurl_protocol_next()。該函式用於獲得下一個URLProtocol(所有的URLProtocol在FFmpeg初始化註冊的時候形成一個連結串列結構)。ffurl_protocol_next()程式碼極其簡單,如下所示。URLProtocol *ffurl_protocol_next(const URLProtocol *prev){ return prev ? prev->next : first_protocol;}
url_alloc_for_protocol()
url_alloc_for_protocol()的定義位於libavformat\avio.c中,如下所示。static int url_alloc_for_protocol(URLContext **puc, struct URLProtocol *up, const char *filename, int flags, const AVIOInterruptCB *int_cb){ URLContext *uc; int err;#if CONFIG_NETWORK if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init()) return AVERROR(EIO);#endif if ((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) { int proto_len= strlen(up->name); char *start = strchr(uc->filename, ','); *(const AVClass **)uc->priv_data = up->priv_data_class; av_opt_set_defaults(uc->priv_data); if(!strncmp(up->name, uc->filename, proto_len) && uc->filename + proto_len == start){ int ret= 0; char *p= start; char sep= *++p; char *key, *val; p++; while(ret >= 0 && (key= strchr(p, sep)) && p<key && (val = strchr(key+1, sep))){ *val= *key= 0; ret= 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_NETWORK if (up->flags & URL_PROTOCOL_FLAG_NETWORK) ff_network_close();#endif return 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 = uc->prot->url_open2 ? uc->prot->url_open2(uc, uc->filename, uc->flags, options) : uc->prot->url_open(uc, uc->filename, uc->flags); 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()。
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中,如下所示。
#define IO_BUFFER_SIZE 32768int ffio_fdopen(AVIOContext **s, URLContext *h){ uint8_t *buffer; 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 = av_malloc(buffer_size); if (!buffer) return AVERROR(ENOMEM); *s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h, (void*)ffurl_read, (void*)ffurl_write, (void*)ffurl_seek); if (!*s) { av_free(buffer); 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; 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; } (*s)->av_class = &ffio_url_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中,如下所示。/** * Allocate and initialize an AVIOContext for buffered I/O. It must be later * freed with av_free(). * * @param buffer Memory block for input/output operations via AVIOContext. * The buffer must be allocated with av_malloc() and friends. * @param buffer_size The buffer size is very important for performance. * For protocols with fixed blocksize it should be set to this blocksize. * For others a typical size is a cache page, e.g. 4kb. * @param write_flag Set to 1 if the buffer should be writable, 0 otherwise. * @param opaque An opaque pointer to user-specific data. * @param read_packet A function for refilling the buffer, may be NULL. * @param write_packet A function for writing the buffer contents, may be NULL. * The function may not change the input buffers content. * @param seek A function for seeking to specified byte position, may be NULL. * * @return Allocated AVIOContext or NULL on failure. */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));
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。下面看一下avio_alloc_context()的定義,位於libavformat\aviobuf.c,如下所示。
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)); if (!s) return NULL; ffio_init_context(s, buffer, buffer_size, write_flag, opaque, read_packet, write_packet, seek); return s;}
該函式程式碼很簡單:首先呼叫av_mallocz()為AVIOContext分配一塊記憶體空間,然後基本上將所有輸入引數傳遞給ffio_init_context()。ffio_init_context()
ffio_init_context()的定義如下。int ffio_init_context(AVIOContext *s, 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)){ s->buffer = buffer; s->orig_buffer_size = s->buffer_size = buffer_size; s->buf_ptr = 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->must_flush = 0; s->eof_reached = 0; s->error = 0; s->seekable = seek ? AVIO_SEEKABLE_NORMAL : 0; s->max_packet_size = 0; s->update_checksum = NULL; if (!read_packet && !write_flag) { s->pos = buffer_size; s->buf_end = s->buffer + buffer_size; } s->read_pause = NULL; s->read_seek = NULL; return 0;}
可以看出,這個函式的工作就是各種賦值,不算很有“技術含量”,不再詳述。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, (void*)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 < 1) return (ret < 0 && ret != AVERROR_EOF) ? ret : len; if (ret) fast_retries = FFMAX(fast_retries, 2); len += ret; } return len;}
從程式碼中可以看出,它的核心實際上是呼叫了一個名稱為transfer_func()的函式。而該函式就是retry_transfer_wrapper()的第四個引數。該函式實際上是對URLProtocol的讀寫操作中的錯誤進行了一些“容錯”處理,可以讓資料的讀寫更加的穩定。avio_alloc_context()執行完畢後,ffio_fdopen()函式的工作就基本完成了,avio_open2()的工作也就做完了。
雷霄驊 (Lei Xiaohua)[email protected]://blog.csdn.net/leixiaohua1020