ExoPlayer源碼淺析

ExoPlayer is an application level media player for Android. It provides an alternative to Android’s MediaPlayer API for playing audio and video both locally and over the Internet. ExoPlayer supports features not currently supported by Android’s MediaPlayer API, including DASH and SmoothStreaming adaptive playbacks. Unlike the MediaPlayer API, ExoPlayer is easy to customize and extend, and can be updated through Play Store application updates.

首先看看ExoPlayer類之間的繼承關系,對這個框架有一個大致的印象

基本類圖(不完整).png

ExoPlayer被定義為Interface,然后又幾個內部類:Factory,Listener,其中,Factory負責初始化ExoPlayer的操作,其關鍵代碼如下:

public static ExoPlayer newInstance(int rendererCount, int minBufferMs, int minRebufferMs) {  return new ExoPlayerImpl(rendererCount, minBufferMs, minRebufferMs);}

Listener則負責向外界回調ExoPlayer狀態變化和錯誤信息。

ExoPlayer有一個子類:ExoPlayerImpl,它繼承了ExoPlayer的所有方法,并且負責接收轉發外界傳遞的消息,為什么是轉發,不是接收呢?因為真正干活的不是ExoPlayerImpl,而是另外一個隱藏類,ExoPlayerImplInternal,幾乎所有的操作都是在ExoPlayerImplInternal中完成的。

Start

我們看一個官方的使用Demo:

// 1. Instantiate the player.
player = ExoPlayer.Factory.newInstance(RENDERER_COUNT);
// 2. Construct renderers.
MediaCodecVideoTrackRenderer videoRenderer = ...
MediaCodecAudioTrackRenderer audioRenderer = ...
// 3. Inject the renderers through prepare.
player.prepare(videoRenderer, audioRenderer);
// 4. Pass the surface to the video renderer.
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
// 5. Start playback.
player.setPlayWhenReady(true);
...
player.release(); 
// Don’t forget to release when done!

我們下面的探索過程都是按照這個Demo一步一步進行的

1.Instantiate the player.

首先,用戶調用ExoPlayer.Factory.newInstance(...)方法得到ExoPlayerImpl的實例,這個過程中,我們看看做了什么:

public ExoPlayerImpl(int rendererCount, int minBufferMs, int minRebufferMs) {
    Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
    //首先初始化一些狀態
    this.playWhenReady = false;
    this.playbackState = STATE_IDLE;
    //ExoPlayer的Listener是通過andListener(listener:Listener)方法添加的,所以需要一個數組去記錄所有的Listener
    this.listeners = new CopyOnWriteArraySet<>();
    //初始化軌道格式數組
    this.trackFormats = new MediaFormat[rendererCount][];
    //選中的軌道索引
    this.selectedTrackIndices = new int[rendererCount];
    //初始化一個Handler,并將收到的消息傳遞給ExoPlayerImpl的handleEvent()方法處理
    eventHandler = new Handler() {
      @Override
      public void handleMessage(Message msg) {
        ExoPlayerImpl.this.handleEvent(msg);
      }
    };
    //初始化ExoPlayerImplInternal
    internalPlayer = new ExoPlayerImplInternal(eventHandler, playWhenReady, selectedTrackIndices,
    minBufferMs, minRebufferMs);
}

然后我們繼續看ExoPlayerImplInternal的構造方法:

public ExoPlayerImplInternal(Handler eventHandler, boolean playWhenReady,int[] selectedTrackIndices, int minBufferMs, int minRebufferMs) {
    //接受從ExoPlayerImpl傳遞進來的Handler
    this.eventHandler = eventHandler;
    //初始化
    this.playWhenReady = playWhenReady;
    this.minBufferUs = minBufferMs * 1000L;
    this.minRebufferUs = minRebufferMs * 1000L;
    //拷貝
    this.selectedTrackIndices = Arrays.copyOf(selectedTrackIndices, selectedTrackIndices.length);
    this.state = ExoPlayer.STATE_IDLE;
    this.durationUs = TrackRenderer.UNKNOWN_TIME_US;
    this.bufferedPositionUs = TrackRenderer.UNKNOWN_TIME_US;
    //初始化StandaloneMediaClock類,它是一個時鐘類,原理是通過獲取手機啟動時間進行差值計算
    standaloneMediaClock = new StandaloneMediaClock();
    //初始化一個自增Integer
    pendingSeekCount = new AtomicInteger();
    enabledRenderers = new ArrayList<TrackRenderer>(selectedTrackIndices.length);
    trackFormats = new MediaFormat[selectedTrackIndices.length][];
    // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
    // not normally change to this priority" is incorrect.
    //初始化和啟動一個HandlerThread
    internalPlaybackThread = new PriorityHandlerThread("ExoPlayerImplInternal:Handler",Process.THREAD_PRIORITY_AUDIO);
    internalPlaybackThread.start();
    //為HandlerThread添加一個Handler
    handler = new Handler(internalPlaybackThread.getLooper(), this);
}

