美文网首页
FFmpeg源码分析:avformat_open_input

FFmpeg源码分析:avformat_open_input

作者: Sivin | 来源:发表于2019-12-12 23:24 被阅读0次

当我们想要播放一个http链接的视频地址时, 首先需要做的是用这个地址进行拉流,下面我们就来分析一下在ffmpeg中这个过程是如何实现的。

第一个函数avformat_open_input这个函数的实现源码在libavformat/utils.c文件中。

int avformat_open_input(AVFormatContext **ps, const char *filename,
                        AVInputFormat *fmt, AVDictionary **options){
    //1.创建并初始化avformatContext对象
    s = avformat_alloc_context();
    //2.打开流地址
    init_input(s,filename,...);
    ...
}

avformat_alloc_context 函数分析

AVFormatContext *avformat_alloc_context(void){
    //分配AVFormatContext对象
    AVFormatContext *ic;
    ic = av_malloc(sizeof(AVFormatContext));
    if (!ic) return ic;
    
    //创建完ic对象后,首先需要对ic对象中的一些成员进行默认初始化
    avformat_get_context_defaults(ic);
    //internal这个成员是ffmpeg内部用于处理拉流解复用缓冲的
    //不能被外界调用者访问,因此这里需要内部将这个变量初始化完成
    ic->internal = av_mallocz(sizeof(*ic->internal));
    if (!ic->internal) {
        avformat_free_context(ic);
        return NULL;
    }
    
    //offset表示在时间基为单位下时间戳的映射,这里初始化成未定义类型
    ic->internal->offset = AV_NOPTS_VALUE;
    //raw_packet_buffer可用的剩余大小,以字节为单位
    ic->internal->raw_packet_buffer_remaining_size =RAW_PACKET_BUFFER_SIZE;
    //一个媒体文件可能有多个流例如音频流和视频流,这个变量表示最短流结束的时间戳
    ic->internal->shortest_end = AV_NOPTS_VALUE;
    return ic;
}

对于上面的函数,我们需要关心的是avformat_get_context_defaults做了什么事,以及这个函数的整体大致做了什么事,当我们清楚主体的架构后,在对每一个模块的细枝末节进行仔细的分析就快很多了。

avformat_get_context_default函数分析

static void avformat_get_context_defaults(AVFormatContext *s)
{
    //清零
    memset(s, 0, sizeof(AVFormatContext));
    //为这个对象赋值一个class对象
    s->av_class = &av_format_context_class;
    //赋值两个函数指针用于io操作
    s->io_open  = io_open_default;
    s->io_close = io_close_default;
    //设置部分默认参数
    av_opt_set_defaults(s);
}

在AVFormatContext对象里有两个很重要的函数,io_open,io_close,这两个函数专门负责对不同媒体协议进行打开读写。接下来我们就来分析一下ffmpeg中io_open_default函数的内部是如何实现的?

static int io_open_default(AVFormatContext *s, AVIOContext **ioCtx,
                           const char *url, int flags, AVDictionary **options){
    
return ffio_open_whitelist(ioCtx, url, flags, 
                           &s->interrupt_callback, 
                           options, 
                           s->protocol_whitelist,
                           s->protocol_blacklist);
}

这个函数只是做了一层代理转发给了ffio_open_whitelist函数。真个函数需要几个很重要的参数,第一个AVIOContext对象,第二个url不用多说,interrupt_callback中断回调。

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;
    
    //1.第一步
    err = ffurl_open_whitelist(&h, filename, 
                               flags, int_cb, 
                               options, whitelist, 
                               blacklist, NULL);
    if (err < 0)
        return err;
    
    //1.第二步
    err = ffio_fdopen(s, h);
    
    if (err < 0) {
        ffurl_close(h);
        return err;
    }
    return 0;
}

通过代码我们能够很清楚的看到ffio_open_whitelist函数内部是分两步进行的,第一步调用ffurl_open_whitelist函数如果没有错误,则调用第二步ffio_fdopen函数。

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;//没有找协议,检测是否是https协议如果是,则友好提示一下
    if (av_strstart(filename, "https:", NULL))
        av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "
                                     "openssl, gnutls "
                                     "or securetransport enabled.\n");
    return AVERROR_PROTOCOL_NOT_FOUND;
}

