热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

FFmpeg从seek闪退问题分析ts时长duration计算方法

背景HTTP点播seek闪退分析FFmpeg解析tsduration流程分析解决思路背景FFmpeg是非常优秀的开源框架,在使用其进行二次开发及适配的过程中


    • 背景
    • HTTP点播seek闪退分析
    • FFmpeg解析ts duration流程分析
    • 解决思路


背景

FFmpeg是非常优秀的开源框架,在使用其进行二次开发及适配的过程中,难免会遇到各种各样的问题。
这次要分析的问题是基于FFmpeg的播放器在HTTP点播seek的时候,出现闪退,从而引申出FFmpeg中ts流duration计算方法的分析。


HTTP点播seek闪退分析

从日志看,发现seek的位置是10分钟左右,小于duration,但是从HTTP的请求来看,请求的大小已经和整个片源大小差不多了,如下:
在这里插入图片描述
最开始我怀疑是seek的模式不对(FFmpeg有几种seek模式)导致HTTP请求的点不对,跟进后并无发现明显异常。
于是将整个ts流dump下来。发现其实整个片源才有不到5分钟,这下问题很明显了:FFmpeg计算的ts时长不对,seek时间点其实已经超过时长了,所以HTTP请求每次都请求到文件末端,一请求就end of file了,看起来效果就是闪退。
所以引申出文章的主题,FFmpeg是如何解析ts的duration的。
在这里插入图片描述


FFmpeg解析ts duration流程分析

1、首先ts流并不像MP4这种,在头部信息里就已经携带duration了,ts的PSI/SI信息并无携带duration。一般来说,ts的时长是由PTS计算得到,当然也可以计算比特率,但对于VBR(动态比特率)的片源,用比特率来计算其实得到的时长并不可靠。
2、FFmpeg中对于ts的时长计算确实也是基于PTS的。
3、分析如下:
在avformat_find_stream_info阶段过来,我们就可以知道片源的duration,所以我们先从avformat_find_stream_info里分析。
可以看到,计算时长的函数为estimate_timings

avformat_find_stream_info:if(ic->probesize)estimate_timings(ic, old_offset);

跟进estimate_timings,可以看到对于ts流来说,计算方式是estimate_timings_from_pts(其他不同格式还有不同的方式,不详细分析)。

