Avformat_open_input函数的分析 结合HTTP协议
前段时间在做直播的优化,主要是优化首屏时间,因为直播播放器大部分都会采用ffmpeg来处理,所以就会用到avformat_open_input这个函数,这也是首屏耗时比较多的一个地方,这里我主要跟踪一下http的请求以及rtmp的请求,源码都是开源的,这里主要是记录下来以备自己查询,本篇文章主要是是以ijkplayer源码为基础分析的。
avformat_open_input
这个函数的作用是打开文件的链接,如果是网络连接,还会发起网络请求,并一直等待网络数据的返回,然后读取视频流的数据。接下来进行详细的分析。
1.接口参数的解析
首先看函数的声明
int avformat_open_input(AVFormatContext **ps, const char *filename,AVInputFormat *fmt, AVDictionary **options)
AVFormatContext **ps
该函数的主要作用是填充好
AVFormatContext **ps
这个结构体。AVFormatContext
这个结构体里面的参数比较多,这里就不一一列举了,详细可以参考avformat.h
这个头文件,具体用到啥到时再详细说明。
const char *filename
文件的全部路径,比如
http://flv-meipai.8686c.com/meipai-live/58e03ffd20a05d7a1410d08c.flv
AVInputFormat *fmt
AVInputFormat
的结构体也比较复杂,主要是封装媒体数据封装类型的结构体,比如flv, mpegts, mp4等。在这里可以传入空,如果为空,后面就会从网络数据中读出。当然如果我们知道文件的类型,先用av_find_input_format("flv")
初始化出对应的结构体,这里我们用的是flv,先初始化好这个结构体,对于直播来说,会比较节约时间。AVDictionary **options
struct AVDictionary {int count;AVDictionaryEntry *elems; }; typedef struct AVDictionaryEntry {char *key;char *value; } AVDictionaryEntry;
字典类型的可选参数,可以向ffmpeg中传入指定的参数的值。比如我们这里传入了
av_dict_set_int(&ffp->format_opts, "fpsprobesize", 0, 0);
表示fpsprobesize
对应的参数值为0,当然还可以传入更多值,具体可以参考options_table.h
这个头文件。
2.函数的关键函数实现
avformat_open_input
的具体实现在libavformat/utils.c
文件。
init_input函数
第一次调用avformat_open_input
函数时,传入的ps
是属于初始化状态,很多部分可以忽略,直接跳到以下部分
if ((ret = init_input(s, filename, &tmp)) < 0)goto fail;
init_input
函数的声明如下
/* Open input file and probe the format if necessary. */
static int init_input(AVFormatContext *s, const char *filename,AVDictionary **options)
函数的主要功能如注释一样,打开一个文件链接,并尽可能解析出该文件的格式。它里面关键的调用是
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)return ret;
io_open
函数是一个回调函数。一般情况下是采用的默认函数io_open_default
,具体的赋值是在libavformat/option.c
文件中,调用过程如下:
avformat_alloc_context->avformat_get_context_defaults->(s->io_open = io_open_default;)
其中io_open_default的函数实现
static int io_open_default(AVFormatContext *s, AVIOContext **pb,const char *url, int flags, AVDictionary **options)
{printf("io_open_default called\n");
#if FF_API_OLD_OPEN_CALLBACKS
FF_DISABLE_DEPRECATION_WARNINGSif (s->open_cb)return s->open_cb(s, pb, url, flags, &s->interrupt_callback, options);
FF_ENABLE_DEPRECATION_WARNINGS
#endifreturn ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
}
这里一般都是没有定义FF_API_OLD_OPEN_CALLBACKS
宏的,所以实际是调用ffio_open_whitelist
函数。
ffio_open_whitelist函数
继续跟进函数的定义和调用发现ffio_open_whitelist
的实现是在libavformat/aviobuf.c
文件中。
err = ffurl_open_whitelist(&h, filename, flags, int_cb, options,
whitelist, blacklist, NULL);
ffurl_open_whitelist
函数的功能主要是打开文件链接,并填充一个URLContext *h
结构体。该结构体的声明是在url.h
文件里面,源码里面有,不过我这里也提一下
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;
这个结构体很重要,里面prot
指向了具体URLProtocol
结构体,该结构体里面包含有打开该协议的url的回调函数,如http,tcp,都有对应的open函数来处理。ok,现在我们深入到ffurl_open_whitelist
函数实现中。
ffurl_open_whitelist函数
该函数的实现是在avio.c
里面,它会先调用下面的函数初始化相应的结构体
int ret = ffurl_alloc(puc, filename, flags, int_cb);
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;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_find_protocol
函数根据url的name,找到对应的URLProtocol
结构。这个函数一般会调用多次,http和tcp协议解析的时候都会调用,这里传入的是http://flv-meipai.8686c.com/meipai-live/58e1c0e9d425e1464018167c.flv
,所以解析出来的URLProtocol
里面应该是HTTP协议的结构体。具体再来看下什么如何初始化的。
protocols = ffurl_get_protocols(NULL, NULL);
首先通过ffurl_get_protocols
获取到已知所有的支持协议的数组。下面是当前ffmpeg支持的协议列表,在protocol_list.c
文件中定义。
static const URLProtocol *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_rtmp_protocol,&ff_rtmpt_protocol,&ff_tee_protocol,&ff_tcp_protocol,&ff_udp_protocol,&ff_udplite_protocol,NULL };
其中每个protocol的具体定义在每个具体的c文件中,可以搜索。我们再回到url_find_protocol
函数中,
size_t proto_len = strspn(filename, URL_SCHEME_CHARS);//先从文件名中获取协议名的长度
有了长度,就可以获取协议的名字,这里获取到的就是http
协议名。获取到协议名以后,就可以从protocols
中获取到对应的http protocol了,那么这里的url_find_protocol
函数返回的就是ff_http_protocol
结构体了,它的结构体定义在libavformat/http.c
文件中。
这时再回到ffurl_alloc
函数中,找到协议URLProtocol *p
后,然后会调用:
p = url_find_protocol(filename);
if (p)return url_alloc_for_protocol(puc, p, filename, flags, int_cb);
接下来进入到url_alloc_for_protocol
函数中。它的主要作用是根据URLProtocol
参数初始化URLContext
结构体,具体有什么作用用到之后再细说。接下来就是
ret = ffurl_connect(*puc, options);
ffurl_connect函数
该函数中唯一一个比较重要的函数就是
err = uc->prot->url_open2 ? uc->prot->url_open2(uc,uc->filename,uc->flags,options) :uc->prot->url_open(uc, uc->filename, uc->flags);
首先判断是否prot->url_open2
函数指针是否有赋值。我们从http.c
中ff_http_protocol
结构体的定义中可以发现
.url_open2 = http_open,
所以在ffurl_connect
函数中这里实际调用的是http_open
函数。那么接下来就进入到我们的关键函数了。
http_open函数
http协议的基本实现都是在http.c
文件中实现的。首先我们看第5行代码:
s->app_ctx = (AVApplicationContext *)(intptr_t)s->app_ctx_intptr;
它的功能是赋值AVApplicationContext
类型的指针,它主要是用于发送消息给应用层的。但问题是app_ctx_intptr
具体是在哪里赋值的呢。通过查找相关代码发现
av_dict_set_int(options, "ijkapplication", (int64_t)(intptr_t)s->app_ctx, 0);
它是通过从options
里面查找"ijkapplication"相关的int
类型的值作为指针赋值。
再从ijkplayer
的上层源代码中可以找到
ffp_set_option_int(ffp, FFP_OPT_CATEGORY_FORMAT, "ijkapplication", (int64_t)(intptr_t)ffp->app_ctx);
可以看出app_ctx
是在上层创建的。
av_application_open(&ffp->app_ctx, ffp);
该函数就是创建一个AVApplicationContext
类型的结构体并赋值给ffp->app_ctx
。
好了,回到http_open
函数,接下来是http_open_cnx
函数,它主要也是调用http_open_cnx_internal
函数。
http_open_cnx_internal函数
首先调用的是:
av_url_split(proto, sizeof(proto), auth, sizeof(auth),hostname, sizeof(hostname), &port,path1, sizeof(path1), s->location);
ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL);
它们的功能是根据传入的s->location
其实就是视频的url,从url里面提取出hostname
,port
,以及path
。
同时ff_url_join
解析出host地址,如果不是ipv6,那么hoststr
就是hostname
。然后还有
use_proxy = !ff_http_match_no_proxy(getenv("no_proxy"), hostname) &&proxy_path && av_strstart(proxy_path, "http://", NULL);
由于我们的http链接,基本都没用到proxy
,所以use_proxy
用不上。接下来是
ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL);
ff_url_join
前面用到的时候是解析出hostname
,但在这里,由于传入了lower_proto
(它表示http 协议的下一层协议,一般都是tcp
,所以该值初始化的时候就是tcp
),所以buf的值是有lower_proto
拼凑起来的tcp链接tcp://flv-meipai.8686c.com:80
,就是tcp+域名。接下来就是
if (!s->hd) {av_dict_set_int(options, "ijkapplication", (int64_t)(intptr_t)s->app_ctx, 0);err = ffurl_open_whitelist(&s->hd, buf, AVIO_FLAG_READ_WRITE,&h->interrupt_callback, options,h->protocol_whitelist, h->protocol_blacklist, h);if (err < 0)return err;
}
首先判断s->hd
是否存在。默认情况下,是为NULL
值,所以调用ffurl_open_whitelist
开始打开tcp://flv-meipai.8686c.com:80
,现在回到ffurl_open_whitelist
函数了。这时通过url_find_protocol
找到的就是tcp
类型的URLProtocol
,那么在ffurl_connect
调用时调用的就是tcp.c
里面的tcp_open
函数,那么tcp的握手连接就在这个函数里面解析了。首先
av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname),&port, path, sizeof(path), uri);
先根据uri解析出协议名以及hostname,然后调用以下的
ret = ijk_tcp_getaddrinfo_nonblock(hostname, portstr, &hints, &ai, s->addrinfo_timeout, &h->interrupt_callback, s->addrinfo_one_by_one);
做DNS解析。这个函数是ijkplayer
作者加上去的,标准的ffmpeg 里面并没有。它的功能是利用多线程来解析DNS。但实际上从代码上并没有看到有什么优势,其实还是阻塞等结果解析出来了才返回的,这个地方不是很懂为什么要这么改。
接下来就是创建socket
了。
fd = ff_socket(cur_ai->ai_family, cur_ai->ai_socktype, cur_ai->ai_protocol);
调用ff_listen_connect
函数进行tcp握手。
至此,调用tcp协议的ffurl_open_whitelist
函数就调用完成了,tcp握手连接也建立成功。再回到http_open
函数。继续调用
err = http_connect(h, path, local_path, hoststr, auth, proxyauth, &location_changed);
该函数的功能就是发送http request
并等待http response
了。我们细看下http_connect
函数的实现,在调用ffurl_write
以前的代码都是在填充http request
的头部。
int ffurl_write(URLContext *h, const unsigned char *buf, int size)
{if (!(h->flags & AVIO_FLAG_WRITE))return AVERROR(EIO);/* avoid sending too big packets */if (h->max_packet_size && size > h->max_packet_size)return AVERROR(EIO);return retry_transfer_wrapper(h, (unsigned char *)buf, size, size,(int (*)(struct URLContext *, uint8_t *, int))h->prot->url_write);
}
从ffurl_write
代码中可以看出,它实际调用的是url_write
方法,而该prot的write方法,是http_write
,它又是调用的ffurl_write(s->hd, buf, size);
就是指http协议下一层的协议tcp的tcp_write
方法。tcp_write
方法最终调用就是ret = send(s->fd, buf, size, MSG_NOSIGNAL);
系统的send方法。所以最终都是调用系统实现的Socket
接口。至此,http_connect
方法的发送request的请求就完毕了。剩下就是等待响应了。
/* wait for header */
err = http_read_header(h, new_location);
http_read_header
就是不断的读取网络返回的数据,并解析出来。
至此http_open_cnx_internal
函数也调用完了。回到http_open_cnx
函数。这时如果能正常获取数据,那么s->http_code
的值应该是200,至此,http_open_cnx
,http_open
函数也返回了,流程可以直接返回到ffio_open_whitelist
函数中,ffio_fdopen
函数只是对AVIOContext
结构体根据http request
获取的数据进行一些赋值。那就可以直接返回到init_input
函数了。接下来是
if (s->iformat)return 0;
return av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize);
判断如果s->iformat
没有值,就根据filename
解析出s->iformat
。这也是在前面开头提到的,如果没有加av_find_input_format("flv")
这个代码,那就要重新根据filename
来解析数据了,这个函数比较耗时,需要读取到一定数据后才能解析出来。
至此,init_input
函数解析完毕,虽然还有大量的细节没有解析,后面有机会继续再细讲。
avformat_open_input
函数最耗时,最重要的就是init_input
函数了,后面的都是些其他细节了,这里就不再细讲了。
Avformat_open_input函数的分析 结合HTTP协议相关推荐
- 从学龄前开始解读FFMPEG代码 之 avformat_open_input函数read_probe和read_head
从学龄前开始解读FFMPEG代码 之 avformat_open_input函数 read_probe和read_head 开始学习前想说的话 avi格式视频要如何probe FLV格式视频是如何pr ...
- linux lock函数,Linux lock_kernel()函数的分析。
这只是暂时的记录,以后会把它归类到start_kernel()函数的分析.在分析之前,我先要说说几个概念. 内核抢占:在2.6内核加入了抢占的能力,就是说调度程序有办法在一个内核级的任务正在执行的时候 ...
- DbgPrint 函数流程分析
DbgPrint 函数流程分析 前言 Windows 下编写内核驱动时经常用到 DbgPrint 函数输出一些调试信息,用来辅助调试.当正在用 WinDbg 内核调试时,调试信息会输出到 WinDbg ...
- 增长黑客系列:今天比昨天增长多少?快使用环比函数来分析日志
摘要: 增长黑客系列:今天比昨天增长多少?快使用环比函数来分析日志 在我们平时分析业务时,一个最重要的指标就是,今天比昨天增长多少,本周比上周增长多少:或者同上一个周期相比增长最大的分类是哪个?这些问 ...
- mysql 源码 缓存_MySQL源码:MYSQL存储过程/函数的分析原理及缓存机制
前言:我个人认为,有关MYSQL存储过程/函数在MYSQL中的实现比较粗糙,可扩展性不够好,其实现的耦合性太高,所以主要讲一些它的原理方面的内容,但有可能在某些方面理解不够好或者有些不正确的地方,欢迎 ...
- linux中request_region()函数的分析
linux中request_region()函数的分析 struct resource ioport_resource = { .name = "PCI IO", .start = ...
- caffe中loss函数代码分析--caffe学习(16)
接上篇:caffe中样本的label一定要从序号0开始标注吗?–caffe学习(15) A: 1:数学上来说,损失函数loss值和label从0开始还是从1或者100开始是没有直接联系的,以欧式距离损 ...
- 蓝牙协议分析(2)_协议架构
原文链接:蓝牙协议分析(2)_协议架构 (wowotech.net) 系列索引:蓝牙协议分析(1)_基本概念 目录 1.前言 2.协议层次 2.1 物理层 2.1.1 Physical Channel ...
- Linux时钟管理clk_get函数透彻分析
硬件资源越来越庞大和复杂,内核的另一个挑战就是要便捷的管理这些资源.同时,面对如此之多的平台不同的CPU ,管理机制需要统一适用,这就需要对资源的管理抽象到更加通用的层次.CPU中各个模块都需要时钟驱 ...
最新文章
- Angualr设置自定义管道Pipe(类似Vue的过滤器filters)货币格式化(实现内置管道CurrencyPipe的功能)
- 框架学习之Spring 第五节 SSH整合开发[Spring2.5+Hibernate3.3+Struts2]
- sap 新建事务_SAP操作常用事务代码(大全)
- 有符号数据的符号位扩展
- Java黑皮书课后题第9章:*9.10(代数:二次方程式)为二次方程式设计一个名为QuadraticEquation的类
- Proxy Pattern using C# (转载)
- 形成20位存储单元的物理地址
- php 现代排序_这就是现代PHP的样子
- 没有钱的男人和有钱的男人区别在哪里?
- 招生技巧----如何预约学生和家长 (一)
- git学习(九)跨团队协作-非团队成员参与git项目开发
- excel处置4000行数据卡_Python自动获取当日所有股票数据
- 在机关事业单位工作年满五十岁,工龄三十年提前退休好还是继续工作好?
- h264解码延迟优化_FFMPEG H264/H265 编码延迟问题
- 计算机系统重装微pe步骤
- VS2015安装包-下载
- xml-rpc是个什么东东?
- 【1】学习前言及数据分析的简单介绍jupyter的介绍与安装
- js当前页面打开小窗口 window.open
- OctaPhenyl POSS/八苯基POSS (Ph-POSS)/八苯基笼型倍半硅氧烷(OPPOSS)/八苯基官能化POSS/八苯基硅倍半氧烷/八苯基取代的POSS
热门文章
- 从无栈协程到 C++异步框架
- Python爬虫入门-小试ImagesPipeline爬取pixabay和煎蛋之为什么是‘404’
- 数字密码锁(数字逻辑大作业)
- 项目Demo —— socket下载器
- 一文弄懂原子性、临界区、临界资源
- 第十三届蓝桥杯B组python(试题A:排列字母)
- 2022年乡村医生考试综合试题及答案
- HCIP(华为高级网络安全工程师)(第十六天)(三层架构/VLAN)
- php音视频边下边播,视频个别片段加旁白,就是我边播放视频边录制旁白或声音...
- 380免费云存储_2019年最好的在线照片存储,让你的照片安全存储在云端