该函数同业也是分两步进行,第一步查找与该输入地址相关练的url协议,如果找到了相关的协议则赋值给URLProtocol对象,然后调用url_alloc_for_protocol进行下一步操作,如果没有找到相关的协议,则直接返回,友好的是在返回前先检测一下是否是https协议,如果是则提示一下用户,这是因为,如果ffmpeg没有关联openssl库,则FFmpeg是不支持https协议的,也就是上一步会返回null对象。

#define URL_SCHEME_CHARS                        \
    "abcdefghijklmnopqrstuvwxyz"                \
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"                \
    "0123456789+-."

static const struct URLProtocol *url_find_protocol(const char *filename)
{
    //这是是一个协议数组对象,每一个协议都是一个指针对象
    const URLProtocol **protocols;
    char proto_str[128], proto_nested[128], *ptr;
    //获取协议头的长度,例如http://livelive.hkstv...
    //返回的长度就是4。
    size_t proto_len = strspn(filename, URL_SCHEME_CHARS);
    int i;
    //这里是一个不常见协议的检测,我们平时一般不会遇到一般我们都是`协议头:`这种形式
    //TODO:文件协议是否走这里
    if (filename[proto_len] != ':' &&
        (strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) ||
        is_dos_path(filename))
        strcpy(proto_str, "file");
    else//一般情况下,代码都会走这里,主要功能是将文件协议头复制到proto_str中
        av_strlcpy(proto_str, filename,
                   FFMIN(proto_len + 1, sizeof(proto_str)));
    
    //将协议头复制一份,用于下文的嵌套协议检测
    av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
    //检测协议头中是否有‘+’号,如果有则将加号的位置用‘\0’替代
    //这样就将后面的协议去掉了
    //这里是为了防止出现类似于这样的协议http+tls:\\这种协议
    //如果这种协议出现,我们是无法在后面匹配到
    if ((ptr = strchr(proto_nested, '+')))
        *ptr = '\0';
    //获取ffmpeg支持的全部协议数组
    protocols = ffurl_get_protocols(NULL, NULL);
    if (!protocols)
        return NULL;
    //遍历数组
    for (i = 0; protocols[i]; i++) {
            const URLProtocol *up = protocols[i];
        //对比协议字符串
        //如果匹配到,则释放协议数组,返回当前协议的URLProtocol对象
        if (!strcmp(proto_str, up->name)) {
            av_freep(&protocols);
            return up;
        }
        //proto_str没有匹配到,则可能是嵌套协议,对比除去`+`后的协议头
        if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&
            !strcmp(proto_nested, up->name)) {
            av_freep(&protocols);
            return up;
        }
    }
    av_freep(&protocols);
    
    return NULL;
}

经过上面分析,该函数就是查找ffmpeg中url所匹配的协议,如果找到,则返回与该协议相关的URLProtocol对象,如果没找到则返回null



#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))

const URLProtocol **ffurl_get_protocols(const char *whitelist,
                                        const char *blacklist){
    const URLProtocol **ret;
    int i, ret_idx = 0;
    //分配协议数组空间
    ret = av_mallocz_array(FF_ARRAY_ELEMS(url_protocols), sizeof(*ret));
    if (!ret)
        return NULL;
    
    for (i = 0; url_protocols[i]; i++) {
        const URLProtocol *up = url_protocols[i];

        if (whitelist && *whitelist && !av_match_name(up->name, whitelist))
            continue;
        if (blacklist && *blacklist && av_match_name(up->name, blacklist))
            continue;

        ret[ret_idx++] = up;
    }

    return ret;
}
//protocol_list.c 这个文件是由configure生成的
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_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,
    &ff_rtmpt_protocol,
    &ff_tee_protocol,
    &ff_tcp_protocol,
    &ff_udp_protocol,
    &ff_udplite_protocol,
    NULL };

相关文章

网友评论

      本文标题:FFmpeg源码分析:avformat_open_input

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