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.pswp.cn/news/445974.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/445974.shtml
英文地址,請注明出處:http://en.pswp.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…

flash靜態的農夫走路_健身神動作——你不知道的“農夫行走”

原標題&#xff1a;健身神動作——你不知道的“農夫行走”本期導讀握力是訓練中及其重要的一環&#xff0c;強大的握力會使你的訓練效果MAX&#xff0c;就像開了加速器一樣&#xff01;很多人把握力和前臂力量混為一談&#xff0c;主要使用腕彎舉提高握力。實際上&#xff0c;握…

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…

用Java的Set實現交并差等集合運算

放碼過來 package com.lun.util;import java.util.HashSet; import java.util.Set;public class SetUtils {public static <T> Set<T> union(Set<T> setA, Set<T> setB) {Set<T> tmp new HashSet<T>(setA);tmp.addAll(setB);return tmp;…

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;運行結果 參考…

halcon 相似度_Halcon分類函數,shape模型

《zw版Halcon-delphi系列原創教程》 Halcon分類函數013,shape模型為方便閱讀&#xff0c;在不影響說明的前提下&#xff0c;筆者對函數進行了簡化&#xff1a;:: 用符號“**”&#xff0c;替換&#xff1a;“procedure”:: 用大寫字母“X”&#xff0c;替換&#xff1a;“IHUnt…

用Python將文件夾打包成Zip并備份至U盤

需求概要 將maven工程打包并備份至U盤。為了簡單起見&#xff0c;只需備份工程中的src文件夾和pom.xml文件即可。 放碼過來 import os import zipfile import datetime import shutilnowTimeStr datetime.datetime.now().strftime("%Y%m%d%H%M") newZipFileName …

FFmpeg源代碼簡單分析-通用-avcodec_close()

參考鏈接 FFmpeg源代碼簡單分析&#xff1a;avcodec_close()_雷霄驊的博客-CSDN博客_avcodec_close avcodec_close() 該函數用于關閉編碼器avcodec_close()函數的聲明位于libavcodec\avcodec.h&#xff0c;如下所示。 ?該函數只有一個參數&#xff0c;就是需要關閉的編碼器的…

redis 緩存過期默認時間_redis緩存過期機制

筆者在線上使用redis緩存的時候發現即使某些查詢已經設置了無過期時間的緩存,但是查詢仍然非常耗時。經過排查,發現緩存確實已經不存在,導致了緩存擊穿,查詢直接訪問了mysql數據庫。因為我們用的是公司公共的redis緩存服務器,在和運維人員交流后發現可能是redis的內存淘汰…

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…

Projection投影

解釋一 Projection means choosing which columns (or expressions) the query shall return. Selection means which rows are to be returned. if the query is select a, b, c from foobar where x3;then “a, b, c” is the projection part, “where x3” the selecti…

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…

python遞歸算法案例教案_python教案

第五單元進階程序設計(總10課時)第一節選擇編程語言(1課時)一、教學目標1、了解程序設計語言和兩種翻譯方式&#xff1b;2、了解Python背景、功能、安裝&#xff0c;熟悉Python編程環境&#xff1b;3、編程初體驗。體驗一個小程序從建立、輸入、調試、運行、保存的全過程。掌握…

FFmpeg源代碼簡單分析-解碼-av_read_frame()

參考鏈接 ffmpeg 源代碼簡單分析 &#xff1a; av_read_frame()_雷霄驊的博客-CSDN博客_ffmpeg frame av_read_frame() ffmpeg中的av_read_frame()的作用是讀取碼流中的音頻若干幀或者視頻一幀。例如&#xff0c;解碼視頻的時候&#xff0c;每解碼一個視頻幀&#xff0c;需要…

數據庫 流量切分_私域流量之社群運營技巧,社群運營技巧解析

一、明白社群運營的目的1、社群的目的確立任何一個社群(組織)成立的時分&#xff0c;都是承載著一定的目的的&#xff0c;這個目的就像是北極星一樣&#xff0c;指引著我們的方向。確立運營目的的過程&#xff0c;也是在尋覓北極星的過程。社群運營屬于觸達用戶的一種方式&…

用Python在Tomcat成功啟動后自動打開瀏覽器訪問Web應用

前提條件 WindowsPython 2.7需設置CATALINA_HOME環境變量 放碼過來 # -*- coding: utf-8 -* import os import time import subprocesstomcatStartFilePath C:\\tomcat\\apache-tomcat-7.0.90-windows-x64\\apache-tomcat-7.0.90\\bin\\startup.bat browserPath C:\\Users…