至此,ExoPlayerImpl和ExoPlayerImplInternal兩個類的狀態都被初始化,啟動一個Process.THREAD_PRIORITY_AUDIO的線程,準備接受任務。

2.Construct renderers.

ExoPlayer被初始化后,用戶需要調用ExoPlayer.prepare(...)進行準備工作:

public void prepare(TrackRenderer... renderers);
TrackRenderer和它的孩子們

我們看到,prepare形參是TrackRenderer數組,那么這個TrackRenderer是個什么東東呢?

ExoPlayer的媒體組件,都是通過注入的方式實現的,而TrackRenderer就是媒體組件的基類。

public abstract class TrackRenderer implements ExoPlayerComponent {}

從源碼看,TrackRenderer是個抽象類,繼承自ExoPlayerComponent,只有一個屬性:

private int state;

大部分方法都是圍繞state實現的,剩下的都是抽象方法,TrackRenderer類用來維護state,而具體的工作需要子類去實現,而做法比較巧妙,如TrackRenderer的prepare()方法:

//prepare方法維護state屬性的狀態,具體的執行則是調用doPrepare()方法
final int prepare(long positionUs) throws ExoPlaybackException {
    Assertions.checkState(state == STATE_UNPREPARED);
    state = doPrepare(positionUs) ? STATE_PREPARED : STATE_UNPREPARED;
    return state;
}

//抽象方法,由子類實現
protected abstract boolean doPrepare(long positionUs) throws ExoPlaybackException;

再看看ExoPlayerComponent

public interface ExoPlayerComponent {
    void handleMessage(int messageType, Object message) throws     ExoPlaybackException;
}

從名字看,它是一個組件,用于在播放線程接受消息,所有實現它的類都可以在播放線程接受消息,所以TrackRenderer可以接收來自其他線程的消息。

那我們看TrackRenderer有哪些子類

TrackRenderer.png

從類圖來看,TrackRenderer有很多子類,其中,SampleSourceTrackRenderer比較重要,我們看一下官方文檔對這個類的介紹:

SampleSourceTrackRenderer.png
SampleSource and SampleSourceReader

TrackRenderer的實例,渲染來從SampleSource采集的樣本,SampleSource是什么呢,從名字看應該是樣本源:


SampleSource.png

媒體樣本源,SampleSource一般暴漏一個或多個軌道,軌道的個數和每個軌道的格式可以通過 SampleSource.SampleSourceReader.getTrackCount()和SampleSource.SampleSourceReader.getFormat(int)得到。

再回頭看SampleSourceTrackRenderer的構造方法:

public SampleSourceTrackRenderer(SampleSource... sources) {
    this.sources = new SampleSourceReader[sources.length];
    for (int i = 0; i < sources.length; i++) {
      this.sources[i] = sources[i].register();
    }
}

接受一個或者多個SampleSource數組,然后調用了SampleSource的register()方法

/**
   * A consumer of samples should call this method to register themselves and gain access to the
   * source through the returned {@link SampleSourceReader}.
   * <p>
   * {@link SampleSourceReader#release()} should be called on the returned object when access is no
   * longer required.
   *
   * @return A {@link SampleSourceReader} that provides access to the source.
   */
public SampleSourceReader register();

從官方介紹來看,消費者(獲取樣本的類,這里是指SampleSourceTrackRenderer)通過調用register()方法來獲得對媒體樣本讀取的能力。

