赞
踩
Exoplayer是一个android平台的播放器,支持格式比android系统的mediaplayer更好,确定性更好,mediaplayer是可以进行厂家定制的,各平台一致性比较差,这里简单介绍一下Exoplayer的最基础的使用接口,方便之后阅读源码
播放器一般分为三部分,获取DataSource,解码以及视输出。因为exoplayer解码基本上是通过android系统mediacode实现的,或者一些分必要的插件实现的,代码比较少,输出包括声音输出以及视频渲染,相对来说业务也不是很复杂,所以资源输入进行了多次抽象。主要分成三层
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));
大概就是传入文件地址,得到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);
大概是这样
在这里我们简单的分析一下ProgressiveMediaSource和player的主要流程,
MediaSource最核心的功能有两个
@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); }
这里只是提供了调用接口,最终交给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); }
注意这里是通过builder初始化了比较多的一个default的factory类,主要解决类型定制的问题,这里主要注意一下:
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; } }
这里比较明显的就是提供一个render列表。
2. 核心是:MediaSourceList
3. 核心是: MediaPeriodQueue
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();
}
其实就是通过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;
}
然后通知解码器去处理解码,
其实整体是有两个线程,一个是mediasource负责读取解封装,一个是负责解码渲染。我们大概介绍一下.核心类就是MediaDataSource。具体类图如下:
其核心就是被ExoPlayerImplInternal控制,进行必要的初始化,然后在调用prepare后,启动loader,进行自治,实现自动加载内容,最终把数据缓存在SampleQueue中,被render读取,大概流程
就是ExoPlayerImplInternal调用MediaPerid中的startloading。启动一个loader。然后自治加载解封装内容,大概流程如下:
关于externalLoadable的加载其实也相对比较复杂,但是大概内容就是通过dataSource加载内容,通过Extractor解封装,传入ExtractorOutput,最终保存到sampleQueues中,具体流程这里就不在详细介绍。
我们把目光关注render中的解码以及输出,
这里大概介绍了一下exoplayer的架构,文中跳过不少过程细节,以及状态判断,但是整体流程基本保留,有机会重新整理一下。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。