ExoPlayer架构详解与源码分析(6)——MediaPeriod

系列文章目录

ExoPlayer架构详解与源码分析(1)------前言

ExoPlayer架构详解与源码分析(2)------Player

ExoPlayer架构详解与源码分析(3)------Timeline

ExoPlayer架构详解与源码分析(4)------整体架构

ExoPlayer架构详解与源码分析(5)------MediaSource

ExoPlayer架构详解与源码分析(6)------MediaPeriod


前言

上篇看完了MediaSource,发现其中正在发挥作用的是其中的MediaPeriod,如果MediaSource是燃料系统的外壳,那么MediaPeriod就是其外壳下的核心,媒体数据的的加载获取甚至是解析主要就靠它了。

MediaPeriod

先看下整体结构

MediaPeriod主要用于加载于Timeline中的一个Period对于的媒体数据。换句话说Timeline中有多少个Period就会对于多少个MediaPeriod。 MediaPeriod的所有方法都在播放器内部线程调用。MediaPeriod是在MediaSource.createPeriod创建的,MediaSource中媒体数据的加载读取解复用最终提供数据给Renderer等都通过它来实现。MediaPeriod 中每个轨道对应一个SampleStream,MediaPeriod 在同一时间可能只能为一个SampleStream提供数据,但是当前的SampleStream 可以切换(因为MediaPeriod在读取数据时是单线程的,获取到数据后会同步解析数据,通过TrackId 将其关联到不同的轨道的SampleStream 中,这个后面具体会讲到)。

看下几个重要的方法定义

  • prepare 异步准备当前的MediaPeriod,因为是异步的方法入参提供了一个Callback在prepared后通知调用者,在通知调用者前搜先会调用MediaSource的onSourceInfoRefreshed通知MediaSource更新Timeline,然后完成解轨道数据的解析提供给getTrackGroups 方法,目的是提前准备好Timeline,如果prepare失败就会调用MediaPeriod的maybeThrowPrepareError方法

  • maybeThrowPrepareError功能如上,这个方法只会在prepare完成前调用

  • getTrackGroups 获取解析出的轨道数据,因为解析轨道是prepare时完成的,所以这个方法只能在prepared之后调用。

  • selectTracks执行轨道选择,这是一个重要的方法,就是在这个方法中将轨道数据提供给上层使用者的确切的说是Renderer,同时Renderer也通过mayRetainStreamFlags 告诉MediaPeriod使用保留当前的SampleStream。MediaPeriod如果创建了新的流或者更新了SampleStream也会通过streamResetFlags=true来告诉Renderer需要重置当前的Renderer了。另外每次调用都应该更新TrackSelections。同样这个方法肯定也只能在prepared之后调用。

    java 复制代码
      long selectTracks(
          @NullableType ExoTrackSelection[] selections,//TrackSelector提供的轨道选择
          boolean[] mayRetainStreamFlags,//需要保留的流
          @NullableType SampleStream[] streams,//提供的数据流
          boolean[] streamResetFlags,//需要替换的流
          long positionUs);//当前的播放位置,如果当前的period还没有播放,这个值就是起始播放位置
  • seekToUs Seek到指定位置。仅当至少选择一个轨道时才会调用此方法。

  • continueLoading 当需要继续加载数据时调用,MediaPeriod在prepareing或者prepared后都可以调用这个方法。

  • reevaluateBuffer 释放SampleStream的相关缓存,同样这个方法肯定也只能在prepared之后调用。

  • getStreamKeys 当MediaPeriod由多个播放源时,如HLS,这个方法返回一个多个源关于选中轨道的KeyList

MediaPeriod接口定义了,数据解析的大致过程:

  1. prepare准备数据源,完成后selectTracks向外提供出SampleStream
  2. continueLoading 继续加载数据填充SampleStream
  3. 将使用完的数据通过reevaluateBuffer释放

MediaPeriod的实现

可以看到基本每一种ExoPlayer支持的媒体都实现了一个MediaPeriod,ProgressiveMediaPeriod相对比较基础和典型,后面也将重点解析它,剩余其他几种类型的可以先做了解,后面有时间再扩充。

ProgressiveMediaPeriod

