FFmpeg的IO模型从avio_open()方法开始,核心结构体由AVIOContext和URLProtocol组成。如果需要读取缓冲区buffer数据进行播放,可以通过自定义AVIOContext,并且实现read_packet、write_packet、seek三个方法。如果需要播放加密视频,可以自定义私有协议进行解密,实现URLProtocol的open、read、write、seek、close等方法。

 

1、avio打开流程

外部调用avformat_open_input方法,内部会调用init_input/avio_open2方法,接着查找协议并且打开,另外是打开fifo。整体的avio打开流程如下:

 avio_open()调用avio_open2(),而avio_open2()又调用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;

    *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;
}

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);
}

int avio_open(AVIOContext **s, const char *filename, int flags)
{
    return avio_open2(s, filename, flags, NULL, NULL);
}

2、AVIOContext结构体

AVIOContext的结构体主要有read_packet、write_packet、seek、read_seek等方法,内部有注释演示读写buffer过程,代码如下:

/**
 * Bytestream IO Context.
 * @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 {
    const AVClass *av_class;
    /*
     * The following shows the relationship between buffer, buf_ptr,
     * buf_ptr_max, buf_end, buf_size, and pos, when reading and when writing
     *
     **********************************************************************************
     *                                   READING
     **********************************************************************************
     *                            |              buffer_size              |
     *                            |---------------------------------------|
     *                            |                                       |
     *
     *                         buffer          buf_ptr       buf_end
     *                            +---------------+-----------------------+
     *                            |/ / / / / / / /|/ / / / / / /|         |
     *  read buffer:              |/ / consumed / | to be read /|         |
     *                            |/ / / / / / / /|/ / / / / / /|         |
     *                            +---------------+-----------------------+
     *
     **********************************************************************************
     *                                   WRITING
     **********************************************************************************
     *
     *                             |          buffer_size                 |
     *                             |--------------------------------------|
     *                             |                                      |
     *
     *                                                buf_ptr_max
     *                          buffer                 (buf_ptr)       buf_end
     *                             +-----------------------+--------------+
     *                             |/ / / / / / / / / / / /|              |
     *  write buffer:              | / / to be flushed / / |              |
     *                             |/ / / / / / / / / / / /|              |
     *                             +-----------------------+--------------+
     *                               buf_ptr can be in this due to a backward seek
     *
     */
    unsigned char *buffer;  /**< Start of the buffer. */
    int buffer_size;        /**< Maximum buffer size */
    unsigned char *buf_ptr; /**< Current position in the buffer */
    unsigned char *buf_end; /**< End of the data, may be less than buffer+buffer_size*/
    void *opaque;           /**< A private pointer, passed to the read/write/seek */
    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);
    int64_t pos;            /**< position in the file of the current buffer */
    int eof_reached;        /**< true if was unable to read due to error or eof */
    int write_flag;         /**< true if open for writing */
    int max_packet_size;
    int error;              /**< contains the error code or 0 if no error happened */
    int64_t (*read_seek)(void *opaque, int stream_index, int64_t timestamp, int flags);
    int64_t maxsize;
    int64_t bytes_read;
    int orig_buffer_size;
    int short_seek_threshold;
    const char *protocol_whitelist;
    const char *protocol_blacklist;
    int64_t written;
    unsigned char *buf_ptr_max;
    int min_packet_size;
} AVIOContext;

3、URLProtocol结构体

URLProtocol的结构体url_open、url_accpet、url_handshake、url_read、url_write、url_seek、url_close等方法,其中url_open()是对url_open2()的封装,代码如下:

typedef struct URLProtocol {
    const char *name;
    int     (*url_open)( URLContext *h, const char *url, int flags);
    int     (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);
    int     (*url_accept)(URLContext *s, URLContext **c);
    int     (*url_handshake)(URLContext *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;

4、打开url

调用avio.c的ffurl_open_whitelist()打开url,内部调用ffurl_alloc和ffurl_connect,代码如下:

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)
{
    int ret = ffurl_alloc(puc, filename, flags, int_cb);
    if (ret < 0)
        return ret;
    ......
    ret = ffurl_connect(*puc, options);
    if (!ret)
        return 0;
fail:
    ffurl_closep(puc);
    return ret;
}

其中ffurl_alloc主要实现2个功能:查找协议和分配协议,代码如下:

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

 url_find_protocol过程主要是遍历协议数组,根据scheme进行匹配:

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");
    else
        av_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);
    return NULL;
}

