概述
目录(?) [+]
- 函数调用结构图
- avio_open
- avio_open2
- URLProtocol和URLContext
- ffurl_open
- ffurl_alloc
- url_find_protocol
- url_alloc_for_protocol
- ffurl_connect
- ffio_fdopen
- avio_alloc_context
- ffio_init_context
- ffurl_readffurl_writeffurl_seek
本文简单分析FFmpeg中一个常用的函数avio_open2()。该函数用于打开FFmpeg的输入输出文件。avio_open2()的声明位于libavformatavio.h文件中,如下所示。
avio_open2()函数参数的含义如下:
URLProtocol的定义位于libavformaturl.h,如下所示。
从URLProtocol的定义可以看出,其中包含了用于协议读写的函数指针。例如:
url_open():打开协议。
url_read():读数据。
url_write():写数据。
url_close():关闭协议。
每种具体的协议都包含了一个URLProtocol结构体,例如:
FILE协议(“文件”在FFmpeg中也被当做一种协议)的结构体ff_file_protocol的定义如下所示(位于libavformatfile.c)。
在使用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的定义如下所示(位于libavformatlibrtmp.c)。
UDP协议的结构体ff_udp_protocol的定义如下所示(位于libavformatudp.c)。
上文中简单介绍了URLProtocol结构体。下面看一下URLContext结构体。URLContext的定义也位于libavformaturl.h,如下所示。
从代码中可以看出,URLProtocol结构体是URLContext结构体的一个成员。由于还没有对URLContext结构体进行详细研究,有关该结构体的代码不再做过多分析。
ffurl_open()的函数定义位于libavformatavio.c中,如下所示。
- /**
- * 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);
/**
* 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:目前还没有用过。
该函数最典型的例子可以参考:最简单的基于FFMPEG的视频编码器(YUV编码为H.264)
函数调用结构图
首先贴出来最终分析得出的函数调用结构图,如下所示。单击查看更清晰的图片
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);
- }
int avio_open(AVIOContext **s, const char *filename, int flags)
{
return avio_open2(s, filename, flags, NULL, NULL);
}
avio_open2()
下面看一下avio_open2()的源代码,位于libavformataviobuf.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;
- }
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的定义位于libavformaturl.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;
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的定义如下所示(位于libavformatfile.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,
- };
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的定义如下所示(位于libavformatlibrtmp.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,
- };
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的定义如下所示(位于libavformatudp.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 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的定义也位于libavformaturl.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;
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()的函数定义位于libavformatavio.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;
- }
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()的定义位于libavformatavio.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;
- }
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 = '