播放器顶层设计:
从最外层的用户角度来说,通过setDataSource,prepare,start就可以播放,这些动作对应播放器底层的很多操作。主要包括创建render,创建extractor,读取source,avsync等。
创建render:DefaultRenderersFactory通过createRenderers来创建audio和video render
创建extractor:通过extractor list依次sniff,哪个成功则用哪个
读取source:extractor读取,同时被loadcontrol加载控制读取的节奏
avsync:视频同步音频,读取audio的timestamp,以此为基准在某个范围区间内才让video去render,晚了丢掉,早了等待
ExoPlayerImplInternal中,针对每个render会有renderer.render动作,其实是通过MSG_DO_SOMEWORK这样一个10ms循环一次的消息来完成所有的工作的,包括source的读取,音视频同步
exo官方说明有4个模块,render,track selector,loadcontrol,bandwidth
loadcontrol是用来控制加载的,包括rebuffer需要缓存多少数据等等,time_over_size还是size_over_time等
如何实现音视频同步:
首先获取AudioTrack的timestamp,查询渲染了多长时间的音频,按照这个数据去对照到video,定义一个区间,如果video timestamp也在这个区间,则允许渲染,如果早了则等待下次,如果晚了则直接扔掉,在processOutputBuffer中渲染同步,同时会参考vsync时间,调整80%的offset
解析器的选择:
extractorsFactory创建各extractor,依次sniff,哪个成功使用哪个
ProgressiveMediaSource
load的时候会一直读取,由loadcontrol控制加载
数据的读取:
audio和video数据各有一个samplequeue的环形队列,extractor通过sampleData和samplemetaData将数据送到队列,render通过readSource从里面取走数据
1.解码器创建过程
SimpleExoPlayer 构造函数通过DefaultRenderersFactory的createRenderers方法来创建MediaCodecAudioRender和MediaCodecVideoRenderer,当然还有一些其他render,创建完成之后统一放入renderersList中保存。随后使用renderersList作为构造参数用来构造ExoPlayerImpl,这个是exoplayer的实际干活的。
player =
new ExoPlayerImpl(
renderers,
builder.trackSelector,
builder.mediaSourceFactory,
builder.loadControl,
builder.bandwidthMeter,
analyticsCollector,
builder.useLazyPreparation,
builder.seekParameters,
builder.livePlaybackSpeedControl,
builder.releaseTimeoutMs,
builder.pauseAtEndOfMediaItems,
builder.clock,
builder.looper,
this,
additionalPermanentAvailableCommands);
从 ExoPlayerImpl的构造函数也可以看出,它汇聚了render,trackselector,loadcontrol等等一系列重要的参数生成实际干活的播放器,这些参数都是之前就已经初始化好了的,用于配置播放器之后就开始真正的干活了。
mediacodec的创建过程backtrace
at com.google.android.exoplayer2.mediacodec.SynchronousMediaCodecAdapter$Factory.createAdapter(SynchronousMediaCodecAdapter.java:49) at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.initCodec(MediaCodecRenderer.java:1142) at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.maybeInitCodecWithFallback(MediaCodecRenderer.java:1049) at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.maybeInitCodecOrBypass(MediaCodecRenderer.java:606) at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.onInputFormatChanged(MediaCodecRenderer.java:1470) at com.google.android.exoplayer2.audio.MediaCodecAudioRenderer.onInputFormatChanged(MediaCodecAudioRenderer.java:428) at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.readSourceOmittingSampleData(MediaCodecRenderer.java:1003) at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:835) at com.google.android.exoplayer2.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:945) at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:478) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:260) at android.os.HandlerThread.run(HandlerThread.java:67)
主线程开启之后,通过 scheduleNextWork 每10ms 轮训一次 doSomeWork,通过这种方式处理音视频的很多任务,包括音视频同步等。
回到创建解码器的过程,doSomeWork开启之后,解码器需要初始化,就一定需要直到audio/video的格式信息,才能去配置对应解码器,因此一定需要读取数据去获取,这个在readSource中操作,读取一点数据拿到格式信息,再回来设置解码器。
2.音视频同步的过程
音视频同步的过程,默认是video参考audio时钟,在MediaCodecVideoRenderer中,processOutputBuffer处理,每次处理一个解码buffer之后,都会先去判断此buffer的timestamp
与audio时间戳时间的关系,如果太晚了,则丢了,同步时间戳内则渲染,太早了则等待。注意里面有一个frameReleaseHelper类用来调整vsync时长,它主要功能是找到下一个vsync时间节点并把releasetime提前20%vsync长度的offset,这样可以在下一个vsync刷新周期到来之前确保将buffer release到SurfaceFlinger。
3.source的创建过程
我们source用的是mediasource,这个mediasource使用datasourcefactory创建的,我们看下具体的创建过程。
4.解析器的创建过程
5.解析器读取音视频数据的过程
6.LoadControl控制加载的过程