static void estimate_timings(AVFormatContext *ic, int64_t old_offset)
{
......省略
//对于ts流来说,计算方式是estimate_timings_from_ptsif ((!strcmp(ic->iformat->name, "mpeg") ||!strcmp(ic->iformat->name, "mpegts")) &&file_size && ic->pb->seekable) {/* get accurate estimate from the PTSes */estimate_timings_from_pts(ic, old_offset);ic->duration_estimation_method = AVFMT_DURATION_FROM_PTS;} ......省略

跟进estimate_timings_from_pts,代码比较简单,如下:
1&#xff09;获取文件大小&#xff0c;seek到倒数DURATION_MAX_READ_SIZE<

2&#xff09;读取第一个ts&#xff0c;根据起始PTS或者DTS。计算出当前ts的PTS差距&#xff0c;则为duration&#xff0c;并将此次计算得duration记录下来&#xff0c;为last_duration。

3&#xff09;循环读取ts&#xff0c;如果此次计算的duration更大&#xff0c;且和last_duration间隔小于60LL*st->time_base.den / st->time_base.num&#xff0c;则更新duration。这是为了防止有些PTS跳变的情况&#xff0c;不更新duration&#xff0c;但仍然会记录此次的计算得值为last_duration。如果后续新的ts的计算得到的duration和last_duration比较是连续的&#xff0c;则可以认为PTS跳变后又连续了&#xff0c;认为计算得到的duration是正确的。


ts流的st->time_base.den /st->time_base.num &#61; 1/90000。因为mpeg的pts、dts都是以90kHz来采样的&#xff0c;所以采样间隔为1/90000秒。


4&#xff09;如果在DURATION_MAX_READ_SIZE内已经解析到时长&#xff0c;完成&#xff0c;否则偏移更大数据量来解析PTS&#xff0c;计算duration。新数据量为DURATION_MAX_READ_SIZE<

5&#xff09;重新seek回原本的偏移位置。

/* only usable for MPEG-PS streams */
static void estimate_timings_from_pts(AVFormatContext *ic, int64_t old_offset)
{AVPacket pkt1, *pkt &#61; &pkt1;AVStream *st;int read_size, i, ret;int64_t end_time;int64_t filesize, offset, duration;int retry&#61;0;/* flush packet queue */flush_packet_queue(ic);for (i&#61;0; inb_streams; i&#43;&#43;) {st &#61; ic->streams[i];if (st->start_time &#61;&#61; AV_NOPTS_VALUE && st->first_dts &#61;&#61; AV_NOPTS_VALUE)av_log(st->codec, AV_LOG_WARNING, "start time is not set in estimate_timings_from_pts\n");if (st->parser) {av_parser_close(st->parser);st->parser&#61; NULL;}}/* estimate the end time (duration) *//* XXX: may need to support wrapping */filesize &#61; ic->pb ? avio_size(ic->pb) : 0; //获取文件大小end_time &#61; AV_NOPTS_VALUE;do{offset &#61; filesize - (DURATION_MAX_READ_SIZE<pb, offset, SEEK_SET); //seek到倒数位置read_size &#61; 0;for(;;) {if (read_size >&#61; DURATION_MAX_READ_SIZE<<(FFMAX(retry-1,0)))break;do {ret &#61; ff_read_packet(ic, pkt); //读取ts} while(ret &#61;&#61; AVERROR(EAGAIN));if (ret !&#61; 0)break;read_size &#43;&#61; pkt->size;st &#61; ic->streams[pkt->stream_index];if (pkt->pts !&#61; AV_NOPTS_VALUE &&(st->start_time !&#61; AV_NOPTS_VALUE ||st->first_dts !&#61; AV_NOPTS_VALUE)) {duration &#61; end_time &#61; pkt->pts;if (st->start_time !&#61; AV_NOPTS_VALUE)duration -&#61; st->start_time; //如果能解析出PTS&#xff0c;起始位置以PTS计算时长elseduration -&#61; st->first_dts; //否则以起始位置的DTS来计算时长if (duration > 0) {if (st->duration &#61;&#61; AV_NOPTS_VALUE || st->info->last_duration<&#61;0 ||//防止PTS跳变情况(st->duration info->last_duration) <60LL*st->time_base.den / st->time_base.num))st->duration &#61; duration;//更新时长st->info->last_duration &#61; duration;}}av_free_packet(pkt);}}while( end_time&#61;&#61;AV_NOPTS_VALUE //没获取有效时长&& filesize > (DURATION_MAX_READ_SIZE<pb, old_offset, SEEK_SET); //重新seek回原本的位置for (i&#61;0; inb_streams; i&#43;&#43;) {st&#61; ic->streams[i];st->cur_dts&#61; st->first_dts;st->last_IP_pts &#61; AV_NOPTS_VALUE;}
}

estimate_timings_from_pts获取到duration后&#xff0c;update_stream_timings(ic);将其更新到AVFormatContext中。

分析完FFmpeg计算ts时长的流程后&#xff0c;我们重新回头来看下这个片源为什么会有问题&#xff0c;以及如何解决。


解决思路

用ts分析工具&#xff0c;可以看到初始PTS和中间有明显的PTS反转情况。

初始PTS&#xff1a;
在这里插入图片描述PTS反转&#xff1a;在这里插入图片描述
所以按照FFmpeg的计算流程&#xff0c;得到的duration才会特别大。
为兼容此流&#xff0c;做了以下修改&#xff1a;
1&#xff09;计算得到PTS反转处为倒数1210340字节处&#xff0c;所以从倒数1.5MB处开始计算PTS。
2&#xff09;处理PTS跳变时&#xff0c;如果跳变过大&#xff0c;像这种反转的情况&#xff0c;就直接不记录到last_duration。

if (duration > 0) {if (st->duration &#61;&#61; AV_NOPTS_VALUE || st->info->last_duration<&#61;0 ||//防止PTS跳变情况(st->duration info->last_duration) <60LL*st->time_base.den / st->time_base.num))st->duration &#61; duration;//更新时长//add for PTS 跳变过大的异常情况&#xff0c;直接忽略if (FFABS(duration - st->info->last_duration) > 600LL * st->time_base.den / st->time_base.num && st->info->last_duration > 0) { st->duration &#61; st->info->last_duration;continue;}//add endst->info->last_duration &#61; duration;}

测试可以获取到正常PTS&#xff0c;当然&#xff0c;这些修改并不是通用&#xff0c;只是我针对该异常ts流测试用&#xff0c;例如如果反转后的流还很长呢&#xff0c;这样的方法计算出来则duration会小一截。
最实际的方法&#xff0c;还是在流的制作时&#xff0c;正确打包PTS。


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • HDFS2.x新特性
    一、集群间数据拷贝scp实现两个远程主机之间的文件复制scp-rhello.txtroothadoop103:useratguiguhello.txt推pushscp-rr ... [详细]
author-avatar
2502910的
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有