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類(lèi)之間的繼承關(guān)系,對(duì)這個(gè)框架有一個(gè)大致的印象

基本類(lèi)圖(不完整).png

ExoPlayer被定義為Interface,然后又幾個(gè)內(nèi)部類(lèi):Factory,Listener,其中,F(xiàn)actory負(fù)責(zé)初始化ExoPlayer的操作,其關(guān)鍵代碼如下:

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

Listener則負(fù)責(zé)向外界回調(diào)ExoPlayer狀態(tài)變化和錯(cuò)誤信息。

ExoPlayer有一個(gè)子類(lèi):ExoPlayerImpl,它繼承了ExoPlayer的所有方法,并且負(fù)責(zé)接收轉(zhuǎn)發(fā)外界傳遞的消息,為什么是轉(zhuǎn)發(fā),不是接收呢?因?yàn)檎嬲苫畹牟皇荅xoPlayerImpl,而是另外一個(gè)隱藏類(lèi),ExoPlayerImplInternal,幾乎所有的操作都是在ExoPlayerImplInternal中完成的。

Start

我們看一個(gè)官方的使用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!

我們下面的探索過(guò)程都是按照這個(gè)Demo一步一步進(jìn)行的

1.Instantiate the player.

首先,用戶調(diào)用ExoPlayer.Factory.newInstance(...)方法得到ExoPlayerImpl的實(shí)例,這個(gè)過(guò)程中,我們看看做了什么:

public ExoPlayerImpl(int rendererCount, int minBufferMs, int minRebufferMs) {
    Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
    //首先初始化一些狀態(tài)
    this.playWhenReady = false;
    this.playbackState = STATE_IDLE;
    //ExoPlayer的Listener是通過(guò)andListener(listener:Listener)方法添加的,所以需要一個(gè)數(shù)組去記錄所有的Listener
    this.listeners = new CopyOnWriteArraySet<>();
    //初始化軌道格式數(shù)組
    this.trackFormats = new MediaFormat[rendererCount][];
    //選中的軌道索引
    this.selectedTrackIndices = new int[rendererCount];
    //初始化一個(gè)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);
}

然后我們繼續(xù)看ExoPlayerImplInternal的構(gòu)造方法:

public ExoPlayerImplInternal(Handler eventHandler, boolean playWhenReady,int[] selectedTrackIndices, int minBufferMs, int minRebufferMs) {
    //接受從ExoPlayerImpl傳遞進(jìn)來(lái)的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類(lèi),它是一個(gè)時(shí)鐘類(lèi),原理是通過(guò)獲取手機(jī)啟動(dòng)時(shí)間進(jìn)行差值計(jì)算
    standaloneMediaClock = new StandaloneMediaClock();
    //初始化一個(gè)自增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.
    //初始化和啟動(dòng)一個(gè)HandlerThread
    internalPlaybackThread = new PriorityHandlerThread("ExoPlayerImplInternal:Handler",Process.THREAD_PRIORITY_AUDIO);
    internalPlaybackThread.start();
    //為HandlerThread添加一個(gè)Handler
    handler = new Handler(internalPlaybackThread.getLooper(), this);
}

至此,ExoPlayerImpl和ExoPlayerImplInternal兩個(gè)類(lèi)的狀態(tài)都被初始化,啟動(dòng)一個(gè)Process.THREAD_PRIORITY_AUDIO的線程,準(zhǔn)備接受任務(wù)。

2.Construct renderers.

ExoPlayer被初始化后,用戶需要調(diào)用ExoPlayer.prepare(...)進(jìn)行準(zhǔn)備工作:

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

我們看到,prepare形參是TrackRenderer數(shù)組,那么這個(gè)TrackRenderer是個(gè)什么東東呢?

ExoPlayer的媒體組件,都是通過(guò)注入的方式實(shí)現(xiàn)的,而TrackRenderer就是媒體組件的基類(lèi)。

public abstract class TrackRenderer implements ExoPlayerComponent {}

從源碼看,TrackRenderer是個(gè)抽象類(lèi),繼承自ExoPlayerComponent,只有一個(gè)屬性:

private int state;