register()方法返回SampleSourceReader類:

/**
   *An interface providing read access to a {@link SampleSource}.   
   */
public interface SampleSourceReader 

是一個接口,定義了一些訪問媒體樣本的方法,以下列舉一些重要的方法,詳細可以去com.google.android.exoplayer.SampleSource.SampleSourceReader類查看:

  • prepare(long positionUS):boolean
  • getTrackCount():int
  • getFormat(int track):MediaFormat
  • enable(int track,long position)
  • disable(int track)
  • readData(int track,long positionUs,MediaFormatHolder formatHolder,SampleHolder sampleHolder):int
  • seekToUs(long positionUs)
  • release()

繼續看SampleSourceTrackRenderer的構造方法:

this.sources[i] = sources[i].register();

SampleSourceTrackRenderer中定義一個全局變量,存儲所有的SampleSourceReader,方便其他方法訪問SampleSource中的資源。

到這里,ExoPlayer的框架結構就比較清晰了,TrackRenderer負責渲染由SampleSource提供的媒體樣本。

3. Inject the renderers through prepare.

player.prepare(videoRenderer, audioRenderer);

前兩步分別初始化ExoPlayer、TrackRenderer和SampleSource,并將SampleSource注入到TrackRenderer,但是直到現在,TrackRenderer都沒有和ExoPlayer產生關系,客官們是不是等的不耐煩了??,那么,prepare正是將TrackRenderer注入ExoPlayer,我們通過源碼,一步一步看看prepare都做了哪些工作。

ExoPlayerImpl:

@Override
public void prepare(TrackRenderer... renderers) {
    Arrays.fill(trackFormats, null);
    internalPlayer.prepare(renderers);
}

在ExoPlayerImpl中,首先初始化了trackFormats數組,然后調用ExoPlayerImplInternal的prepare(...)方法。

ExoPlayerImplInternal:

public void prepare(TrackRenderer... renderers) {
    handler.obtainMessage(MSG_PREPARE, renderers).sendToTarget();
}

ExoPlayerImplInternal.prepare(...)方法通過handler發送一個MSG_PREPARE的指令并傳入renderers數組。而這個Handler所在的線程是我們之前講到的初始化過程中啟動工作線程的HandlerThread對應的Handler,那我們找到接受Handler的handleMessage(...)方法:

