ExoPlayer播放器剖析 流程分析---从build到prepare看ExoPlayer的创建流程
关联博客:
ExoPlayer播放器剖析(一)进入ExoPlayer的世界
ExoPlayer播放器剖析(二)编写exoplayer的demo
ExoPlayer播放器剖析(三)流程分析—从build到prepare看ExoPlayer的创建流程
ExoPlayer播放器剖析(四)从renderer.render函数分析至MediaCodec
ExoPlayer播放器剖析(五)ExoPlayer对AudioTrack的操作
ExoPlayer播放器扩展(一)DASH流与HLS流简介
一、前言:
上一篇博客介绍了exoplayer的简单demo,对流程有了一个大致的了解,我们都知道exoplayer的本质是调用Android原生的MediaCodec接口,这篇博客将着重分析其内部实现逻辑,看exoplayer是如何完成创建的。
二、流程分析:
先贴出上篇博客中讲exoplayer初始化的五步曲:
private fun initPlayer(playUri: String?) {
if (playUri == null){
Log.d("ExoTest", "playUri is null!")
return
}
/* 1.创建SimpleExoPlayer实例 */
mPlayer = SimpleExoPlayer.Builder(this).build()
/* 2.创建播放菜单并添加到播放器 */
val firstLocalMediaItem = MediaItem.fromUri(playUri)
mPlayer!!.addMediaItem(firstLocalMediaItem)
/* 3.设置播放方式为自动播放 */
mPlayer!!.playWhenReady = true
/* 4.将SimpleExoPlayer实例设置到StyledPlayerView中 */
mPlayerView!!.player = mPlayer
/* 5,设置播放器状态为prepare */
mPlayer!!.prepare()
}
实际上,我们可以看到,真正跟播放器相关的只有1、3和5三步,我们就从这三步入手看看exoplayer内部到底是如何实现的。
1.分析SimpleExoPlayer.Builder(this).build():
看build实现如下:
/**
* Builds a {@link SimpleExoPlayer} instance.
*
* @throws IllegalStateException If this method has already been called.
*/
public SimpleExoPlayer build() {
Assertions.checkState(!buildCalled);
buildCalled = true;
return new SimpleExoPlayer(/* builder= */ this);
}
}
看build方式实际上是实例了一个SimpleExoPlayer对象,继续跟进到SimpleExoPlayer的构造函数:
protected SimpleExoPlayer(Builder builder) {
...
/* 构建render */
renderers =
builder.renderersFactory.createRenderers(
eventHandler,
componentListener,
componentListener,
componentListener,
componentListener);
...
// Build the player and associated objects.
player =
new ExoPlayerImpl(
renderers,
builder.trackSelector,
builder.mediaSourceFactory,
builder.loadControl,
builder.bandwidthMeter,
analyticsCollector,
builder.useLazyPreparation,
builder.seekParameters,
builder.pauseAtEndOfMediaItems,
builder.clock,
builder.looper);
player.addListener(componentListener);
videoDebugListeners.add(analyticsCollector);
videoListeners.add(analyticsCollector);
audioDebugListeners.add(analyticsCollector);
audioListeners.add(analyticsCollector);
addMetadataOutput(analyticsCollector);
...
}
可以看到,SimpleExoPlayer的构造里面继续去实例化了一个ExoPlayerImpl的对象,再往下跟进:
public ExoPlayerImpl(
Renderer[] renderers,
TrackSelector trackSelector,
MediaSourceFactory mediaSourceFactory,
LoadControl loadControl,
BandwidthMeter bandwidthMeter,
@Nullable AnalyticsCollector analyticsCollector,
boolean useLazyPreparation,
SeekParameters seekParameters,
boolean pauseAtEndOfMediaItems,
Clock clock,
Looper applicationLooper) {
...
internalPlayer =
new ExoPlayerImplInternal(
renderers,
trackSelector,
emptyTrackSelectorResult,
loadControl,
bandwidthMeter,
repeatMode,
shuffleModeEnabled,
analyticsCollector,
seekParameters,
pauseAtEndOfMediaItems,
applicationLooper,
clock,
playbackInfoUpdateListener);
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
}
继续实例化对象ExoPlayerImplInternal,需要注意的是ExoPlayerImplInternal实现了Handler.Callback接口,所以实例化的方式是以new Handler来进行,那么
ExoPlayerImplInternal必然实现了handleMessage方法,同时,也知道了ExoPlayerImplInternal的内部是以消息机制来通信的。所以,到目前为止我们清楚了ExoPlayerImplInternal才是内部的核心播放器,上面的几层封装不过是为了服务于api函数而已。
下面我们看下ExoPlayerImplInternal的构造函数:
public ExoPlayerImplInternal(
Renderer[] renderers,
TrackSelector trackSelector,
TrackSelectorResult emptyTrackSelectorResult,
LoadControl loadControl,
BandwidthMeter bandwidthMeter,
@Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled,
@Nullable AnalyticsCollector analyticsCollector,
SeekParameters seekParameters,
boolean pauseAtEndOfWindow,
Looper applicationLooper,
Clock clock,
PlaybackInfoUpdateListener playbackInfoUpdateListener) {
...
/* 维护playbackInfo类 */
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
...
/* 获取每个渲染器的属性 */
for (int i = 0; i < renderers.length; i++) {
renderers[i].setIndex(i);
rendererCapabilities[i] = renderers[i].getCapabilities();
}
/* 音视频同步会使用该类 */
mediaClock = new DefaultMediaClock(this, clock);
...
/* 创建一个子线程:用于分担main looper的工作量 */
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
// not normally change to this priority" is incorrect.
internalPlaybackThread = new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO);
internalPlaybackThread.start();
playbackLooper = internalPlaybackThread.getLooper();
handler = clock.createHandler(playbackLooper, this);
}
我们看到有代码中会获取多个render属性,这里的render有几个?分别是什么意思?是在哪里创建的?通过调试打印发现,一共会有6个render,用于处理6种类型的数据,render的创建是在最外层的SimpleExoPlayer构造函数中完成的,可以看到最上面的代码,renderers = builder.renderersFactory.createRenderers,这里的renderersFactory对应的接口是RenderersFactory,默认情况下实现类为DefaultRenderersFactory,看一下该类的createRenderers方法:
----------------------------------------------------------------------------
createRenderers@ExoPlayer\library\core\src\main\java\com\google\android\exoplayer2\DefaultRenderersFactory.java
----------------------------------------------------------------------------
@Override
public Renderer[] createRenderers(
Handler eventHandler,
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput,
MetadataOutput metadataRendererOutput) {
ArrayList<Renderer> renderersList = new ArrayList<>();
/* 1.video的render */
buildVideoRenderers(
context,
extensionRendererMode,
mediaCodecSelector,
enableDecoderFallback,
eventHandler,
videoRendererEventListener,
allowedVideoJoiningTimeMs,
renderersList);
@Nullable
/* 2.audio的render */
AudioSink audioSink =
buildAudioSink(context, enableFloatOutput, enableAudioTrackPlaybackParams, enableOffload);
if (audioSink != null) {
buildAudioRenderers(
context,
extensionRendererMode,
mediaCodecSelector,
enableDecoderFallback,
audioSink,
eventHandler,
audioRendererEventListener,
renderersList);
}
/* 3.字幕文本的render */
buildTextRenderers(context, textRendererOutput, eventHandler.getLooper(),
extensionRendererMode, renderersList);
/* 4.MetaData的render */
buildMetadataRenderers(context, metadataRendererOutput, eventHandler.getLooper(),
extensionRendererMode, renderersList);
/* 5.动态摄像机的render,这个我也不知道是用来干什么的 */
buildCameraMotionRenderers(context, extensionRendererMode, renderersList);
/* 6.混合render,可能是用来存放其他类似数据的 */
buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList);
return renderersList.toArray(new Renderer[0]);
}
从上述代码中可以看到,默认是有6个render的,但是通常第6个不会创建,每种类型的render会实例化进行具体的类实现,这里就不去展开描述了。
总结:
a.SimpleExoPlayer是供外部调用的api类,构造函数里面会去创建5~6个类型的render用于各自类型的数据处理;
b.ExoPlayerImplInternal是最终的调用实现,再往下调用将是exoplayer的内部实现;
2.setPlayWhenReady分析:
@Override
public void setPlayWhenReady(boolean playWhenReady) {
verifyApplicationThread();
/* 与音频焦点相关 */
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
}
跟进updatePlayWhenReady函数:
private void updatePlayWhenReady(
boolean playWhenReady,
@AudioFocusManager.PlayerCommand int playerCommand,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
playWhenReady = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY;
@PlaybackSuppressionReason
int playbackSuppressionReason =
playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
? Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS
: Player.PLAYBACK_SUPPRESSION_REASON_NONE;
/* 调用ExoPlayerImpl的对应函数 */
player.setPlayWhenReady(playWhenReady, playbackSuppressionReason, playWhenReadyChangeReason);
}
跟进到ExoPlayerImpl:
public void setPlayWhenReady(
boolean playWhenReady,
@PlaybackSuppressionReason int playbackSuppressionReason,
@PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
...
internalPlayer.setPlayWhenReady(playWhenReady, playbackSuppressionReason);
...
}
这里的internalPlayer是ExoPlayerImplInternal,继续看下setPlayWhenReady函数:
public void setPlayWhenReady(
boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason) {
handler
.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, playbackSuppressionReason)
.sendToTarget();
}
前面我们分析构造函数的时候说过,ExoPlayerImplInternal是一个Hander,直接看handleMessage中是如何处理MSG_SET_PLAY_WHEN_READY消息的:
case MSG_SET_PLAY_WHEN_READY:
setPlayWhenReadyInternal(
/* playWhenReady= */ msg.arg1 != 0,
/* playbackSuppressionReason= */ msg.arg2,
/* operationAck= */ true,
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
break;
继续跟进,看下setPlayWhenReadyInternal函数实现:
private void setPlayWhenReadyInternal(
boolean playWhenReady,
@PlaybackSuppressionReason int playbackSuppressionReason,
boolean operationAck,
@Player.PlayWhenReadyChangeReason int reason)
throws ExoPlaybackException {
playbackInfoUpdate.incrementPendingOperationAcks(operationAck ? 1 : 0);
playbackInfoUpdate.setPlayWhenReadyChangeReason(reason);
playbackInfo = playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason);
rebuffering = false;
/* 正常情况下,初始化不会进入这个分支 */
if (!shouldPlayWhenReady()) {
stopRenderers();
updatePlaybackPositions();
} else {
/* 如果播放器状态为ready,则开始渲染并发送MSG_DO_SOME_WORK消息 */
if (playbackInfo.playbackState == Player.STATE_READY) {
startRenderers();
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
/* 如果播放器状态为buffring,发送MSG_DO_SOME_WORK消息 */
} else if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
}
}
是否还记得第一篇博客中将的播放器一共存在四种状态机,分别是idle、buffring、ready和ended,实际上,此时的播放器状态是idle,因此进入else分支之后什么都不会去执行。
总结:
setPlayWhenReady在底层维护了一些类变量的更新,没有去做太多关键性的操作。
3.prepare分析:
还是从SimpleExoPlayer开始:
@Override
public void prepare() {
verifyApplicationThread();
/* 同setPlayWhenReady操作无异 */
boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, Player.STATE_BUFFERING);
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
/* 执行prepare */
player.prepare();
}
跟进到prepare:
@Override
public void prepare() {
/* 如果状态不是STATE_IDLE则异常,直接返回 */
if (playbackInfo.playbackState != Player.STATE_IDLE) {
return;
}
PlaybackInfo playbackInfo = this.playbackInfo.copyWithPlaybackError(null);
/* 更新playbackInfo的状态为STATE_BUFFERING */
playbackInfo =
playbackInfo.copyWithPlaybackState(
playbackInfo.timeline.isEmpty() ? Player.STATE_ENDED : Player.STATE_BUFFERING);
// Trigger internal prepare first before updating the playback info and notifying external
// listeners to ensure that new operations issued in the listener notifications reach the
// player after this prepare. The internal player can't change the playback info immediately
// because it uses a callback.
pendingOperationAcks++;
/* 调用下一级的prepare */
internalPlayer.prepare();
/* 更新播放信息 */
updatePlaybackInfo(
playbackInfo,
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* ignored */ TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
/* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
/* seekProcessed= */ false);
}
这里需要注意的时候,播放信息状态机已经更新为STATE_BUFFERING,后面将会赋值给播放器进行状态更新, 继续进入到ExoPlayerImplInternal的prepare函数:
public void prepare() {
handler.obtainMessage(MSG_PREPARE).sendToTarget();
}
看下是如何处理MSG_PREPARE消息的:
case MSG_PREPARE:
prepareInternal();
break;
继续看下prepareInternal函数:
private void prepareInternal() {
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
resetInternal(
/* resetRenderers= */ false,
/* resetPosition= */ false,
/* releaseMediaSourceList= */ false,
/* resetError= */ true);
loadControl.onPrepared();
/* 设置播放器状态为STATE_BUFFERING */
setState(playbackInfo.timeline.isEmpty() ? Player.STATE_ENDED : Player.STATE_BUFFERING);
mediaSourceList.prepare(bandwidthMeter.getTransferListener());
/* 重点是发送MSG_DO_SOME_WORK消息 */
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
看下如何处理MSG_DO_SOME_WORK消息:
case MSG_DO_SOME_WORK:
doSomeWork();
break;
doSomeWork函数非常长,选择重点来分析:
private void doSomeWork() throws ExoPlaybackException, IOException {
...
/* 1.更新音频时间戳 */
updatePlaybackPositions();
...
/* 2.调用各个类型的render进行数据处理 */
for (int i = 0; i < renderers.length; i++) {
...
/* 核心处理方法 */
renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
....
}
...
if (finishedRendering && playingPeriodHolder.info.isFinal) {
setState(Player.STATE_ENDED);
stopRenderers();
/* 更新播放器状态为STATE_READY */
} else if (playbackInfo.playbackState == Player.STATE_BUFFERING
&& shouldTransitionToReadyState(renderersAllowPlayback)) {
setState(Player.STATE_READY);
/* 如果playWhenReady为true,则开始渲染 */
if (shouldPlayWhenReady()) {
startRenderers();
}
} else if (playbackInfo.playbackState == Player.STATE_READY
&& !(enabledRendererCount == 0 ? isTimelineReady() : renderersAllowPlayback)) {
rebuffering = shouldPlayWhenReady();
setState(Player.STATE_BUFFERING);
stopRenderers();
}
...
if ((shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY)
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
/* 开启渲染之后将进入这个分支:ACTIVE_INTERVAL_MS为10ms */
maybeScheduleWakeup(operationStartTimeMs, ACTIVE_INTERVAL_MS);
} else if (enabledRendererCount != 0 && playbackInfo.playbackState != Player.STATE_ENDED) {
scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS);
} else {
handler.removeMessages(MSG_DO_SOME_WORK);
}
}
整个doSomeWork函数将会从prepare之后开始不断执行,updatePlaybackPositions是更新音频的时间戳,便于后面同步时使用,紧接着是调用各个类型的render进行渲染操作,其中最核心的代码就是renderer.render,这个方法的实现最终会调用到MediaCodec里面去,后面为更新状态,开启渲染操作,最后一点需要注意的是,maybeScheduleWakeup函数的实现,这里面会发送MSG_DO_SOME_WORK从而进行循环处理,看下这个函数:
private void maybeScheduleWakeup(long operationStartTimeMs, long intervalMs) {
if (offloadSchedulingEnabled && requestForRendererSleep) {
return;
}
/* 执行下次循环操作 */
scheduleNextWork(operationStartTimeMs, intervalMs);
}
继续跟进:
private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) {
handler.removeMessages(MSG_DO_SOME_WORK);
/* intervalMs值为10ms */
handler.sendEmptyMessageAtTime(MSG_DO_SOME_WORK, thisOperationStartTimeMs + intervalMs);
}
这里就可以看到,整个exoplayer的不停渲染操作实际上是通过消息自发送消息处理来完成的,并且每次处理渲染的时间间隔为10ms。
总结:
如果仅仅是看exoplayer的内部运行机制,基本上就已经看完了,核心就是doSomeWork做的工作,不停地调用各种render来进行数据处理同时发送消息来实现10ms的循环处理,下面是对目前代码流程的一个创建时序图,需要注意其中播放器的状态机变化:
在下一篇博客中,将重点分析doSomeWork函数,看看exoplayer到底是如何封装MediaCodec的。
本文地址:https://blog.csdn.net/achina2011jy/article/details/112781822
上一篇: Chipotle数据分析-知识点汇总
下一篇: Android项目配置签名