项目记录: Exoplayer备忘录1

项目记录: Exoplayer备忘录1

一体式头盔全景播放器开发是Unity-Android协作完成的.

Android端采用的Google的Exoplayer作为框架.

重点通过学习了以下几篇博客,对Exoplayer有了一个初步的认识.做一下记录

1. Exoplayer简介

ExoPlayer是建立在 Android low-level api之上的 app级开源播放器。开源项目包含 ExoPlayer库和demo。 它用Java实现了解封装,用MediaCodec实现 硬件解码

  • ExoPlayer库 - 类库源代码

  • Demo - 演示类库的使用

ExoPlayer相较于MediaPlayer有很多优点:

  • 支持 Dynamic Adaptive Streaming over HTTP (DASH) 和SmoothStreaming,更多支持请参阅支持的格式)详细信息页面。
  • 支持高级 HLS (HTTP Live Streaming)功能,如正确处理 #EXT-X-DISCONTINUITY的标签。
  • 能够无缝融合,串联和循环媒体资源。
  • 支持定制和扩展,ExoPlayer是考虑到这一点而设计的,并允许许多部件与定制实现替换。
  • 更新起来更方便
  • 设备通用性更强
  • 支持在Android 4.3(API级别18)和更高的Widevine通用加密。

2. Library Overview

ExoPlayer库的核心是ExoPlayer接口。

ExoPlayer 接口暴露了传统的 high-level 播放器中的功能,如资源缓冲,播放,暂停和拖拽等。接口的实现类对媒体的播放类型、存储位置和渲染方式做出假设,而不是笼统的加载和渲染。

Exoplayer 把播放类型、存储位置和渲染方式等任务委派给不同的部件,然后在创建播放器或后台播放的时候把这些部件注入。 这些部件包括:

  • MediaSource - 负责装载 media,装载到MediaSource 的 media 可以被读取,MediaSource 在调用 ExoPlayer.prepare 方法时被注入。
  • Render S - 用于渲染 media 的部件,在创建播放器时被注入
  • TrackSelector - 从MediaSource 中选出 media 提供给可用的 Render S 来渲染,在创建播放器时被注入。
  • LoadControl - 控制 MediaSource 缓存更多的 media,有多少 media 被缓冲。在创建播放器时被注入。

类库提供这些部件在通常情况下的默认实现.一个 ExoPlayer 可以利用这些部件.

如果标准实现不能满足需求,也可以使用自定义实现

举例:

  • ExoPlayer提供默认的音频和视频渲染器,利用了Android框架中的MediaCodec和AudioTrack类。这两个都需要一个SampleSource对象中注入,用来实现媒体示例的播放。
  • 组件的注入在当前ExoPlayer库中是普遍存在的。
  • 下图展示了使用一个ExoPlayer来配置和播放MP4媒体流的高级对象模型。
  • 默认的音频和视频渲染器已经被注解到ExoPlayer中。一个叫ExtractorSampleSource类的实现被注解到渲染器中用于提供简单的媒体播放功能。DataSource和Extractor示例被注解到ExtractorSampleSource来支持加载媒体流和在被加载的数据中提取样板。在这个示例中DefaultUriDataSource和Mp4Extractor被用于播放从URIs中导入的MP4流。

img

3. Demo上层调用

上层调用方式基本为:

PlayerActivity -> DemoPlayer -> ExoPlayer 

PlayerActivity -> RendererBuilder -> ExtractorRendererBuilder 

类图为:

这里写图片描述

  • 其中,PlayerActivity是面向UI层的.一方面是控制播放器Demoplayer.一方面是选择了Renderer.这里的Renderer指定了数据源的格式,解码方式和缓冲区大小等等.
  • 缓冲区的大小指的是 RollingSampleBuffer的大小 不会影响播放的速度,只会 影响缓存数据的最大值.
  • Exoplayer则是媒体API的接口
  • DemoPlayer中直接封装了ExoPlayer和相关的回调接口,负责播放器的逻辑控制和传入 SurfaceView 等操作, 而非播放器内部的原理.

通过时序图来说明Demo中几个类的调用和封装方式

img