大部分方法都是圍繞state實(shí)現(xiàn)的,剩下的都是抽象方法,TrackRenderer類(lèi)用來(lái)維護(hù)state,而具體的工作需要子類(lèi)去實(shí)現(xiàn),而做法比較巧妙,如TrackRenderer的prepare()方法:

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

//抽象方法,由子類(lèi)實(shí)現(xiàn)
protected abstract boolean doPrepare(long positionUs) throws ExoPlaybackException;

再看看ExoPlayerComponent

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

從名字看,它是一個(gè)組件,用于在播放線程接受消息,所有實(shí)現(xiàn)它的類(lèi)都可以在播放線程接受消息,所以TrackRenderer可以接收來(lái)自其他線程的消息。

那我們看TrackRenderer有哪些子類(lèi)

TrackRenderer.png

從類(lèi)圖來(lái)看,TrackRenderer有很多子類(lèi),其中,SampleSourceTrackRenderer比較重要,我們看一下官方文檔對(duì)這個(gè)類(lèi)的介紹:

SampleSourceTrackRenderer.png
SampleSource and SampleSourceReader

TrackRenderer的實(shí)例,渲染來(lái)從SampleSource采集的樣本,SampleSource是什么呢,從名字看應(yīng)該是樣本源:


SampleSource.png

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

再回頭看SampleSourceTrackRenderer的構(gòu)造方法:

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

接受一個(gè)或者多個(gè)SampleSource數(shù)組,然后調(diào)用了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();

從官方介紹來(lái)看,消費(fèi)者(獲取樣本的類(lèi),這里是指SampleSourceTrackRenderer)通過(guò)調(diào)用register()方法來(lái)獲得對(duì)媒體樣本讀取的能力。

register()方法返回SampleSourceReader類(lèi):

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

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

  • 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()

繼續(xù)看SampleSourceTrackRenderer的構(gòu)造方法:

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

SampleSourceTrackRenderer中定義一個(gè)全局變量,存儲(chǔ)所有的SampleSourceReader,方便其他方法訪問(wèn)SampleSource中的資源。

到這里,ExoPlayer的框架結(jié)構(gòu)就比較清晰了,TrackRenderer負(fù)責(zé)渲染由SampleSource提供的媒體樣本。

3. Inject the renderers through prepare.

player.prepare(videoRenderer, audioRenderer);

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

ExoPlayerImpl:

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

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

ExoPlayerImplInternal:

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

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

@Override
public boolean handleMessage(Message msg) {
    try {
        switch (msg.what) {
        ...
        case MSG_PREPARE: {
            //如果消息類(lèi)型是MSG_PREPARE,則調(diào)用下面這個(gè)方法并返回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 {
      //執(zhí)行重置工作,主要有:移除handler消息隊(duì)列中的MSG_DO_SOME_WORD、MSG_INCREMENTAL_PREPARE
      //停止時(shí)鐘,停止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];
        }
      }
      //修改狀態(tài)為STATE_PREPARING
      setState(ExoPlayer.STATE_PREPARING);
      //增量準(zhǔn)備?應(yīng)該是為了復(fù)用代碼進(jìn)行封裝的,具體看里面的代碼吧
      incrementalPrepareInternal();
}

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

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

    if (!prepared) {
        // We're still waiting for some sources to be prepared.
        //如果未準(zhǔn)備成功,則在PREPARE_INTERVAL_MS時(shí)間后重試
        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];
        //獲取每個(gè)TrackRenderer的TrackCount(軌道個(gè)數(shù))
        int rendererTrackCount = renderer.getTrackCount();
        MediaFormat[] rendererTrackFormats = new MediaFormat[rendererTrackCount];
        for (int trackIndex = 0; trackIndex < rendererTrackCount; trackIndex++) {
            rendererTrackFormats[trackIndex] = renderer.getFormat(trackIndex);
        }
        //記錄每個(gè)TrackRenderer的每個(gè)Track的格式
        trackFormats[rendererIndex] = rendererTrackFormats;
        if (rendererTrackCount > 0) {
            //如果時(shí)間為未知時(shí)間,則不作處理,(這塊沒(méi)有太看懂,為什么上一個(gè)TrackRenderer的durationUs作為這個(gè)TrackRenderer的判斷依據(jù),
            // 如果上一個(gè)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 {
                    //如果上一個(gè)TrackRenderer的Duration和這個(gè)TrackRenderer的Duration不一致,則取大的
                    durationUs = Math.max(durationUs, trackDurationUs);
                }
            }
            //selectedTrackIndices是從ExoPlayerImpl傳入的,每個(gè)Renderer同時(shí)只能有一個(gè)track在工作
            int trackIndex = selectedTrackIndices[rendererIndex];
            if (0 <= trackIndex && trackIndex < rendererTrackFormats.length) {
                //打開(kāi)對(duì)應(yīng)的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,更改狀態(tài)
    // 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發(fā)送 MSG_DO_SOME_WORK 命令
    handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}

