ExoPlayer里里外外之:核心类和数据流

来源:互联网 发布:云同步盘 linux 编辑:程序博客网 时间:2024/05/14 20:13

2017-09-08 TAO streaming那些事儿

相信大部分玩Android人并不陌生,ExoPlayer是Google在Android上除了MediaPlayer之外,提供的一套完全基于Java的播放器,https://github.com/google/ExoPlayer是官方地址,优点自不必多说,支持Dash、HLS、SS、RTMP等,最重要的是它完全是Java的,极大的方便了基于它的Video App的升级。


ExoPlayer不乏使用者,并且越来越流行,但彻底弄清楚它的里里外外的我相信是少数人,网上能看到的资料大部分都是翻译自官网的,详解它的实现的少之又少;由于它本身是开源的,我们会把当前掌握的一些技术细节给大家分享出来,大家一起探讨,后续会逐渐分享。另外我们在使用的过程中也做了不少优化,改进相当明显,其中非涉密的部分,我们也会分享给大家,还有一些有优化想法的部分,也会在叙述过程中罗列出来。


先大致描述一下ExoPlayer核心类库的关系,如下图:


ExoPlayerImplInternal是播放器主线程所在,它最核心依赖的两个类MediaSource和Renderer,MediaSource是HLS/Dash/SS等Source的接口定义,Renderer是MediaCodecRenderer的接口,MediaCodecRenderer最终会调用Android SDK的MediaCodec和Render(surface和track);MediaSource会create MediaPeriod,MediaPeriod是ExoPlayer中source部分大循环loop的入口,也许是受Dash的影响,这个入口被命名成了Peroid(Dash MPD最外层的XML Tag就是Peroid)。


整个ExoPlayer工作在ExoPlayerImplInternal Looper线程中,不停地通过MediaPeriod跟Source读数据,写到Buffer中,然后Renderer不停地从Buffer中取数据送给MediaCodec解码然后显示,完成整个播放的pipeline;那这个Buffer究竟是怎么工作的,形态是什么样的?我们参看下面的数据流图(以HLS播放为例):


上图以HLS播放为例,图中,蓝色部分表示的是ExoPlayer的主线程,绿色部分是下载线程,注意每个manifest或者chunk的下载都会启用一个新的线程,下载到的视频数据最终会add到DefaultTrackOutput的DataQueue中,Renderer会从DataQueue中读取数据;上图中虚线示意AdtsReader和H264Reader会并行工作,但A/V数据会分别放到两个不同的DefaultTrackOutput实例中。


DataQueue中存放的是demux后的数据,以Sample为单位,对video来说就是一帧;有些播放器的source buffer会直接存放demux前的数据,对HLS来说就是ts数据,这里有点不同;这么设计buffer的一个原因,我再次认为,是为Dash定制的(没办法,Dash仍是主流,有DRM就有Dash),因为Dash的MPD文件中音视频数据天然是分开的,Peroid中video和audio的AS是分开的,甚至Peroid内的A、V数据都可以在不同的CDN上。


你马上会想到,FMP4是不是不用做Demux了?没错,你说的对,后面的文章我们会陆续有详细分析。说到此,Dash的这个优点在高码率的情况下,实在是能节省不少CPU资源,由此想到能在配置并不高的电视上流畅的观看4K视频,这是一种怎样的体验?但这背后的技术支撑又岂是码农们一朝一夕的积累。


扯远了,回到DataQueue,DataQueue中只存ES数据,与这些数据相对应的Metadata信息,则是放在一个叫做InfoQueue的数据结构中,InfoQueue根据offset和size来管理DataQueue中的ES。


关于DataQueue和InfoQueue的具体数据结构,后面有单独篇幅做详细分析,插一句,我们也会穿插说下多媒体码农们跟“数据结构和算法”的关系,你会看到,关系还是:蛮大的,往后不要再埋怨面试的时候挂在“数据结构和算法”了?呵呵。。。


这里再提一下streaming时候,DataQueue的数据到底存放多少?原则是什么?有三个关键数据:

  1. LOW_WATERMARK(15s)

  2. HIGH_WATERMARK(30s)

  3. TOTAL_BUFFER_SIZE(16M Bytes)


规则如下:

  1. 如果DataQueue中数据时长小于15s,则继续下载新的Chunk

  2. 如果DataQueue中数据时长大于30s,则停止下载新的Chunk

  3. 如果DataQueue中数据在介于15s~30s,并且targetBufferSize小于TOTAL_BUFFER_SIZE,继续下载

停止下载数据执行:priorityTaskManager.remove(LOADING_PRIORITY) 。

上面的逻辑在DefaultLoadControl中控制,DataQueue中数据时长计算,非常好理解:bufferedDurationUs = nextLoadPositionUs - rendererPositionUs。


Buffer就说到这里:下次写DataQueue和InfoQueue的详细说明,以及ExoPlayer中用到的其他数据结构:大家都熟悉的HashMap,以及,号称堪比Hash的SparseArray。

原创粉丝点击