@Override
public boolean handleMessage(Message msg) {
    try {
        switch (msg.what) {
        ...
        case MSG_PREPARE: {
            //如果消息類型是MSG_PREPARE,則調用下面這個方法并返回true
            prepareInternal((TrackRenderer[]) msg.obj);
            return true;
        }
        ...
    } catch (ExoPlaybackException e) {
        Log.e(TAG, "Internal track renderer error.", e);
        eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
        stopInternal();
        return true;
    } catch (RuntimeException e) {
       Log.e(TAG, "Internal runtime error.", e);
       eventHandler.obtainMessage(MSG_ERROR, new ExoPlaybackException(e, true)).sendToTarget();
       stopInternal();
       return true;
    }  
}

private void prepareInternal(TrackRenderer[] renderers) throws ExoPlaybackException {
      //執行重置工作,主要有:移除handler消息隊列中的MSG_DO_SOME_WORD、MSG_INCREMENTAL_PREPARE
      //停止時鐘,停止TrackRenderer,釋放TrackRenderer,清除緩存等
      resetInternal();
      //將傳入的renderers賦值給全局變量
      this.renderers = renderers;
      //將trackFormats置為空
      Arrays.fill(trackFormats, null);
      for (int i = 0; i < renderers.length; i++) {
        //遍歷所有的renderer
        //TrackRenderer的getMediaClock()的方法介紹在下面
        MediaClock mediaClock = renderers[i].getMediaClock();
        if (mediaClock != null) {
          Assertions.checkState(rendererMediaClock == null);
          //如果該renderer提供MediaClock,則將該MediaClock賦值為全局變量
          rendererMediaClock = mediaClock;
          rendererMediaClockSource = renderers[i];
        }
      }
      //修改狀態為STATE_PREPARING
      setState(ExoPlayer.STATE_PREPARING);
      //增量準備?應該是為了復用代碼進行封裝的,具體看里面的代碼吧
      incrementalPrepareInternal();
}

TrackRenderer.getMediaClock()方法介紹:如果這個renderer提供了他自己的播放位置,那么,這個方法會返回對應的MediaClock,如果有,播放器會使用它提供的MediaClock作為視頻播放周期,一個播放器中至少有一個Renderer提供MediaClock。

private void incrementalPrepareInternal() throws ExoPlaybackException {
    //獲取當前系統啟動時間
    long operationStartTimeMs = SystemClock.elapsedRealtime();
    boolean prepared = true;
    for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
        TrackRenderer renderer = renderers[rendererIndex];
        //如果當前狀態為STATE_UNPREPARED,調用TrackRenderer.prepare(...)方法
        if (renderer.getState() == TrackRenderer.STATE_UNPREPARED) {
            int state = renderer.prepare(positionUs);
            //檢查狀態是否還是STATE_UNPREPARED,如果是則拋出異常
            if (state == TrackRenderer.STATE_UNPREPARED) {
                renderer.maybeThrowError();
                prepared = false;
            }
        }
    }

    if (!prepared) {
        // We're still waiting for some sources to be prepared.
        //如果未準備成功,則在PREPARE_INTERVAL_MS時間后重試
        scheduleNextOperation(MSG_INCREMENTAL_PREPARE, operationStartTimeMs, PREPARE_INTERVAL_MS);
        return;
    }

    long durationUs = 0;
    boolean allRenderersEnded = true;
    boolean allRenderersReadyOrEnded = true;
    //再次遍歷所有的TrackRenderer
    for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
        TrackRenderer renderer = renderers[rendererIndex];
        //獲取每個TrackRenderer的TrackCount(軌道個數)
        int rendererTrackCount = renderer.getTrackCount();
        MediaFormat[] rendererTrackFormats = new MediaFormat[rendererTrackCount];
        for (int trackIndex = 0; trackIndex < rendererTrackCount; trackIndex++) {
            rendererTrackFormats[trackIndex] = renderer.getFormat(trackIndex);
        }
        //記錄每個TrackRenderer的每個Track的格式
        trackFormats[rendererIndex] = rendererTrackFormats;
        if (rendererTrackCount > 0) {
            //如果時間為未知時間,則不作處理,(這塊沒有太看懂,為什么上一個TrackRenderer的durationUs作為這個TrackRenderer的判斷依據,
            // 如果上一個TrackRenderer的durationUs = TrackRenderer.UNKNOWN_TIME_US,則之后所有的durationUs都是TrackRenderer.UNKNOWN_TIME_US?)
            if (durationUs == TrackRenderer.UNKNOWN_TIME_US) {
                // We've already encountered a track for which the duration is unknown, so the media
                // duration is unknown regardless of the duration of this track.
            } else {
                long trackDurationUs = renderer.getDurationUs();
                if (trackDurationUs == TrackRenderer.UNKNOWN_TIME_US) {
                    durationUs = TrackRenderer.UNKNOWN_TIME_US;
                } else if (trackDurationUs == TrackRenderer.MATCH_LONGEST_US) {
                    // Do nothing.
                } else {
                    //如果上一個TrackRenderer的Duration和這個TrackRenderer的Duration不一致,則取大的
                    durationUs = Math.max(durationUs, trackDurationUs);
                }
            }
            //selectedTrackIndices是從ExoPlayerImpl傳入的,每個Renderer同時只能有一個track在工作
            int trackIndex = selectedTrackIndices[rendererIndex];
            if (0 <= trackIndex && trackIndex < rendererTrackFormats.length) {
                //打開對應的Track
                renderer.enable(trackIndex, positionUs, false);
                enabledRenderers.add(renderer);
                allRenderersEnded = allRenderersEnded && renderer.isEnded();
                allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded(renderer);
            }
        }
    }
    this.durationUs = durationUs;
    //更新ExoPlayer的state
    if (allRenderersEnded
            && (durationUs == TrackRenderer.UNKNOWN_TIME_US || durationUs <= positionUs)) {
        // We don't expect this case, but handle it anyway.
        state = ExoPlayer.STATE_ENDED;
    } else {
        state = allRenderersReadyOrEnded ? ExoPlayer.STATE_READY : ExoPlayer.STATE_BUFFERING;
    }

    //通知ExoPlayerImpl,更改狀態
    // Fire an event indicating that the player has been prepared, passing the initial state and
    // renderer track information.
    eventHandler.obtainMessage(MSG_PREPARED, state, 0, trackFormats).sendToTarget();

    // Start the renderers if required, and schedule the first piece of work.
    if (playWhenReady && state == ExoPlayer.STATE_READY) {
        startRenderers();
    }
    // 向Handler發送 MSG_DO_SOME_WORK 命令
    handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}

