Exoplayer简介

前言

Exoplayer是一个android平台的播放器,支持格式比android系统的mediaplayer更好,确定性更好,mediaplayer是可以进行厂家定制的,各平台一致性比较差,这里简单介绍一下Exoplayer的最基础的使用接口,方便之后阅读源码

正文

播放器一般分为三部分,获取DataSource,解码以及视输出。因为exoplayer解码基本上是通过android系统mediacode实现的,或者一些分必要的插件实现的,代码比较少,输出包括声音输出以及视频渲染,相对来说业务也不是很复杂,所以资源输入进行了多次抽象。主要分成三层

  1. MediaItem是对输入项的抽象,主要代表一个可播放的资源,包括文件路径,网络地址,android的asset文件和file,
  2. DataSource主要是读取MediaItem内容,
  3. Extractor是解封装,解封装的。
  4. MediaSource主要是对于DataSource进一封装,主要是为了解决HLS对片段的支持。
    一个最简单的MediaSource构建如下:
Uri uri = Uri.parse("/sdcard/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv")
DataSource.Factory dataSourceFactory = new FileDataSource.Factory();
mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory,MatroskaExtractor.FACTORY).
		createMediaSource(MediaItem.fromUri(uri));

大概就是传入文件地址,得到mediaitem,然后通过FileDataSource.Factory(),生成FileDataSource去可以读取文件内容,这里的复杂操作都是通过ProgressiveMediaSource通过类的聚合,把FileDataSource、Extractor和MediaItem统一进行调度,实现文件的读取,注意这里ProgressiveMediaSource是针对单个文件的定制类,如果hls或者dash需要特定的mediaSource。
播放的话,直接调用如下:

    ExoPlayer player = new ExoPlayer.Builder(getApplicationContext()).build();
    player.setMediaSource(mediaSource);
    player.prepare();
    player.play();
    player.setVideoSurface(surface);
    

大概是这样

在这里我们简单的分析一下ProgressiveMediaSource和player的主要流程,
MediaSource最核心的功能有两个

  1. 提供一个Timeline,主要是描述一个多媒体的列表,或者一个片段。是对多媒体管理
  2. 提供MediaPeriod,给播放器读取多媒体资源
    最关键的接口是两个,一个是prepareSource获取TImeLine,createPeriod获取MediaPeriod。我们主要关注createPeriod。
  @Override
  public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
    DataSource dataSource = dataSourceFactory.createDataSource();
    if (transferListener != null) {
      dataSource.addTransferListener(transferListener);
    }
    return new ProgressiveMediaPeriod(
        localConfiguration.uri,
        dataSource,
        progressiveMediaExtractorFactory.createProgressiveMediaExtractor(),
        drmSessionManager,
        createDrmEventDispatcher(id),
        loadableLoadErrorHandlingPolicy,
        createEventDispatcher(id),
        this,
        allocator,
        localConfiguration.customCacheKey,
        continueLoadingCheckIntervalBytes);
  }

这里只是提供了调用接口,最终交给player获取MediaPeriod,因为这里面包含了MediaExtractor以及DataSource,完全可以解封装出我们需要的东西,这里我们记录一下,这就是mkv的MediaExtractor以及FileDataSource。关于mediasource的阅读到此为止,下部分分析Player相关流程。
Player构造比较简单,主要是通过builder模式实现的

    public Builder(Context context) {
      this(
          context,
          () -> new DefaultRenderersFactory(context),
          () -> new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory()));
    }
   
   private Builder(
        Context context,
        Supplier<RenderersFactory> renderersFactorySupplier,
        Supplier<MediaSourceFactory> mediaSourceFactorySupplier) {
      this(
          context,
          renderersFactorySupplier,
          mediaSourceFactorySupplier,
          () -> new DefaultTrackSelector(context),
          DefaultLoadControl::new,
          () -> DefaultBandwidthMeter.getSingletonInstance(context),
          /* analyticsCollectorSupplier= */ null);
    }

    private Builder(
        Context context,
        Supplier<RenderersFactory> renderersFactorySupplier,
        Supplier<MediaSourceFactory> mediaSourceFactorySupplier,
        Supplier<TrackSelector> trackSelectorSupplier,
        Supplier<LoadControl> loadControlSupplier,
        Supplier<BandwidthMeter> bandwidthMeterSupplier,
        @Nullable Supplier<AnalyticsCollector> analyticsCollectorSupplier) {
      this.context = context;
      this.renderersFactorySupplier = renderersFactorySupplier;
      this.mediaSourceFactorySupplier = mediaSourceFactorySupplier;
      this.trackSelectorSupplier = trackSelectorSupplier;
      this.loadControlSupplier = loadControlSupplier;
      this.bandwidthMeterSupplier = bandwidthMeterSupplier;
      this.analyticsCollectorSupplier =
          analyticsCollectorSupplier != null
              ? analyticsCollectorSupplier
              : () -> new AnalyticsCollector(checkNotNull(clock));
      looper = Util.getCurrentOrMainLooper();
      audioAttributes = AudioAttributes.DEFAULT;
      wakeMode = C.WAKE_MODE_NONE;
      videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
      videoChangeFrameRateStrategy = C.VIDEO_CHANGE_FRAME_RATE_STRATEGY_ONLY_IF_SEAMLESS;
      useLazyPreparation = true;
      seekParameters = SeekParameters.DEFAULT;
      seekBackIncrementMs = C.DEFAULT_SEEK_BACK_INCREMENT_MS;
      seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS;
      livePlaybackSpeedControl = new DefaultLivePlaybackSpeedControl.Builder().build();
      clock = Clock.DEFAULT;
      releaseTimeoutMs = DEFAULT_RELEASE_TIMEOUT_MS;
      detachSurfaceTimeoutMs = DEFAULT_DETACH_SURFACE_TIMEOUT_MS;
    }
    
        public ExoPlayer build() {
      return buildSimpleExoPlayer();
    }

    /* package */ SimpleExoPlayer buildSimpleExoPlayer() {
      checkState(!buildCalled);
      buildCalled = true;
      return new SimpleExoPlayer(/* builder= */ this);
    }