緊接著又向handler發(fā)送MSG_DO_SOME_WORK命令:

private void doSomeWork() throws ExoPlaybackException {
    TraceUtil.beginSection("doSomeWork");
    //獲取系統(tǒng)啟動(dòng)時(shí)間
    long operationStartTimeMs = SystemClock.elapsedRealtime();
    //緩存位置,默認(rèn)值為durationUs 或 Long.MAX_VALUE
    long bufferedPositionUs = durationUs != TrackRenderer.UNKNOWN_TIME_US ? durationUs
            : Long.MAX_VALUE;
    boolean allRenderersEnded = true;
    boolean allRenderersReadyOrEnded = true;
    //刷新播放時(shí)間
    updatePositionUs();
    //遍歷所有開(kāi)啟的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.
        //調(diào)用TrackRenderer的doSomeWork,這個(gè)后面再說(shuō)
        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 {
            //獲取實(shí)際的緩存位置
            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 {
                //一般情況會(huì)進(jìn)入這個(gè)條件
                bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs);
            }
        }
    }
    //刷新全局緩存位置
    this.bufferedPositionUs = bufferedPositionUs;

    //如果所有的Renderer都是結(jié)束狀態(tài),或者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) {
        //如果狀態(tài)是STATE_BUFFERING,但是所有的Renderer已經(jīng)準(zhǔn)備就緒,則開(kāi)始渲染,并將狀態(tài)改為STATE_READY
        setState(ExoPlayer.STATE_READY);
        if (playWhenReady) {
            startRenderers();
        }
    } else if (state == ExoPlayer.STATE_READY && !allRenderersReadyOrEnded) {
        //如果狀態(tài)已經(jīng)是READY,但不是allRenderersReadyOrEnded,則當(dāng)前視頻正在緩存,停止渲染,等待緩存
        rebuffering = playWhenReady;
        setState(ExoPlayer.STATE_BUFFERING);
        stopRenderers();
    }

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

    TraceUtil.endSection();
}

當(dāng)prepare調(diào)用doSomeWork()之后,在整個(gè)播放期間,doSomeWork()會(huì)一直重復(fù)執(zhí)行。

看完ExoPlayerImplInternal類(lèi),我們?cè)偃rackRenderer看一看:

