一直在使用FFmpeg做各种音视频相关的操作,可是如果有人问你,你知道一个URL地址从用户输入到连接到服务器这个过程(FFmpeg的IO操作)底层代码是如何实现的你能说明白吗?这篇文章就来从源码的层面来看看FFmpeg是如何巧妙的实现IO操作的。例如: rtmp://127.0.0.1:1935/livestream/1234。
1.一般的调用ffmpeg第一步流程
avfilter_register_all();
av_register_all();
//初始化网络支持
avformat_network_init();
2.第二步流程,在独立的线程中read_frame
AVFormatContext *ic = NULL;
......
ic = avformat_alloc_context();
......
err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
3. 第三步就可以直接读取数据包了
ret = av_read_frame(ic, pkt);
上述代码中,第二个步骤就是对IO的封装了。
FFmpeg的IO层次,不同的封装层次:
ffmpeg_io.pngFFmpeg的IO操作主要在libavformat库中实现,部分实现用到了libavutil中的工具。所谓IO就是数据的存取,主要的途径也就是文件或者网络。数据IO是基于文件格式的,与具体的编码标准无关。FFmpeg对各种协议实现了封装,使用同样的接口,完成对不同数据的读取。例如在libavformat/protocol_list.c(此文件为动态生成文件,在编译时由shell脚本文件生成)中展示了支持的所有传输协议。
static const URLProtocol * const url_protocols[] = {
&ff_async_protocol,
&ff_cache_protocol,
&ff_data_protocol,
&ff_ffrtmphttp_protocol,
&ff_file_protocol,
&ff_ftp_protocol,
&ff_hls_protocol,
&ff_http_protocol,
&ff_httpproxy_protocol,
&ff_https_protocol,
&ff_ijkhttphook_protocol,
&ff_ijklongurl_protocol,
&ff_ijkmediadatasource_protocol,
&ff_ijksegment_protocol,
&ff_ijktcphook_protocol,
&ff_ijkio_protocol,
&ff_pipe_protocol,
&ff_prompeg_protocol,
&ff_rtmp_protocol, //这就是我们将要用到的rtmp协议
&ff_rtmpt_protocol,
&ff_tee_protocol,
&ff_tcp_protocol, //这就是我们将要用到的tcp协议
&ff_tls_openssl_protocol,
&ff_udp_protocol,
&ff_udplite_protocol,
NULL
};
在这其中主要涉及几个重要的数据结构:
- AVFormatContext 用于封装输入输出的个各种数据类型和结构
/**
* Format I/O context.
* New fields can be added to the end with minor version bumps.
* Removal, reordering and changes to existing fields require a major
* version bump.
* sizeof(AVFormatContext) must not be used outside libav*, use
* avformat_alloc_context() to create an AVFormatContext.
*
* Fields can be accessed through AVOptions (av_opt*),
* the name string used matches the associated command line parameter name and
* can be found in libavformat/options_table.h.
* The AVOption/command line parameter names differ in some cases from the C
* structure field names for historic reasons or brevity.
*/
typedef struct AVFormatContext {
/**
* A class for logging and @ref avoptions. Set by avformat_alloc_context().
* Exports (de)muxer private options if they exist.
*/
const AVClass *av_class;
/**
* The input container format.
*
* Demuxing only, set by avformat_open_input().
*/
struct AVInputFormat *iformat;
/**
* The output container format.
*
* Muxing only, must be set by the caller before avformat_write_header().
*/
struct AVOutputFormat *oformat;
/**
* Format private data. This is an AVOptions-enabled struct
* if and only if iformat/oformat.priv_class is not NULL.
*
* - muxing: set by avformat_write_header()
* - demuxing: set by avformat_open_input()
*/
void *priv_data; //这个存储的使自定义的context
/**
* I/O context.
*
* - demuxing: either set by the user before avformat_open_input() (then
* the user must close it manually) or set by avformat_open_input().
* - muxing: set by the user before avformat_write_header(). The caller must
* take care of closing / freeing the IO context.
*
* Do NOT set this field if AVFMT_NOFILE flag is set in
* iformat/oformat.flags. In such a case, the (de)muxer will handle
* I/O in some other way and this field will be NULL.
*/
AVIOContext *pb; //这是本文的核心结构体,IO就靠他传递
//省略其他与本章无关数据结构体
......
/**
* ',' separated list of allowed protocols.
* - encoding: unused
* - decoding: set by user through AVOptions (NO direct access)
*/
char *protocol_whitelist;
/*
* A callback for opening new IO streams.
*
* Whenever a muxer or a demuxer needs to open an IO stream (typically from
* avformat_open_input() for demuxers, but for certain formats can happen at
* other times as well), it will call this callback to obtain an IO context.
*
* @param s the format context
* @param pb on success, the newly opened IO context should be returned here
* @param url the url to open
* @param flags a combination of AVIO_FLAG_*
* @param options a dictionary of additional options, with the same
* semantics as in avio_open2()
* @return 0 on success, a negative AVERROR code on failure
*
* @note Certain muxers and demuxers do nesting, i.e. they open one or more
* additional internal format contexts. Thus the AVFormatContext pointer
* passed to this callback may be different from the one facing the caller.
* It will, however, have the same 'opaque' field.
*/
//这个回调函数注册进来,用于提供默认的打开协议操作,很关键!!!
int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,
int flags, AVDictionary **options);
/**
* A callback for closing the streams opened with AVFormatContext.io_open().
*/
void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);
/**
* ',' separated list of disallowed protocols.
* - encoding: unused
* - decoding: set by user through AVOptions (NO direct access)
*/
char *protocol_blacklist;
/**
* The maximum number of streams.
* - encoding: unused
* - decoding: set by user through AVOptions (NO direct access)
*/
int max_streams;
} AVFormatContext;
- AVIOContext, 在函数avformat_alloc_context()中
AVFormatContext *avformat_alloc_context(void)
{
AVFormatContext *ic;
ic = av_malloc(sizeof(AVFormatContext)); //申请了AVFormatContext空间
if (!ic) return ic;
/*这个函数很关键,它里面初始化了AVFormatContext,
*并给io_open和io_close赋值了默认打开函数,
*这个将在之后的avformat_open_input中被调用,是实现协议分装的很重要一步骤。
*/
avformat_get_context_defaults(ic);
ic->internal = av_mallocz(sizeof(*ic->internal));
if (!ic->internal) {
avformat_free_context(ic);
return NULL;
}
ic->internal->offset = AV_NOPTS_VALUE;
ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
ic->internal->shortest_end = AV_NOPTS_VALUE;
return ic;
}
下面来看一下avformat_get_context_defaults()是如何实现的。
static void avformat_get_context_defaults(AVFormatContext *s)
{
//初始化
memset(s, 0, sizeof(AVFormatContext));
//赋值默认的av_class
s->av_class = &av_format_context_class;
//提供了默认的open和close函数,这个是打开协议的核心代码
s->io_open = io_open_default;
s->io_close = io_close_default;
av_opt_set_defaults(s);
}
在深入的看看io_open_default()是如何实现的。
static int io_open_default(AVFormatContext *s, AVIOContext **pb,
const char *url, int flags, AVDictionary **options)
{
#if FF_API_OLD_OPEN_CALLBACKS
FF_DISABLE_DEPRECATION_WARNINGS
if (s->open_cb)
return s->open_cb(s, pb, url, flags, &s->interrupt_callback, options);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
//这个函数里就真正的跳转到打开协议的核心代码里了
return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
}
深入到这个函数我们就会发现出现了一个关键的结构体AVIOContext,下面让我们看看这个结构体的实现。
/**
* Bytestream IO Context.
* New fields can be added to the end with minor version bumps.
* Removal, reordering and changes to existing fields require a major
* version bump.
* sizeof(AVIOContext) must not be used outside libav*.
*
* @note None of the function pointers in AVIOContext should be called
* directly, they should only be set by the client application
* when implementing custom I/O. Normally these are set to the
* function pointers specified in avio_alloc_context()
*/
typedef struct AVIOContext {
/**
* A class for private options.
*
* If this AVIOContext is created by avio_open2(), av_class is set and
* passes the options down to protocols.
*
* If this AVIOContext is manually allocated, then av_class may be set by
* the caller.
*
* warning -- this field can be NULL, be sure to not pass this AVIOContext
* to any av_opt_* functions in that case.
*/
const AVClass *av_class;
......
void *opaque; /**< A private pointer, passed to the read/write/seek/...
functions. */
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);
/**
* Pause or resume playback for network streaming protocols - e.g. MMS.
*/
int (*read_pause)(void *opaque, int pause);
/**
* Seek to a given timestamp in stream with the specified stream_index.
* Needed for some network streaming protocols which don't support seeking
* to byte position.
*/
int64_t (*read_seek)(void *opaque, int stream_index,
int64_t timestamp, int flags);
......
/**
* ',' separated list of allowed protocols.
*/
const char *protocol_whitelist;
/**
* ',' separated list of disallowed protocols.
*/
const char *protocol_blacklist;
......
/**
* A callback that is used instead of short_seek_threshold.
* This is current internal only, do not use from outside.
*/
int (*short_seek_get)(void *opaque);
} AVIOContext;
到这里我们需要回过头来看函数avformat_open_input()的实现,在里面将会为AVIOContext分配空间并关联相应的的接口函数
int avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)
{
AVFormatContext *s = *ps;
int i, ret = 0;
AVDictionary *tmp = NULL;
ID3v2ExtraMeta *id3v2_extra_meta = NULL;
......
//此处是关联上面提到的io_open的核心函数
if ((ret = init_input(s, filename, &tmp)) < 0)
goto fail;
s->probe_score = ret;
//省略之后无关代码
......
}
/* Open input file and probe the format if necessary. */
static int init_input(AVFormatContext *s, const char *filename,
AVDictionary **options)
{
int ret;
AVProbeData pd = { filename, NULL, 0 };
int score = AVPROBE_SCORE_RETRY;
.....
//此处是关键,注意这个枚举值AVIO_FLAG_READ,后面会用到
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
return ret;
if (s->iformat)
return 0;
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
}
分析到此处我们又要回头去看函数ffio_open_whitelist()的实现了。
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;
//此处寻找协议并申请URLContext空间,然后关联上URLProtocol,就是最开头列出的配置允许的协议。此处会匹配rtmp协议ff_rtmp_protocol
err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);
if (err < 0)
return err;
//此处关联了URLContext和AVFormatContext, 在函数ffio_fdopen中调用了avio_alloc_context()函数,用于申请空间和赋关联值
err = ffio_fdopen(s, h);
if (err < 0) {
ffurl_close(h);
return err;
}
return 0;
}
因此我们在这里就有必要看看下URLContext和URLProtocol的结构体实现了。
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;
} URLContext;
//提供了协议相关的通用接口,所有的协议都会实现这些接口中相应的功能
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);
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);
int priv_data_size;
const AVClass *priv_data_class;
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;
接下来我们看下rtmp和tcp两种协议的具体实现。
#define RTMP_PROTOCOL(flavor) \
static const AVClass flavor##_class = { \
.class_name = #flavor, \
.item_name = av_default_item_name, \
.option = rtmp_options, \
.version = LIBAVUTIL_VERSION_INT, \
}; \
\
const URLProtocol ff_##flavor##_protocol = { \
.name = #flavor, \
//在rtmp_open中调用了ff_tcp_protocol,用于传输数据
.url_open = rtmp_open, \
.url_read = rtmp_read, \
.url_read_seek = rtmp_seek, \
.url_read_pause = rtmp_pause, \
.url_write = rtmp_write, \
.url_close = rtmp_close, \
.priv_data_size = sizeof(RTMPContext), \
.flags = URL_PROTOCOL_FLAG_NETWORK, \
.priv_data_class= &flavor##_class, \
};
RTMP_PROTOCOL(rtmp)
RTMP_PROTOCOL(rtmpe)
RTMP_PROTOCOL(rtmps)
RTMP_PROTOCOL(rtmpt)
RTMP_PROTOCOL(rtmpte)
RTMP_PROTOCOL(rtmpts)
const URLProtocol ff_tcp_protocol = {
.name = "tcp",
.url_open = tcp_open,
.url_accept = tcp_accept,
.url_read = tcp_read,
.url_write = tcp_write,
.url_close = tcp_close,
.url_get_file_handle = tcp_get_file_handle,
.url_get_short_seek = tcp_get_window_size,
.url_shutdown = tcp_shutdown,
.priv_data_size = sizeof(TCPContext),
.flags = URL_PROTOCOL_FLAG_NETWORK,
.priv_data_class = &tcp_class,
};
结束语
到此关于FFmpeg的整个IO层传输的大致流程就梳理出来了。这种面向对象的封装过程,是需要我慢慢学习和体会消化的。阅读源码乐趣无穷,希望你也乐在其中。
网友评论