• 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));
    
    • 1
    • 2
    • 3
    • 4

    大概就是传入文件地址,得到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);
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    大概是这样

    在这里我们简单的分析一下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);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这里只是提供了调用接口,最终交给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);
                  
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    注意这里是通过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;
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    这里比较明显的就是提供一个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();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其实就是通过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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

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

    后记

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

  • 相关阅读:
    curl常用参数详解及示例
    莱特兄弟的家庭教育
    jenkins-pipeline集成sonarqube代码扫描
    TCP/IP协议详解
    AE动画调整
    Vue基础语法的进阶,事件处理器,自定义组件及组件通信
    学历不高,为何我还要转行编程?这个行业的秘密你知道吗?
    基于github上go版本的LoraWAN Server安装及使用
    部门树递归实现
    基于web的客车自动收费系统
  • 原文地址:https://blog.csdn.net/qq_28282317/article/details/126627985