美文网首页
FFmpeg IO源码层面理解

FFmpeg IO源码层面理解

作者: krislyy_ | 来源:发表于2018-11-08 15:09 被阅读0次

一直在使用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.png

FFmpeg的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 
};

在这其中主要涉及几个重要的数据结构:

  1. 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;
  1. 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层传输的大致流程就梳理出来了。这种面向对象的封装过程,是需要我慢慢学习和体会消化的。阅读源码乐趣无穷,希望你也乐在其中。

相关文章

网友评论

      本文标题:FFmpeg IO源码层面理解

      本文链接:https://www.haomeiwen.com/subject/ymjwxqtx.html