4. 代码结构

  • 以播放器本地视频的代码结构展开
ExoPlayer ->ExoPlayerImpl -> ExoPlayerImplInternal -> TrackRenderer 

MediaCodecVideoTrackRenderer & MediaCodecAudioTrackRenderer ->  MediaCodecTrackRenderer -> SampleSourceTrackRenderer -> SampleSource,SampleSourceReader 

ExtractorSampleSource -> DataSource & Extractor & Loader 

img

  • ExoPlayer为接口。ExoPlayerImpl为实现,实现的一些详细步骤在ExoPlayerImplInternal中。后者用 Handler消息机制 进行异步通信,必要时会阻塞。
  • TrackRenderer 是渲染器的接口
  • MediaCodecTrackRenderer 中加入了 MediaCodec (Android硬解码)。ExoPlayer用的是硬解,并且要求4.1以上Android系统。
  • SampleSourceTrackRenderer 中调用了 SampleSource , SampleSourceReader 的接口. SampleSource 在这里指的是解封装后的媒体数据。
  • ExtractorSampleSource 相当于一个核心控制器,它实现了SampleSource和SampleSourceReader接口。它通过实际的控制线程 Loader,把从某DataSource即数据源中传过来的原始数据,传递给某Extractor来解封装。原始数据解析成SampleSource后,储存在RollingSampleBuffer即环形缓冲区中。
  • MediaCodecTrackRenderer 会间接通过ExtractorSampleSource 间接从 RollingSampleBuffer 中读取数据并渲染成画面,显示到SurfaceView中。

流程图如下所示:

这里写图片描述

5. 代码原理

ExoPlayer -> ExoPlayerImpl -> ExoPlayerImplInternal

img

通过以下这段代码 ExoPlayerImpl的构造函数.可以看出,ExoplayerImpl中持有一个 ExoplayerImpl 对象来控制播放器 . 在创建播放器的时候,传入了一个 eventHandler 对象,把底层的错误信息和状态信息传递给上层.

// ExoPlayerImpl类中构造方法:

eventHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        ExoPlayerImpl.this.handleEvent(msg);
    }
};
internalPlayer = new ExoPlayerImplInternal(eventHandler, playWhenReady, selectedTrackIndices,
                                           minBufferMs, minRebufferMs);

具体的功能性代码,都在 ExoPlayerImplInternal 中实现.状态改变信息和错误信息会通过eventHandler传上来进行处理.

// ExoPlayerImpl类:

// Not private so it can be called from an inner class without going through
// a thunk method.
/* package */ void handleEvent(Message msg) {
    switch (msg.what) {
        case ExoPlayerImplInternal.MSG_PREPARED: {
            System.arraycopy(msg.obj, 0, trackFormats, 0, trackFormats.length);
            playbackState = msg.arg1;
            for (Listener listener : listeners) {
                listener.onPlayerStateChanged(playWhenReady, playbackState);
            }
            break;
        }
        case ExoPlayerImplInternal.MSG_STATE_CHANGED: {
            playbackState = msg.arg1;
            for (Listener listener : listeners) {
                listener.onPlayerStateChanged(playWhenReady, playbackState);
            }
            break;
        }
        case ExoPlayerImplInternal.MSG_SET_PLAY_WHEN_READY_ACK: {
            pendingPlayWhenReadyAcks--;
            if (pendingPlayWhenReadyAcks == 0) {
                for (Listener listener : listeners) {
                    listener.onPlayWhenReadyCommitted();
                }
            }
            break;
        }
        case ExoPlayerImplInternal.MSG_ERROR: {
            ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
            for (Listener listener : listeners) {
                listener.onPlayerError(exception);
            }
            break;
        }
    }
}
  • 这里的listeners是一个CopyOnWriteArrayList,里面的对象都是Listener,这里用的是一个观察者模式,用于给上层监听回调消息。上层即DemoPlayer或是EventLogger都在这里注册或注销监听。

ExoPlayerImplInternal -> TrackRenderer -> SampleSource,SampleSourceReader -> ExtractorSampleSource

img

1) ExoplayerImplInternal中的消息机制

// ExoPlayerImplInternal类中构造方法:

