【AVD】简述某些视频在线播放时卡顿、本地播放时不卡顿的问题

【AVD】简述某些视频在线播放时卡顿、本地播放时不卡顿的问题曾经在业务中遇到过这样的问题,我们编码出来的视频在Android、iOS端,使用ijkplayer内核的播放器播放时卡顿,甚至无法任意定位播放位置,将导致卡顿无法播放。今天,又有同事遇到类似的问题,而我发现,我只写过一个《用notepad++和Excel协助分析媒体文件包》,而并没有把当时遇到的问题分析记录下来。于是,在此简单说明一下。视频文件结构教科书般的教程、课程中对视频文件结构的描述非常详细,此处不赘述,简单地说,视频文件也是一种文件,是文件,就是一堆二进制数的集合,而且是一个.


Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

曾经在业务中遇到过这样的问题,我们编码出来的视频在 Android、iOS 端,使用 ijkplayer 内核的播放器播放时卡顿,甚至无法任意定位播放位置,将导致卡顿无法播放。今天,又有同事遇到类似的问题,而我发现,我只写过一个《用 notepad++ 和 Excel 协助分析媒体文件包》,而并没有把当时遇到的问题分析记录下来。于是,在此简单说明一下。

视频文件结构

教科书般的教程、课程中对视频文件结构的描述非常详细,此处不赘述,简单地说,视频文件也是一种文件,是文件,就是一堆二进制数的集合,而且是一个一维的二进制数的集合。因此,视频文件中的视频流、音频流,甚至可能包含的字幕流是如何存放的呢?

答案显而易见,就是那么交织地(interleaved)放着的。通过 ffprobe 相关命令行 ffprobe -i test.mp4 -show_packets 可以看到视频文件 test.mp4 中的各个数据包的存放状态。在这里插入图片描述
如上图所示,这是上述命令的一段输出,用[PACKET]...[/PACKET]区隔,它表示了两个包(packet),简单分析一下这些参数,

  • 首先,这两个包都是音频包(codec_type=audio),
  • 然后,stream_index=1 表明这两个音频包处于同一个数据流(流1)中,
  • pts 的值需要根据 stream info 中的 timebase 换算成 pts_t,
  • 而 pts_t 就是我们正常理解的时间,表明了这两个包应该在第124秒左右被渲染展示(presentation)
  • dts 跟 pts 一样,是个 int64_t 的值,需要借助 timebase 转换成 dts_t
  • 同样,dts_t 就是表明了这两个包应该在124秒左右被解码(decode)
  • duration 与 dts pts 一样,是个 int64_t 的值
  • duration_time 是表示这个包所需要展示的时长
  • timebase = pts_t/pts = dts_t/dts = duration_time/duration = 1/44100 这不是巧合
  • convergence 相关的两个参数暂时不清楚啥意思
  • size 表明了这个包的数据占有的字节数
  • pos 表明了这个包在文件中的位置偏移(offset)
  • pos(n) = pos(n-1) + size(n-1) 这也不是巧合
  • flags=K_ 表明这是个关键帧,这在视频流中很有用,音频流每个包都有这个标记

dts_t 和 pos

重点关注上述 packet info 中的 dts_t 和 pos 这两个参数,这两个参数,一个标记了这个包应该在什么时间被解码,另一个标记了这个包在文件中的存储位置。因此,当视频文件被播放时,读取文件也是从头到尾一个包一个包地读入,并且送给对应的音频或视频解码器。

因此,我们可以来看看,那些卡顿的视频的数据包中的 dts_t 和 pos 的关系是怎样的。

我拿同事发给我的一个在 Android 端用 ijkplayer 播放卡顿的视频,根据 《用 notepad++ 和 Excel 协助分析媒体文件包》提到的方法,做了个 pos 随 dts_t 变化的曲线,如下:在这里插入图片描述
如果对 stream_idx 进行筛选可知,上面这张图里下面那条线是音频的线,而上面那条线是视频的线。当然,不是很严谨。严谨地说,它的音频流的 pos 随 dts_t 的变化曲线是这样的:
在这里插入图片描述
对,后面有极个别的包在很大的 pos 上。从数据上看,是这样的:
在这里插入图片描述
它有一个很大的断层。而这个很大的断层中间就夹杂了大量的视频流的包。
在这里插入图片描述

这样的话,会有什么样的影响呢?请看着那个分叉了的散点图,我们来分析,播放器开始读取视频准备播放,时间轴是从左向右推进的,但是播放器读文件却是y轴从下向上推进的。这就会有一个问题:假设播放器是按时间从文件中取数据的,就会发现,随着时间的推进,需要在文件中不断地跳来跳去地取数据,它需要跳到比较大的位置上去取一帧视频数据,然后再在一个比较小的位置上去取音频数据。或者,换个思路看,是这样的问题:播放器是按读入的数据进行播放的,那么它将沿 y 轴自下而上地读取数据包,结果,播放器读入了很多音频数据包,却发现暂时用不到这些音频数据包,那么,它就得缓存下来,继续读下个包。尤其是在上面那条曲线的拐点位置,播放器几乎读取了全部的音频数据包,却发现都不是它想要的视频数据包。

这样一来,本地播放的话,如果内存够大,应该问题不大。但是在线播放的话,当在时间轴上定位到一个中间位置,那么网络服务器将从文件的中间位置处开始返回数据报,对应于文件的一个中间位置上,能取到对应的视频包,却找不到与之对应的音频包(同时刻的数据包在文件的较靠前的位置上),于是,要么播放器就一直等待寻找 dts 合适的音频包,要么就只能舍弃音频包静音播放了。于是就卡顿,甚至不能播放了。