/**
 * 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的狀態(tài),具體執(zhí)行交給了子類(lèi),上面介紹過(guò)的TrackRenderer的子類(lèi)DummyTrackRenderer,SampleSourceTrackRenderer,其中DummyTrackRenderer我們這里用不到,所以直接看SampleSourceTrackRenderer:

@Override
protected final boolean doPrepare(long positionUs) throws ExoPlaybackException {
    boolean allSourcesPrepared = true;
    for (int i = 0; i < sources.length; i++) {
        //這里調(diào)用SampleSourceReader.prepare(...)準(zhǔn)備資源 :( 層層調(diào)用啊,感覺(jué)快被繞瘋了
        allSourcesPrepared &= sources[i].prepare(positionUs);
    }
    //如其中有資源無(wú)法準(zhǔn)備就緒,直接返回false,夠狠
    if (!allSourcesPrepared) {
        return false;
    }
    // The sources are all prepared.
    // 記錄一下所有的軌道個(gè)數(shù)
    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++;
                //獲得軌道的時(shí)長(zhǎng)
                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
    //這塊不知道為什么拆成兩個(gè)數(shù)組,其實(shí)一個(gè)二維數(shù)組也是可以的
    this.handledSourceIndices = Arrays.copyOf(handledSourceIndices, handledTrackCount);
    this.handledSourceTrackIndices = Arrays.copyOf(handledTrackIndices, handledTrackCount);
    return true;
}

在上面的代碼中,調(diào)用的SampleSourceReader的prepare()方法,這里的SampleSourceReader實(shí)際是ExtractorSampleSource,其主要從URL、File、Assets等讀取數(shù)據(jù)做準(zhǔn)備,對(duì)數(shù)據(jù)的加載后面會(huì)單獨(dú)分析。

我們?cè)俎垡槐閜repare的執(zhí)行過(guò)程:

準(zhǔn)備過(guò)程時(shí)序圖.png

4. Pass the surface to the video renderer.

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

在進(jìn)行之前,我們有必要講一下MediaCodec這個(gè)類(lèi),呃,其實(shí)官方已經(jīng)介紹的很清楚了,這個(gè)類(lèi)好復(fù)雜呀,我們了解一下工作原理就可以了。

MediaCodec

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

  • 輸入外界提供的數(shù)據(jù):外界提供一個(gè)Input Buffer,發(fā)送給MediaCodec處理
  • 輸出合成后的數(shù)據(jù):外界獲取由MediaCodec處理過(guò)的Output Buffer
支持的數(shù)據(jù)類(lèi)型

MediaCodec支持三種數(shù)據(jù)

  • 壓縮數(shù)據(jù)
  • 音頻數(shù)據(jù)
  • 視頻數(shù)據(jù)

三種數(shù)據(jù)都支持通過(guò)ByteBuffers的方式傳入,但如果傳遞的是視頻數(shù)據(jù),需要傳遞一個(gè)Surface提高M(jìn)ediaCodec的性能,Surface使用的是原始視頻Buffer,無(wú)需轉(zhuǎn)換或拷貝到ByteBuffers,所以它比較高效。

如果使用Surface,通常情況下無(wú)法獲取到二進(jìn)制數(shù)據(jù),但是你可以用ImageReader讀取視頻幀,而如果你使用ByteBuffers,你也可以使用Image類(lèi)讀取視頻幀。

這是我從別處抄來(lái)的一個(gè)Demo,具體如何使用MediaCodec播放視頻

現(xiàn)在我們繼續(xù)看ExoPlayer的代碼,上面看到,調(diào)用者向ExoPlayer發(fā)送一個(gè)消息,消息類(lèi)型是MSG_SET_SURFACE并傳入一個(gè)surface,這是因?yàn)?,MediaCodec需要一個(gè)Surface將視頻原始Buffer數(shù)據(jù)直接傳遞給Surface,那這個(gè)消息最后的接受者是誰(shuí)呢?
答案是: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中可以找到這個(gè)方法,如果消息類(lèi)型是MSG_SET_SURFACE,則接受并調(diào)用setSurface(surface:Surface),否則不處理,到這一步以后的處理就屬于ExoPlayer調(diào)用Android系統(tǒng)的MediaCodec的方法了,在上面我們已經(jīng)講過(guò),不再重復(fù)。


到這里,ExoPlayer的結(jié)構(gòu)大致說(shuō)完了,如果有什么不懂的,也不用問(wèn)我,其實(shí)...我也不會(huì),O(∩_∩)O哈哈~,去看代碼,去看代碼,去看代碼,重要的事情要說(shuō)三遍......如果單用一篇文章去徹底弄清楚ExoPlayer是不現(xiàn)實(shí)的,而且經(jīng)過(guò)我的過(guò)濾,可能會(huì)丟掉一些很重要的東西,所以我希望這篇文章只是在你學(xué)習(xí)ExoPlayer過(guò)程中的參考,主要的路徑依然是看官方講解和源碼,另外,這篇文章中可能會(huì)有錯(cuò)誤,如果發(fā)現(xiàn),請(qǐng)及時(shí)的告訴我,謝謝!


goyourfly - 完成與2016年4月22日
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容