ProgressiveMediaSource的MediaPeriod实现,主要用于渐进式媒体文件的加载,如本地或远程的单个视频文件, 先看下ProgressiveMediaPeriod整体结构 ProgressiveMediaPeriod主要分2部分:

  • 数据存取 管理缓存获取的数据,主要交由SampleQueue来负责,需要提供高效的缓存读写能力保证数据的持续性。
  • 数据加载解析 加载主要由DataSource负责,主要工作是获取媒体数据供解析器读取,解析器主要指的是Extractor,主要负责将获取的数据解析到不同的SampleQueue中。

上图中并没有MediaPeriod提到的SampleStream,其实ProgressiveMediaPeriod内部类中实现了SampleStream ,通过内部类将 对SampleStream 操作通过trackid关联转发给SampleQueue来实现,所以上图中的SampleQueue其实就是相当于SampleStream。

java 复制代码
private final class SampleStreamImpl implements SampleStream {

    private final int track;

    public SampleStreamImpl(int track) {
      this.track = track;
    }

    @Override
    public boolean isReady() {
      return ProgressiveMediaPeriod.this.isReady(track);
    }

    @Override
    public void maybeThrowError() throws IOException {
      ProgressiveMediaPeriod.this.maybeThrowError(track);
    }

    @Override
    public int readData(
        FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) {
      return ProgressiveMediaPeriod.this.readData(track, formatHolder, buffer, readFlags);
    }

    @Override
    public int skipData(long positionUs) {
      return ProgressiveMediaPeriod.this.skipData(track, positionUs);
    }
  }
  /* package */ int readData(
      int sampleQueueIndex,
      FormatHolder formatHolder,
      DecoderInputBuffer buffer,
      @ReadFlags int readFlags) {
    if (suppressRead()) {
      return C.RESULT_NOTHING_READ;
    }
    maybeNotifyDownstreamFormat(sampleQueueIndex);
    int result =//sampleQueueIndex查询对应的sampleQueues读取数据
        sampleQueues[sampleQueueIndex].read(formatHolder, buffer, readFlags, loadingFinished);
    if (result == C.RESULT_NOTHING_READ) {
      maybeStartDeferredRetry(sampleQueueIndex);
    }
    return result;
  }

Extractor和DataSource那一块还没扩展开,目前就已经很复杂了,DrmSessionManager和LoadErrorHandlingPolicy不是主线任务就跳过了,重点关注Loder、SampleQueue、DataSource、Extractor。

总结

MediaPeriod接口只是定义了标准的执行逻辑,具体如何实现其中定义的每个方法其实每种MediaPeriod实现都会有很大的差异,因为媒体结构本身就千变万化。ProgressiveMediaPeriod实现了其中一种相对基础的媒体类型,但是结构也很复杂,预计下面分多篇将ProgressiveMediaPeriod的SampleQueue、Loder、DataSource、Extractor分析一遍,下篇先从SampleQueue说起。


版权声明 ©

本文为作者山雨楼原创文章

转载请注明出处

原创不易,觉得有用的话,收藏转发点赞支持

相关推荐
Antonio9151 小时前
【音视频】Android NDK 与.so库适配
android·音视频
sun00770010 小时前
android ndk编译valgrind
android
AI视觉网奇11 小时前
android studio 断点无效
android·ide·android studio
jiaxi的天空11 小时前
android studio gradle 访问不了
android·ide·android studio
No Silver Bullet12 小时前
android组包时会把从maven私服获取的包下载到本地吗
android
catchadmin12 小时前
PHP serialize 序列化完全指南
android·开发语言·php
tangweiguo0305198713 小时前
Kable使用指南:Android BLE开发的现代化解决方案
android·kotlin
00后程序员张16 小时前
iOS App 混淆与资源保护:iOS配置文件加密、ipa文件安全、代码与多媒体资源防护全流程指南
android·安全·ios·小程序·uni-app·cocoa·iphone
柳岸风17 小时前
Android Studio Meerkat | 2024.3.1 Gradle Tasks不展示
android·ide·android studio
编程乐学17 小时前
安卓原创--基于 Android 开发的菜单管理系统
android