能正常播放的视频文件的包的 pos 与 dts_t 的关系应该是这样的:
在这里插入图片描述
无论是筛选出音频包还是视频包,或者两者并存的情况下,这张散点图都应该是近似一条曲线的。这样,当用户定位到时间轴(x轴)的任意位置,网络服务器同样 seek 到文件的对应中间位置,然后开始源源不断地返回 interleaved 音视频数据包,客户端这边才能流畅正常播放。

关注封装

那么,如何才能保证,转码或者编码或者压缩后的视频文件里的包,能像上图这样,能正常流畅播放呢?

问题所在就是关注封装,关注封装驱动的对音/视频的选择。如果是用 FFmpeg api,则需要关注的是 avformat,关注 av_interleaved_write_frame() 这个接口的调用。而如果是 MediaCodec,则需要关注的是 MediaMuxer 类中的 writeSampleData 接口。

我们要保证,这个接口写入的包的 dts_t 的信息是连续的,或者单调的。
那么,也就是 av_interleaved_write_frame(AVFormatContext * ctx, AVPacket *pkt) 中的 pkt->dts 根据 stream->timebase 转换成 dts_t 后的 float 值,是连续的,或者单调的。
用 MediaCodec,由于 mediacodec 没有 dts 的概念,在文件中的存放顺序就是解码顺序,所以我们就要关注 writeSampleData(int, ByteBuffer, MediaCodec.BufferInfo) 中的 BufferInfo.presentationTimeUs 这个参数是连续的、单调的。

这里的连续的,是指,我们要拿两个变量来分别记录上次写入的视频包和音频包的这个值,如果这一帧是视频帧,它的 dts_t 或者 presentationTimeUs 大于了上次写入的音频包的这个值,那么写入的下一帧,就得是个音频帧。如果小于,那么就继续写视频帧。
如果这一帧是音频帧,它的值大于上次写入的视频包的这个值,那么写入的下一帧,就得是个视频帧,否则,就继续写音频帧。

也就是说,下一帧要编码视频还是音频,是由封装时写入的包的时间值选择驱动的。如果是多线程编码,则要阻塞视频编码或者阻塞音频编码,是由这个值来决定的。

总之,要保证实实在在往文件中写入操作的这个接口调用时参数中的 pkt->dts 或者 Bufferinfo.presentationTimeUs 是连续或单调的。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/193436.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • UE4地编基础-材质蓝图篇[通俗易懂]

    UE4地编基础-材质蓝图篇[通俗易懂]一、贴图规格颜色贴图:颜色贴图必须是8位数RGB颜色。如果在UE4里,发现颜色贴图泛白,那么明颜色贴图是16位数的。改成8位数就正常了。灰度贴图:包括AO、Metallic(金属度)、Roughness(粗糙度)贴图。法线贴图:UE4支持Directx的法线贴图。二、贴图效果调节(蓝图)法线贴图强度调节:AO贴图强度调节:三、各种材质制作1、玻璃材质2、自发光材质……

    2022年9月27日
  • python中if的用法例子_python中while的意思是

    python中if的用法例子_python中while的意思是一、if判断语句if语句是用来进行判断的,其使用格式如下:if要判断的条件:条件成立时,要做的事情else这里写代码片否者要做的事情二、框图三、参考代码:chePiao=1ifchePiao==1:print("hell")else:print("hell…

    2022年9月26日
  • JVM成长之路,记录一次内存溢出导致频繁FGC的问题排查及解决「建议收藏」

    JVM成长之路,记录一次内存溢出导致频繁FGC的问题排查及解决「建议收藏」现象:现象截图:内存:命令:jmap-heap30069GC截图:FGC次数19529次!!!何等的恐怖!!!!!命令:jstat-gcutil300691000现象描述:Node模块启动后收到请求却未能响应。一直在频繁的FGC。新生代内

    2022年6月19日
  • 广东公布重点项目名单:茂名天源石化碳三碳四资源利用项目等[通俗易懂]

    广东公布重点项目名单:茂名天源石化碳三碳四资源利用项目等[通俗易懂]广东省发展改革委官网3月31日公布《广东省2021年重点建设项目计划》。2021年广东共安排省重点项目1395个,总投资达7.28万亿元,年度计划投资8000亿元。石化重大项目方面:新开工项目有3个,总投资约267亿元,年度投资约60.5亿元,分别是恒力石化(惠州)PTA项目、东华能源(茂名)烷烃资源综合利用项目(一期)、茂名天源石化碳三碳四资源利用项目。续建项目有7个,总投资超过1500亿元,2021年度投资约160.9亿元。分别是埃克森美孚惠州乙烯项目、惠州石化产品结构优化及升级项目、惠州宇新轻

    2022年10月15日
  • mongodb 集合_mongodb原理

    mongodb 集合_mongodb原理最近公司用MongoDB,整合一下网上的优缺点,学习下MongoDB没有找到原作者一:MongoDB的优点和缺点优点面向文档存储(类JSON数据模式简单而强大) 动态查询 全索引支持,扩展到内部对象和内嵌数组 查询记录分析 快速,就地更新 高效存储二进制大对象(比如照片和视频) 复制和故障切换支持 Auto-Sh…

    2022年10月30日
  • mssql QUOTENAME 的作用[通俗易懂]

    mssql QUOTENAME 的作用[通俗易懂]简单来说吧比如你有一个表,名字叫index你有一个动态查询,参数是表名declare@tbnamevarchar(256)set@tbname=’index’—查这个表里的数据:print(‘select*from’+@tbname)exec(‘select*from’+@tbname)–这样print出来的数据是select*…

    2022年7月25日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号