故事的背景是这样的:
在整个视频云的流程中(对于冷流整个流程是比较多的),其中有一个环节是转拉。转拉就是从源站拉流,然后推流到目的服务器上的过程。转拉的技术方案是有多种的,此处我们选择的是用ffmpeg来进行转拉。该环节在优化前的耗时在1.7s以上,经过优化后,目前大概耗时在600 ms左右。值得说明的是,此处的耗时是开始启动ffmpeg到最后和目的站建立连接的过程。
过程分析:
既然是要进行时间的优化,那么首先肯定需要知道ffmpeg中每个调用环节的耗时。首先我们分析下ffmpeg源码。需要说明的是下图的流程只是ffmpeg.c源码中的一小部分,且ffmpeg的版本为3.4.3. 不同的版本可能存在细微的差别。此外,我们主要的优化是从和源站建链到和目的站建连的过程。所以下图的分析虽然不够全,但是对于本次的分析已经足够了。关于更多的ffmpeg源码分析,请参考本文末尾所贴的参考文献链接。
从main函数进入,到最后和目的站建立连接,主要是两块函数的调用,av***_register_all 和 ffmpeg_parse_option. 之所以强调了register函数,是因为ffmpeg编译的时候,decoder,encoder以及filter等等都是可选的。不同的编译参数编译出来的ffmpeg大小是不同。 初始的时候,我们以为减小ffmpeg编出来的大小,会有助于加快转拉的速度,事实上并没有。register函数做的事情挺简单的,主要是往链表后面追加一点点的数据,执行过程是非常快的,几乎可以忽略。
那么我们的重点自然就到了ffmpeg_parse_option这个函数中了。这个函数的作用就是解析我们的命令行参数。然后调用open_file函数,分别打开输入文件和输出文件。通过逐步打日志,我们发现,整个耗时主要就集中在两个函数:avformat_open_input和avformat_find_stream_info。其中avformat_open_input 函数的调用相对简单,主要是调动avio_open2与源站建立连接耗时比较严重。通过大量的转拉案例,我们发现,正常情况下,这个建链的过程几十毫秒就能完成。但是建链这个过程主要是网络的相关,在ffmpeg层面可改动优化的空间并不大。所以这里也没过多进行优化,实际上如果想优化,也是有一定的优化空间的。打日志发现,init_input中除了avio_open2耗时之外,av_prob_input_buffer2偶尔也会比较耗时,通过这个函数名字我们也可以知道,既然是探测,当然是可以缩短一些探测的时间的。这里不做深入的讨论,因为大量例子发现,多数时候,avformat_open_input建立连接都是很快的。
事实上最耗时且可优化的是avfomat_find_stream_info函数。
查看源码可以发现,在avformat_find_stream_info中有个无限循环,该函数的调用耗时也主要是在这个循环里面。这个函数里面会调用read_frame_internal 与 try_decode_frame 进行一些分析和探测,当达到某种条件的时候,就会break,跳出循环。其实我们可以逐个分析循环中break的地方, 如果循环能够尽早break掉,那自然就会减少整个循环的调用时间了。
1) ff_check_interrupt 导致的break。 这个地方显然从代码层面上,没啥可进行改动的。
2) 如下图,有header信息,并且所有的流都解析到了退出。
3)读取数据的大小达到上限,需要说明的是probsize这个值是可配的。命令行参数中指定 -probsize 就行,或者在options_table中avformat_options[]数组中直接设置默认值也是可以的。
4)分析的时间音视频帧时间戳达到了上限,这里的时间上限值也是可设置的。可以命令行指定 -analyzeduration参数,或者options_table.h中设置默认值。
对于第三四处的break, 其实意思是相同的。但是简单的将上限值缩小是不可取的。主要是两方面的原因:1)测试发现,循环并不是因为达到了上限值才退出的;2)通过缩小上限值退出循环,可能导致本来是音视频两条流的,最后推出去的流只有一路。这个情况在某个客户的转拉的过程中就出现了。该客户源站吐流前面几秒钟都是音频数据,并且header信息里面也没有视频帧,当达到阈值退出时。可能只分析到了音频帧,以至于后面即使来了是视频帧,ffmpeg也会将其丢弃,最后导致推到目的站的流是纯音频的。
到此也可以猜到了,我们的改动主要在第二处break处了了。图2中,有个变量,fps_analyze_frame_count,我们可以看到默认值为20,并且这个for循环里面还有注释,"检查一个编解码器时候还需要被处理",这个变量即使从变量名也能猜到他是要干嘛的——分析帧的数量。这个默认值是比较大的,特别是对于我们的直播转拉环节。所以在此我们适当的减小了这个值。在实际项目中,在确定了有两条流的情况下,我们将音频帧的分析帧数设置为10,视频帧设置为2. 当然这个值的选择参考意义可能不是特别大。用户可以根据不同的需求,自己设置,然后进行测试。
至此,本次分析就差不都结束了。下面展示下实验的结果。
测试结果
测试结果记录了优化前后,每次转拉的平均耗时。因为刚开始是在一台正式环境上测试的,所以数据量有限,另外由于我们的重点是关注优化后的数据,所以优化前相较于优化后的转拉次数是比较少的。
优化前的数据
图6是优化前的转拉耗时,总共有记录590条,此处只截图了其中50条记录。图中总共有4列数据时间,单位都是ms。第一列是调用avformat_open_input的耗时,第二列是调用avformat_find_stream_info的耗时,第三列是从和源站建立连接到和目的站建立连接的耗时,即两个avio_open2之间的调用间隔,第四列是从main函数开始到调用与目的站建立连接的avio_open2函数的耗时。可以看到大部分总体耗时都是在几百毫秒内,偶尔会有几个耗时比较多的。
通过对着590条转拉记录统计平均值,我们发现大概在1700 ms。
优化后的数据
同样我们也贴上优化后的50次转拉耗时,第一列是流id,可以不管。后面的4列和优化前的4列一一对应。
这些数据是目前一台线上机器上的数据,因为本次优化已经上线了几天了,所以数据相对来说多点,有24000 条数据,平均耗时612ms左右。我登录了几台线上的机器,统计发现差不多都是在 600 ms左右。
整片文章到此就算完结了
参考文献
https://blog.csdn.net/leixiaohua1020/article/details/39760711