ffurl_connect主要是判断用url_open2还是url_open来打开:

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);

    ......
    return 0;
}

5、打开fifo

调用aviobuf.c的fifo_fdopen方法打开fifo,主要是分配buffer缓冲区、分配AVIOContext:

int ffio_fdopen(AVIOContext **s, URLContext *h)
{
    // malloc buffer
    buffer = av_malloc(buffer_size);
    if (!buffer)
        return AVERROR(ENOMEM);
    // malloc context
    *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);

fail:
    av_freep(&buffer);
    return AVERROR(ENOMEM);
}

6、protocol协议列表

FFmpeg支持的protocol包括:file、http、tcp、udp、hls、rtmp等,在protocols.c中声明为全局常量,列表如下(有删减):

extern const URLProtocol ff_async_protocol;
extern const URLProtocol ff_concat_protocol;
extern const URLProtocol ff_crypto_protocol;
extern const URLProtocol ff_file_protocol;
extern const URLProtocol ff_ftp_protocol;
extern const URLProtocol ff_hls_protocol;
extern const URLProtocol ff_http_protocol;
extern const URLProtocol ff_httpproxy_protocol;
extern const URLProtocol ff_https_protocol;
extern const URLProtocol ff_pipe_protocol;
extern const URLProtocol ff_rtmp_protocol;
extern const URLProtocol ff_rtp_protocol;
extern const URLProtocol ff_tcp_protocol;
extern const URLProtocol ff_tls_protocol;
extern const URLProtocol ff_udp_protocol;

file协议默认白名单有file/crypto/data,位于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"
};

http协议默认白名单有http、https、tls、rtp、tcp、udp、crypto、httpproxy、data,位于libavformat/http.c,定义如下:

const URLProtocol ff_http_protocol = {
    .name                = "http",
    .url_open2           = http_open,
    .url_accept          = http_accept,
    .url_handshake       = http_handshake,
    .url_read            = http_read,
    .url_write           = http_write,
    .url_seek            = http_seek,
    .url_close           = http_close,
    .url_get_file_handle = http_get_file_handle,
    .url_get_short_seek  = http_get_short_seek,
    .url_shutdown        = http_shutdown,
    .priv_data_size      = sizeof(HTTPContext),
    .priv_data_class     = &http_context_class,
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
    .default_whitelist   = "http,https,tls,rtp,tcp,udp,crypto,httpproxy,data"
};

7、makefile配置

libavformat的makefile脚本,支持enable/disable某种协议,如果开关设为true最终xxx.o文件被链接到动态库或者静态库。具体如下:

# protocols I/O
OBJS-$(CONFIG_ASYNC_PROTOCOL)            += async.o
OBJS-$(CONFIG_CONCAT_PROTOCOL)           += concat.o
OBJS-$(CONFIG_CRYPTO_PROTOCOL)           += crypto.o
OBJS-$(CONFIG_FILE_PROTOCOL)             += file.o
OBJS-$(CONFIG_FTP_PROTOCOL)              += ftp.o urldecode.o
OBJS-$(CONFIG_HLS_PROTOCOL)              += hlsproto.o
OBJS-$(CONFIG_HTTP_PROTOCOL)             += http.o httpauth.o urldecode.o
OBJS-$(CONFIG_HTTPPROXY_PROTOCOL)        += http.o httpauth.o urldecode.o
OBJS-$(CONFIG_HTTPS_PROTOCOL)            += http.o httpauth.o urldecode.o
OBJS-$(CONFIG_PIPE_PROTOCOL)             += file.o
OBJS-$(CONFIG_RTMP_PROTOCOL)             += rtmpproto.o rtmpdigest.o rtmppkt.o
OBJS-$(CONFIG_RTP_PROTOCOL)              += rtpproto.o ip.o
OBJS-$(CONFIG_TCP_PROTOCOL)              += tcp.o
TLS-OBJS-$(CONFIG_OPENSSL)               += tls_openssl.o
OBJS-$(CONFIG_TLS_PROTOCOL)              += tls.o $(TLS-OBJS-yes)
OBJS-$(CONFIG_UDP_PROTOCOL)              += udp.o ip.o
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