internalPlaybackThread = new PriorityHandlerThread(getClass().getSimpleName() + ":Handler",
                                                   Process.THREAD_PRIORITY_AUDIO);
internalPlaybackThread.start();
handler = new Handler(internalPlaybackThread.getLooper(), this);

ExoplayerImplInternal 实现了 Handler.Callback 接口

// ExoPlayerImplInternal类: 消息处理

@Override
public boolean handleMessage(Message msg) {
    try {
        switch (msg.what) {
        case MSG_PREPARE: {
            prepareInternal((TrackRenderer[]) msg.obj);
            return true;
        }
        case MSG_INCREMENTAL_PREPARE: {
            incrementalPrepareInternal();
            return true;
        }
        case MSG_SET_PLAY_WHEN_READY: {
            setPlayWhenReadyInternal(msg.arg1 != 0);
            return true;
        }
        case MSG_DO_SOME_WORK: {
            doSomeWork();
            return true;
        }
        case MSG_SEEK_TO: {
            seekToInternal(Util.getLong(msg.arg1, msg.arg2));
            return true;
        }
        case MSG_STOP: {
            stopInternal();
            return true;
        }
        case MSG_RELEASE: {
            releaseInternal();
            return true;
        }
        case MSG_CUSTOM: {
            sendMessageInternal(msg.arg1, msg.obj);
            return true;
        }
        case MSG_SET_RENDERER_SELECTED_TRACK: {
            setRendererSelectedTrackInternal(msg.arg1, msg.arg2);
            return true;
        }
        default:
            return false;
        }
    } 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;
    }
}
  • 通过这段代码,可以看出来,在 ExoPlayerImplInternal 内部是通过消息来控制播放器逻辑(控制 TrackRenderer )。

2) doSomeWork分析及作用

  • doSomeWork方法是在播放器执行完prepare后执行的。是在准备动作都完成后,具体控制播放器开始渲染画面的方法。

  • 主要的动作有:

    1. 更新positionUs(以及elapsedRealtimeUs)
    2. renderer.doSomeWork 调用渲染器进行渲染
    3. 把播放状态回调上层
    4. 定时执行下一次doSomeWork (10ms?)
    
// ExoPlayerImplInternal类:doSomeWork

private void doSomeWork() throws ExoPlaybackException {
    TraceUtil.beginSection("doSomeWork");
    long operationStartTimeMs = SystemClock.elapsedRealtime();
    long bufferedPositionUs = durationUs != TrackRenderer.UNKNOWN_TIME_US ? durationUs : Long.MAX_VALUE;
    boolean allRenderersEnded = true;
    boolean allRenderersReadyOrEnded = true;
    updatePositionUs();// 笔记:更新positionUs
    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.

        // 笔记:这里调用了renderer的doSomeWork方法并传入了positionUs,
        //      elapsedRealtimeUs是个独立的系统时间参考
        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;

    // 笔记:根据durationUs和positionUs来判断状态和开关渲染器(Renderer)
    if (allRenderersEnded && (durationUs == TrackRenderer.UNKNOWN_TIME_US || durationUs <= positionUs)) {
        setState(ExoPlayer.STATE_ENDED);
        stopRenderers();
    } else if (state == ExoPlayer.STATE_BUFFERING && allRenderersReadyOrEnded) {
        setState(ExoPlayer.STATE_READY);
        if (playWhenReady) {
            startRenderers();
        }
    } else if (state == ExoPlayer.STATE_READY && !allRenderersReadyOrEnded) {
        rebuffering = playWhenReady;
        setState(ExoPlayer.STATE_BUFFERING);
        stopRenderers();
    }

    // 笔记:准备再次调用doSomework
    handler.removeMessages(MSG_DO_SOME_WORK);
    if ((playWhenReady && state == ExoPlayer.STATE_READY) || state == ExoPlayer.STATE_BUFFERING) {
        scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, RENDERING_INTERVAL_MS);
    } else if (!enabledRenderers.isEmpty()) {
        scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, IDLE_INTERVAL_MS);
    }

    TraceUtil.endSection();
}