player =
          new ExoPlayerImpl(
              renderers,
              builder.trackSelectorSupplier.get(),
              builder.mediaSourceFactorySupplier.get(),
              builder.loadControlSupplier.get(),
              builder.bandwidthMeterSupplier.get(),
              analyticsCollector,
              builder.useLazyPreparation,
              builder.seekParameters,
              builder.seekBackIncrementMs,
              builder.seekForwardIncrementMs,
              builder.livePlaybackSpeedControl,
              builder.releaseTimeoutMs,
              builder.pauseAtEndOfMediaItems,
              builder.clock,
              builder.looper,
              /* wrappingPlayer= */ this,
              additionalPermanentAvailableCommands);
              
  }

注意这里是通过builder初始化了比较多的一个default的factory类,主要解决类型定制的问题,这里主要注意一下:

  1. DefaultRenderersFactory
    这是提供解码器的核心工具类,我们自己定义个简单的MyRenderersFactory
static class MyRenderersFactory implements RenderersFactory{

    private final Context context;

    MyRenderersFactory(Context context){
      this.context = context;
    }
    @Override
    public Renderer[] createRenderers(Handler eventHandler, VideoRendererEventListener videoRendererEventListener,
                                      AudioRendererEventListener audioRendererEventListener, TextOutput textRendererOutput, MetadataOutput metadataRendererOutput) {
      MediaCodecVideoRenderer videoRenderer =
              new MediaCodecVideoRenderer(
                      context,
                      new DefaultMediaCodecAdapterFactory(),
                      MediaCodecSelector.DEFAULT,
                      5000,
                      false,
                      eventHandler,
                      videoRendererEventListener,
                      50);

      @Nullable
      AudioSink audioSink =
              new DefaultAudioSink(AudioCapabilities.getCapabilities(context), new DefaultAudioSink.DefaultAudioProcessorChain(),
                      true, true, DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED);

      MediaCodecAudioRenderer audioRenderer =
              new MediaCodecAudioRenderer(
                      context,
                      new DefaultMediaCodecAdapterFactory(),
                      MediaCodecSelector.DEFAULT,
                      false,
                      eventHandler,
                      audioRendererEventListener,
                      audioSink);
      Renderer[] renderers = {videoRenderer,audioRenderer};
      return renderers;
    }
  }

这里比较明显的就是提供一个render列表。
2. 核心是:MediaSourceList
3. 核心是: MediaPeriodQueue

setMediaSource
  public void setMediaSources(
      List<MediaSourceList.MediaSourceHolder> mediaSources,
      int windowIndex,
      long positionUs,
      ShuffleOrder shuffleOrder) {
    handler
        .obtainMessage(
            MSG_SET_MEDIA_SOURCES,
            new MediaSourceListUpdateMessage(mediaSources, shuffleOrder, windowIndex, positionUs))
        .sendToTarget();
  }

其实就是通过handler处理消息,以及把mediasource,传入
关于如何读取data。以及如何解封装,相关调用比较复杂,不过在我们场景下,就是通过ProgressiveMediaPeriod获取解封装后的数据,解码器中,大概代码如下:

  /* package */ int readData(
      int sampleQueueIndex,
      FormatHolder formatHolder,
      DecoderInputBuffer buffer,
      @ReadFlags int readFlags) {

    int result =
        sampleQueues[sampleQueueIndex].read(formatHolder, buffer, readFlags, loadingFinished);
    return result;
  }

然后通知解码器去处理解码,

其实整体是有两个线程,一个是mediasource负责读取解封装,一个是负责解码渲染。我们大概介绍一下.核心类就是MediaDataSource。具体类图如下:
Exoplayer简介_第1张图片
其核心就是被ExoPlayerImplInternal控制,进行必要的初始化,然后在调用prepare后,启动loader,进行自治,实现自动加载内容,最终把数据缓存在SampleQueue中,被render读取,大概流程
就是ExoPlayerImplInternal调用MediaPerid中的startloading。启动一个loader。然后自治加载解封装内容,大概流程如下:
Exoplayer简介_第2张图片
关于externalLoadable的加载其实也相对比较复杂,但是大概内容就是通过dataSource加载内容,通过Extractor解封装,传入ExtractorOutput,最终保存到sampleQueues中,具体流程这里就不在详细介绍。
我们把目光关注render中的解码以及输出,

后记

这里大概介绍了一下exoplayer的架构,文中跳过不少过程细节,以及状态判断,但是整体流程基本保留,有机会重新整理一下。

你可能感兴趣的:(android,android,java)