緊接著又向handler發送MSG_DO_SOME_WORK命令:

private void doSomeWork() throws ExoPlaybackException {
    TraceUtil.beginSection("doSomeWork");
    //獲取系統啟動時間
    long operationStartTimeMs = SystemClock.elapsedRealtime();
    //緩存位置,默認值為durationUs 或 Long.MAX_VALUE
    long bufferedPositionUs = durationUs != TrackRenderer.UNKNOWN_TIME_US ? durationUs
            : Long.MAX_VALUE;
    boolean allRenderersEnded = true;
    boolean allRenderersReadyOrEnded = true;
    //刷新播放時間
    updatePositionUs();
    //遍歷所有開啟的Renderer
    for (int i = 0; i < enabledRenderers.size(); i++) {
        TrackRenderer renderer = enabledRenderers.get(i);
        // TODO: Each renderer should return the maximum delay before which it wishes to be
        // invoked again. The minimum of these values should then be used as the delay before the next
        // invocation of this method.
        //調用TrackRenderer的doSomeWork,這個后面再說
        renderer.doSomeWork(positionUs, elapsedRealtimeUs);
        allRenderersEnded = allRenderersEnded && renderer.isEnded();

        // Determine whether the renderer is ready (or ended). If it's not, throw an error that's
        // preventing the renderer from making progress, if such an error exists.
        boolean rendererReadyOrEnded = rendererReadyOrEnded(renderer);
        if (!rendererReadyOrEnded) {
            renderer.maybeThrowError();
        }
        allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded;

        if (bufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US) {
            // We've already encountered a track for which the buffered position is unknown. Hence the
            // media buffer position unknown regardless of the buffered position of this track.
        } else {
            //獲取實際的緩存位置
            long rendererDurationUs = renderer.getDurationUs();
            long rendererBufferedPositionUs = renderer.getBufferedPositionUs();
            if (rendererBufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US) {
                bufferedPositionUs = TrackRenderer.UNKNOWN_TIME_US;
            } else if (rendererBufferedPositionUs == TrackRenderer.END_OF_TRACK_US
                    || (rendererDurationUs != TrackRenderer.UNKNOWN_TIME_US
                    && rendererDurationUs != TrackRenderer.MATCH_LONGEST_US
                    && rendererBufferedPositionUs >= rendererDurationUs)) {
                // This track is fully buffered.
            } else {
                //一般情況會進入這個條件
                bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs);
            }
        }
    }
    //刷新全局緩存位置
    this.bufferedPositionUs = bufferedPositionUs;

    //如果所有的Renderer都是結束狀態,或者durationUs = TrackRenderer.UNKNOWN_TIME_US,或者durationUs <= positionUs,
    //則停止渲染
    if (allRenderersEnded
            && (durationUs == TrackRenderer.UNKNOWN_TIME_US || durationUs <= positionUs)) {
        setState(ExoPlayer.STATE_ENDED);
        stopRenderers();
    } else if (state == ExoPlayer.STATE_BUFFERING && allRenderersReadyOrEnded) {
        //如果狀態是STATE_BUFFERING,但是所有的Renderer已經準備就緒,則開始渲染,并將狀態改為STATE_READY
        setState(ExoPlayer.STATE_READY);
        if (playWhenReady) {
            startRenderers();
        }
    } else if (state == ExoPlayer.STATE_READY && !allRenderersReadyOrEnded) {
        //如果狀態已經是READY,但不是allRenderersReadyOrEnded,則當前視頻正在緩存,停止渲染,等待緩存
        rebuffering = playWhenReady;
        setState(ExoPlayer.STATE_BUFFERING);
        stopRenderers();
    }

    handler.removeMessages(MSG_DO_SOME_WORK);
    //如何state為STATE_READY或者STATE_BUFFERING,則定時RENDERING_INTERVAL_MS重新執行該方法
    if ((playWhenReady && state == ExoPlayer.STATE_READY) || state == ExoPlayer.STATE_BUFFERING) {
        scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, RENDERING_INTERVAL_MS);
    } else if (!enabledRenderers.isEmpty()) {
        //否則,則定時IDLE_INTERVAL_MS重新執行該方法
        scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, IDLE_INTERVAL_MS);
    }

    TraceUtil.endSection();
}