private void scheduleNextOperation(int operationType, long thisOperationStartTimeMs, long intervalMs) {
    long nextOperationStartTimeMs = thisOperationStartTimeMs + intervalMs;
    long nextOperationDelayMs = nextOperationStartTimeMs - SystemClock.elapsedRealtime();
    if (nextOperationDelayMs <= 0) {
        handler.sendEmptyMessage(operationType);
    } else {
        handler.sendEmptyMessageDelayed(operationType, nextOperationDelayMs);
    }
}

// 笔记:通过上层传入的eventHandler把状态改变信息传递给上层
private void setState(int state) {
    if (this.state != state) {
        this.state = state;
        eventHandler.obtainMessage(MSG_STATE_CHANGED, state, 0).sendToTarget();
    }
}

3) updataPositionUs和renderer.doSomeWork分析

  • positionUs指的是实际渲染位置。
  • 通过这段在ExoPlayerImplInternal类中的代码,看出,这有两个分支,第一个分支主要是用于有音频的情况下,音频时间可以作为整体参考时间,来调整positionUs。第二个分支是没有音频的情况下,用系统独立时钟作为整体参考时间,来调整positionUs。
// ExoPlayerImplInternal类:updatePositionUs

private void updatePositionUs() {
    if (rendererMediaClock != null && enabledRenderers.contains(rendererMediaClockSource)
        && !rendererMediaClockSource.isEnded()) {
        positionUs = rendererMediaClock.getPositionUs();
        standaloneMediaClock.setPositionUs(positionUs);
    } else {
        positionUs = standaloneMediaClock.getPositionUs();
    }
    elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
}
  • positionUs传递给了drainOutputBuffer方法和feedInputBuffer方法。用于调整播放时间,和获取缓冲帧。
MediaCodecTrackRenderer类:

@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
    // 笔记:判断是否应该继续缓冲
    sourceState = continueBufferingSource(positionUs)
            ? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState) : SOURCE_STATE_NOT_READY;
    // 笔记:判断解码是否连续,如果不连续,则重启解码器
    checkForDiscontinuity(positionUs);
    if (format == null) {
        // 笔记:读取格式
        readFormat(positionUs);
    }
    if (codec == null && shouldInitCodec()) {
        // 笔记:当有格式无解码器时,开启解码器
        maybeInitCodec();
    }
    if (codec != null) {
        TraceUtil.beginSection("drainAndFeed");
        // 笔记:如果解码器中可以输出缓冲,则会返回true,否则返回false
        while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {
        }
        // 笔记:如果解码器还可以输入原始帧,则返回true,否则返回false,第二个参数代表是否首次执行
        if (feedInputBuffer(positionUs, true)) {
            while (feedInputBuffer(positionUs, false)) {
            }
        }
        TraceUtil.endSection();
    }
    codecCounters.ensureUpdated();
}
  • drainOutputBuffer 方法调用到了 processOutputBuffer 方法,这里处理缓冲帧。这个方法在 MediaCodecTrackRenderer 类中是个抽象方法,具体实现在 MediaCodecVideoTrackRendererMediaCodecAudioTrackRenderer 类中。
// MediaCodecVideoTrackRenderer类:

// 笔记:返回true意味着输出的缓冲帧已经被渲染,false意味着尚未被渲染
@Override
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, ByteBuffer buffer,
        MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip) {
    if (shouldSkip) {
        skipOutputBuffer(codec, bufferIndex);
        return true;
    }

    if (!renderedFirstFrame) {
        if (Util.SDK_INT >= 21) {
            renderOutputBufferV21(codec, bufferIndex, System.nanoTime());
        } else {
            renderOutputBuffer(codec, bufferIndex);
        }
        return true;
    }

    if (getState() != TrackRenderer.STATE_STARTED) {
        return false;
    }

    // Compute how many microseconds it is until the buffer's presentation
    // time.
    long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs;
    long earlyUs = bufferInfo.presentationTimeUs - positionUs - elapsedSinceStartOfLoopUs;

    // Compute the buffer's desired release time in nanoseconds.
    long systemTimeNs = System.nanoTime();
    long unadjustedFrameReleaseTimeNs = systemTimeNs + (earlyUs * 1000);

    // Apply a timestamp adjustment, if there is one.
    long adjustedReleaseTimeNs = frameReleaseTimeHelper.adjustReleaseTime(bufferInfo.presentationTimeUs,
            unadjustedFrameReleaseTimeNs);
    earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;

    // 笔记:以上是通过positionUs(实际渲染位置),elapsedRealtimeUs(独立时钟位置),
    //      bufferInfo.presentationTimeUs(缓冲帧位置)得出缓冲位置和播放位置之间的时间差值。

    // 笔记:如果渲染位置在此缓冲帧位置后面30ms,则弃掉此帧
    if (earlyUs < -30000) {
        // We're more than 30ms late rendering the frame.
        dropOutputBuffer(codec, bufferIndex);
        return true;
    }

    if (Util.SDK_INT >= 21) {
        // 笔记:如果系统api在21以上,则可以在framework层控制渲染速度
        // Let the underlying framework time the release.
        // 笔记:如果渲染位置在缓冲帧位置50毫秒之前,就return false。否则则渲染。
        if (earlyUs < 50000) {
            renderOutputBufferV21(codec, bufferIndex, adjustedReleaseTimeNs);
            return true;
        }
    } else {
        // 笔记:如果系统api在21以下,我们需要自己控制渲染速度
        // We need to time the release ourselves.
        if (earlyUs < 30000) {
            // 笔记:如果渲染位置和缓冲帧位置之差在30毫秒和11毫秒之间,则推迟至少1毫秒再渲染。
            //      如果在11毫秒以内,则直接渲染。
            if (earlyUs > 11000) {
                // We're a little too early to render the frame. Sleep until
                // the frame can be rendered.
                // Note: The 11ms threshold was chosen fairly arbitrarily.
                try {
                    // Subtracting 10000 rather than 11000 ensures the sleep
                    // time will be at least 1ms.
                    Thread.sleep((earlyUs - 10000) / 1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            renderOutputBuffer(codec, bufferIndex);
            return true;
        }
    }

    // We're either not playing, or it's not time to render the frame yet.
    // 笔记:return false的意思是,我们既不播放,而且也不渲染这帧。
    return false;
}
  • 在renderOutputBuffer中,通过releaseOutputBuffer方法把相关帧播放到surface中。
codec.releaseOutputBuffer(bufferIndex, true);
  • 在feedInputBuffer中, 通过readSource,调用到了ExtractorSampleSource中的readData方法,从rollingBuffer中取到了数据。
result = readSource(positionUs, formatHolder, sampleHolder, false);

注意! 如果positionUs获取错误的话,那么会直接影响到播放流程中从缓冲区获取数据和解码器渲染数据等功能。

ExtractorSampleSource -> DataSource & Extractor & Loader

img

1) ExtractingLoadable 分析

ExtractingLoadable 是一个 ExtractorSampleSource 的内部类.它实现了 Loadable 接口.

Loadable 接口应用于Loader,后者是一个异步线程。在这里主要用于从DataSource 数据源中获取数据放进 RollingSampleBuffer 即缓冲区中。

// 这个异步线程的主要动作是:
1. dataSource.open,即打开数据源
2. Extractor extractor = extractorHolder.selectExtractor(input),选择正确的文件封装拆解器
3. result = extractor.read(input, positionHolder),从数据源中读取数据
4. dataSource.close,关闭数据源

具体代码如下所示:

/**
 * Loads the media stream and extracts sample data from it.
 */
private static class ExtractingLoadable implements Loadable {

    private final Uri uri;
    private final DataSource dataSource;
    private final ExtractorHolder extractorHolder;
    private final Allocator allocator;
    private final int requestedBufferSize;
    private final PositionHolder positionHolder;

    private volatile boolean loadCanceled;

    private boolean pendingExtractorSeek;

    public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder, Allocator allocator,
                              int requestedBufferSize, long position) {
        this.uri = Assertions.checkNotNull(uri);
        this.dataSource = Assertions.checkNotNull(dataSource);
        this.extractorHolder = Assertions.checkNotNull(extractorHolder);
        this.allocator = Assertions.checkNotNull(allocator);
        this.requestedBufferSize = requestedBufferSize;
        positionHolder = new PositionHolder();
        positionHolder.position = position;
        pendingExtractorSeek = true;
    }

    // 笔记:用于控制线程的关闭
    @Override
    public void cancelLoad() {
        loadCanceled = true;
    }

    @Override
    public boolean isLoadCanceled() {
        return loadCanceled;
    }

    @Override
    public void load() throws IOException, InterruptedException {
        int result = Extractor.RESULT_CONTINUE;
        while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
            ExtractorInput input = null;
            try {
                long position = positionHolder.position;
                // 笔记:开打数据源,这里C.LENGTH_UNBOUNDED值为-1
                long length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNBOUNDED, null));
                if (length != C.LENGTH_UNBOUNDED) {
                    length += position;
                }
                // 笔记:这里的ExtractorInput是一个对于数据源、读取位置、读取长度的封装
                //      用于向Extractor输入数据
                input = new DefaultExtractorInput(dataSource, position, length);
                // 笔记:通过数据选择正确的Extractor即文件封装拆解器
                Extractor extractor = extractorHolder.selectExtractor(input);
                if (pendingExtractorSeek) {
                    extractor.seek();
                    pendingExtractorSeek = false;
                }
                // 笔记:这个循环用于从Extractor中不断读取数据,放进RollingSampleBuffer中
                while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
                    allocator.blockWhileTotalBytesAllocatedExceeds(requestedBufferSize);
                    result = extractor.read(input, positionHolder);
                    // TODO: Implement throttling to stop us from buffering
                    // data too often.
                }
            } finally {
                if (result == Extractor.RESULT_SEEK) {
                    result = Extractor.RESULT_CONTINUE;
                } else if (input != null) {
                    positionHolder.position = input.getPosition();
                }
                // 笔记:关闭数据源
                dataSource.close();
            }
        }
    }
}

