FFmpeg源码分析:avformat_open_input

当我们想要播放一个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 };
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351