當prepare調用doSomeWork()之后,在整個播放期間,doSomeWork()會一直重復執行。

看完ExoPlayerImplInternal類,我們再去TrackRenderer看一看:

/**
 * Prepares the renderer. This method is non-blocking, and hence it may be necessary to call it
 * more than once in order to transition the renderer into the prepared state.
 *
 * @param positionUs The player's current playback position.
 * @return The current state (one of the STATE_* constants), for convenience.
 * @throws ExoPlaybackException If an error occurs.
 */
/* package */
final int prepare(long positionUs) throws ExoPlaybackException {
    Assertions.checkState(state == STATE_UNPREPARED);
    state = doPrepare(positionUs) ? STATE_PREPARED : STATE_UNPREPARED;
    return state;
}

/**
 * Invoked to make progress when the renderer is in the {@link #STATE_UNPREPARED} state. This
 * method will be called repeatedly until {@code true} is returned.
 * <p>
 * This method should return quickly, and should not block if the renderer is currently unable to
 * make any useful progress.
 *
 * @param positionUs The player's current playback position.
 * @return True if the renderer is now prepared. False otherwise.
 * @throws ExoPlaybackException If an error occurs.
 */
protected abstract boolean doPrepare(long positionUs) throws ExoPlaybackException;

TrackRenderer的prepare(...)只是修改了state的狀態,具體執行交給了子類,上面介紹過的TrackRenderer的子類DummyTrackRenderer,SampleSourceTrackRenderer,其中DummyTrackRenderer我們這里用不到,所以直接看SampleSourceTrackRenderer:

@Override
protected final boolean doPrepare(long positionUs) throws ExoPlaybackException {
    boolean allSourcesPrepared = true;
    for (int i = 0; i < sources.length; i++) {
        //這里調用SampleSourceReader.prepare(...)準備資源 :( 層層調用啊,感覺快被繞瘋了
        allSourcesPrepared &= sources[i].prepare(positionUs);
    }
    //如其中有資源無法準備就緒,直接返回false,夠狠
    if (!allSourcesPrepared) {
        return false;
    }
    // The sources are all prepared.
    // 記錄一下所有的軌道個數
    int totalSourceTrackCount = 0;
    for (int i = 0; i < sources.length; i++) {
        totalSourceTrackCount += sources[i].getTrackCount();
    }
    long durationUs = 0;
    int handledTrackCount = 0;
    int[] handledSourceIndices = new int[totalSourceTrackCount];
    int[] handledTrackIndices = new int[totalSourceTrackCount];
    int sourceCount = sources.length;
    // 遍歷所有的軌道
    for (int sourceIndex = 0; sourceIndex < sourceCount; sourceIndex++) {
        SampleSourceReader source = sources[sourceIndex];
        int sourceTrackCount = source.getTrackCount();
        for (int trackIndex = 0; trackIndex < sourceTrackCount; trackIndex++) {
            MediaFormat format = source.getFormat(trackIndex);
            boolean handlesTrack;
            try {
                //判斷是否可以處理該媒體格式的軌道
                handlesTrack = handlesTrack(format);
            } catch (DecoderQueryException e) {
                throw new ExoPlaybackException(e);
            }
            if (handlesTrack) {
                handledSourceIndices[handledTrackCount] = sourceIndex;
                handledTrackIndices[handledTrackCount] = trackIndex;
                handledTrackCount++;
                //獲得軌道的時長
                if (durationUs == TrackRenderer.UNKNOWN_TIME_US) {
                    // We've already encountered a track for which the duration is unknown, so the media
                    // duration is unknown regardless of the duration of this track.
                } else {
                    long trackDurationUs = format.durationUs;
                    if (trackDurationUs == TrackRenderer.UNKNOWN_TIME_US) {
                        durationUs = TrackRenderer.UNKNOWN_TIME_US;
                    } else if (trackDurationUs == TrackRenderer.MATCH_LONGEST_US) {
                        // Do nothing.
                    } else {
                        durationUs = Math.max(durationUs, trackDurationUs);
                    }
                }
            }
        }
    }
    this.durationUs = durationUs;
    //記錄所有可以處理的Source和SourceTrack
    //這塊不知道為什么拆成兩個數組,其實一個二維數組也是可以的
    this.handledSourceIndices = Arrays.copyOf(handledSourceIndices, handledTrackCount);
    this.handledSourceTrackIndices = Arrays.copyOf(handledTrackIndices, handledTrackCount);
    return true;
}