2) ExtractorHolder分析

  • ExtractorHolder 也是一个 ExtractorSampleSource 中的内部类。它主要负责持有Extractor。
// ExtractorHolder类:

public Extractor selectExtractor(ExtractorInput input)
    throws UnrecognizedInputFormatException, IOException, InterruptedException {
    if (extractor != null) {
        return extractor;
    }
    for (Extractor extractor : extractors) {
        try {
            // 笔记:一旦识别到正确的解析器,则会返回true
            if (extractor.sniff(input)) {
                this.extractor = extractor;
                break;
            }
        } catch (EOFException e) {
            // Do nothing.
        }
        input.resetPeekPosition();
    }
    // 笔记:如果没有适配的解析器,则会抛出异常
    if (extractor == null) {
        throw new UnrecognizedInputFormatException(extractors);
    }
    // 笔记:这里调用了extractor.init即初始化
    extractor.init(extractorOutput);
    return extractor;
}

3) Extractor分析

  • Extractor是个接口,表示文件封装解析器。里面主要有四个方法:
void init(ExtractorOutput output);

boolean sniff(ExtractorInput input) throws IOException, InterruptedException;

int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException;

void seek();
  • read 方法是阻塞的。每次调用read只会获取一小部分数据。 同时这里定义了三个read方法的特殊返回值:

  • RESULT_CONTINUE = 0; //表示需要继续读取数据
    RESULT_SEEK = 1; //表示需要重新定位数据
    RESULT_END_OF_INPUT = C.RESULT_END_OF_INPUT; //表示已经读取结束
    
  • 通过 Extractor 的实现类我们可以找到,当调用 read 方法时,都会调到 trackOutput.sampleData 方法。 这个方法表示输出解封装后的帧。具体就是把解封装的帧存入 RollingSampleBuffer 中,在 TrackOutput 的实现类 DefaultTrackOutput 中的如下代码可以印证这一点:

    @Override
    public void sampleData(ParsableByteArray buffer, int length) {
        rollingBuffer.appendData(buffer, length);
    }
    

参考文献

Google Exoplayer之全面认识

Android 异步通信:图文详解Handler机制工作原理

Exoplayer备忘录

猜你喜欢

转载自blog.csdn.net/qjh5606/article/details/84974199