欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

ExoPlayer播放器剖析 流程分析---从build到prepare看ExoPlayer的创建流程

程序员文章站 2022-03-03 22:01:49
一、前言:上一篇博客介绍了exoplayer的简单demo,对流程有了一个大致的了解,我们都知道exoplayer的本质是调用Android原生的MediaCodec接口,这篇博客将着重分析其内部实现逻辑,看exoplayer是如何层层封装至MediaCodec的。二、源码分析:先贴出下上篇博客中讲exoplayer初始化的五步曲: private fun initPlayer(playUri: String?) { if (playUri == null){...

关联博客:

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的内部实现;

ExoPlayer播放器剖析 流程分析---从build到prepare看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的循环处理,下面是对目前代码流程的一个创建时序图,需要注意其中播放器的状态机变化:
ExoPlayer播放器剖析 流程分析---从build到prepare看ExoPlayer的创建流程
在下一篇博客中,将重点分析doSomeWork函数,看看exoplayer到底是如何封装MediaCodec的。

本文地址:https://blog.csdn.net/achina2011jy/article/details/112781822