在上面的代碼中,調用的SampleSourceReader的prepare()方法,這里的SampleSourceReader實際是ExtractorSampleSource,其主要從URL、File、Assets等讀取數據做準備,對數據的加載后面會單獨分析。

我們再捋一遍prepare的執行過程:

準備過程時序圖.png

4. Pass the surface to the video renderer.

 player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);

在進行之前,我們有必要講一下MediaCodec這個類,呃,其實官方已經介紹的很清楚了,這個類好復雜呀,我們了解一下工作原理就可以了。

MediaCodec

先看這一張圖,介紹MediaCodec的工作原理,它主要負責兩件事:

  • 輸入外界提供的數據:外界提供一個Input Buffer,發送給MediaCodec處理
  • 輸出合成后的數據:外界獲取由MediaCodec處理過的Output Buffer
支持的數據類型

MediaCodec支持三種數據

  • 壓縮數據
  • 音頻數據
  • 視頻數據

三種數據都支持通過ByteBuffers的方式傳入,但如果傳遞的是視頻數據,需要傳遞一個Surface提高MediaCodec的性能,Surface使用的是原始視頻Buffer,無需轉換或拷貝到ByteBuffers,所以它比較高效。

如果使用Surface,通常情況下無法獲取到二進制數據,但是你可以用ImageReader讀取視頻幀,而如果你使用ByteBuffers,你也可以使用Image類讀取視頻幀。

這是我從別處抄來的一個Demo,具體如何使用MediaCodec播放視頻

現在我們繼續看ExoPlayer的代碼,上面看到,調用者向ExoPlayer發送一個消息,消息類型是MSG_SET_SURFACE并傳入一個surface,這是因為,MediaCodec需要一個Surface將視頻原始Buffer數據直接傳遞給Surface,那這個消息最后的接受者是誰呢?
答案是:MediaCodecVideoTrackRenderer

@Override
public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
    if (messageType == MSG_SET_SURFACE) {
        setSurface((Surface) message);
    } else {
        super.handleMessage(messageType, message);
    }
}

在MediaCodecVideoTrackRenderer中可以找到這個方法,如果消息類型是MSG_SET_SURFACE,則接受并調用setSurface(surface:Surface),否則不處理,到這一步以后的處理就屬于ExoPlayer調用Android系統的MediaCodec的方法了,在上面我們已經講過,不再重復。


到這里,ExoPlayer的結構大致說完了,如果有什么不懂的,也不用問我,其實...我也不會,O(∩_∩)O哈哈~,去看代碼,去看代碼,去看代碼,重要的事情要說三遍......如果單用一篇文章去徹底弄清楚ExoPlayer是不現實的,而且經過我的過濾,可能會丟掉一些很重要的東西,所以我希望這篇文章只是在你學習ExoPlayer過程中的參考,主要的路徑依然是看官方講解和源碼,另外,這篇文章中可能會有錯誤,如果發現,請及時的告訴我,謝謝!


goyourfly - 完成與2016年4月22日
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容