Improve SimpleExoPlayer flexibility

- Allow extension and overriding of renderer creation.
  Several developers have asked for this, so that they
  can use their own renderers (typically extensions to
  the core ones) without losing the ability to use
  SimpleExoPlayer.
- Add option to not attempt extension renderer creation,
  for efficiency.
- Align build variants for internal and external demo
  apps. This is slightly unfortunate, but convergence
  seems necessary for useExtensionRenderers.
- Fix DASH playback tests to use the debug video
  renderer.

Issue #2102

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=140140915
This commit is contained in:
olly 2016-11-24 09:08:10 -08:00 committed by Oliver Woodman
parent cbf5988803
commit 894ae1a310
9 changed files with 358 additions and 176 deletions

View File

@ -37,16 +37,23 @@ android {
abortOnError false
}
flavorDimensions "extensions"
productFlavors {
noExtensions
withExtensions
noExtns {
dimension "extensions"
}
extns {
dimension "extensions"
}
}
}
dependencies {
compile project(':library')
withExtensionsCompile project(path: ':extension-ffmpeg')
withExtensionsCompile project(path: ':extension-flac')
withExtensionsCompile project(path: ':extension-opus')
withExtensionsCompile project(path: ':extension-vp9')
extnsCompile project(path: ':extension-ffmpeg')
extnsCompile project(path: ':extension-flac')
extnsCompile project(path: ':extension-opus')
extnsCompile project(path: ':extension-vp9')
}

View File

@ -36,13 +36,19 @@ public class DemoApplication extends Application {
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
}
DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
public DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
return new DefaultDataSourceFactory(this, bandwidthMeter,
buildHttpDataSourceFactory(bandwidthMeter));
}
HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
public HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter);
}
public boolean useExtensionRenderers() {
// We should return BuildConfig.FLAVOR_extensions.equals("extns") here, but this is currently
// incompatible with a Google internal build system.
return true;
}
}

View File

@ -251,12 +251,17 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
}
}
@SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode =
((DemoApplication) getApplication()).useExtensionRenderers()
? (preferExtensionDecoders ? SimpleExoPlayer.EXTENSION_RENDERER_MODE_PREFER
: SimpleExoPlayer.EXTENSION_RENDERER_MODE_ON)
: SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF;
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER);
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(),
drmSessionManager, preferExtensionDecoders);
drmSessionManager, extensionRendererMode);
player.addListener(this);
eventLogger = new EventLogger(trackSelector);

View File

@ -49,7 +49,7 @@ public final class ExoPlayerFactory {
/**
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
* {@link Looper}.
* {@link Looper}. Available extension renderers are not used.
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
@ -59,7 +59,8 @@ public final class ExoPlayerFactory {
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager, false);
return newSimpleInstance(context, trackSelector, loadControl,
drmSessionManager, SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF);
}
/**
@ -71,15 +72,15 @@ public final class ExoPlayerFactory {
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param preferExtensionDecoders True to prefer {@link Renderer} instances defined in
* available extensions over those defined in the core library. Note that extensions must be
* included in the application build for setting this flag to have any effect.
* @param extensionRendererMode The extension renderer mode, which determines if and how available
* extension renderers are used. Note that extensions must be included in the application
* build for them to be considered available.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean preferExtensionDecoders) {
@SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode) {
return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager,
preferExtensionDecoders, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS);
extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS);
}
/**
@ -91,17 +92,18 @@ public final class ExoPlayerFactory {
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param preferExtensionDecoders True to prefer {@link Renderer} instances defined in
* available extensions over those defined in the core library. Note that extensions must be
* included in the application build for setting this flag to have any effect.
* @param extensionRendererMode The extension renderer mode, which determines if and how available
* extension renderers are used. Note that extensions must be included in the application
* build for them to be considered available.
* @param allowedVideoJoiningTimeMs The maximum duration for which a video renderer can attempt to
* seamlessly join an ongoing playback.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
@SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) {
return new SimpleExoPlayer(context, trackSelector, loadControl, drmSessionManager,
preferExtensionDecoders, allowedVideoJoiningTimeMs);
extensionRendererMode, allowedVideoJoiningTimeMs);
}
/**

View File

@ -21,6 +21,7 @@ import android.graphics.SurfaceTexture;
import android.media.MediaCodec;
import android.media.PlaybackParams;
import android.os.Handler;
import android.support.annotation.IntDef;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
@ -45,6 +46,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
@ -54,7 +57,7 @@ import java.util.List;
* be obtained from {@link ExoPlayerFactory}.
*/
@TargetApi(16)
public final class SimpleExoPlayer implements ExoPlayer {
public class SimpleExoPlayer implements ExoPlayer {
/**
* A listener for video rendering information from a {@link SimpleExoPlayer}.
@ -88,8 +91,33 @@ public final class SimpleExoPlayer implements ExoPlayer {
}
/**
* Modes for using extension renderers.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, EXTENSION_RENDERER_MODE_PREFER})
public @interface ExtensionRendererMode {}
/**
* Do not allow use of extension renderers.
*/
public static final int EXTENSION_RENDERER_MODE_OFF = 0;
/**
* Allow use of extension renderers. Extension renderers are indexed after core renderers of the
* same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore
* prefer to use a core renderer to an extension renderer in the case that both are able to play
* a given track.
*/
public static final int EXTENSION_RENDERER_MODE_ON = 1;
/**
* Allow use of extension renderers. Extension renderers are indexed before core renderers of the
* same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore
* prefer to use an extension renderer to a core renderer in the case that both are able to play
* a given track.
*/
public static final int EXTENSION_RENDERER_MODE_PREFER = 2;
private static final String TAG = "SimpleExoPlayer";
private static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50;
protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50;
private final ExoPlayer player;
private final Renderer[] renderers;
@ -120,21 +148,16 @@ public final class SimpleExoPlayer implements ExoPlayer {
private float audioVolume;
private PlaybackParamsHolder playbackParamsHolder;
/* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
protected SimpleExoPlayer(Context context, TrackSelector trackSelector, LoadControl loadControl,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs) {
mainHandler = new Handler();
componentListener = new ComponentListener();
// Build the renderers.
ArrayList<Renderer> renderersList = new ArrayList<>();
if (preferExtensionDecoders) {
buildExtensionRenderers(renderersList, allowedVideoJoiningTimeMs);
buildRenderers(context, drmSessionManager, renderersList, allowedVideoJoiningTimeMs);
} else {
buildRenderers(context, drmSessionManager, renderersList, allowedVideoJoiningTimeMs);
buildExtensionRenderers(renderersList, allowedVideoJoiningTimeMs);
}
buildRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
allowedVideoJoiningTimeMs, renderersList);
renderers = renderersList.toArray(new Renderer[renderersList.size()]);
// Obtain counts of video and audio renderers.
@ -593,54 +616,99 @@ public final class SimpleExoPlayer implements ExoPlayer {
return player.getCurrentManifest();
}
// Internal methods.
// Renderer building.
private void buildRenderers(Context context,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, ArrayList<Renderer> renderersList,
long allowedVideoJoiningTimeMs) {
MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context,
MediaCodecSelector.DEFAULT, allowedVideoJoiningTimeMs, drmSessionManager, false,
mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
renderersList.add(videoRenderer);
Renderer audioRenderer = new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT,
drmSessionManager, true, mainHandler, componentListener,
AudioCapabilities.getCapabilities(context));
renderersList.add(audioRenderer);
Renderer textRenderer = new TextRenderer(componentListener, mainHandler.getLooper());
renderersList.add(textRenderer);
MetadataRenderer metadataRenderer = new MetadataRenderer(componentListener,
mainHandler.getLooper(), new Id3Decoder());
renderersList.add(metadataRenderer);
private void buildRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs,
ArrayList<Renderer> out) {
buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, allowedVideoJoiningTimeMs, out);
buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, out);
buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMiscellaneousRenderers(context, mainHandler, extensionRendererMode, out);
}
private void buildExtensionRenderers(ArrayList<Renderer> renderersList,
long allowedVideoJoiningTimeMs) {
// Load extension renderers using reflection so that demo app doesn't depend on them.
// Class.forName(<class name>) appears for each renderer so that automated tools like proguard
// can detect the use of reflection (see http://proguard.sourceforge.net/FAQ.html#forname).
/**
* Builds video renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* not be used for DRM protected playbacks.
* @param extensionRendererMode The extension renderer mode.
* @param eventListener An event listener.
* @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video renderers
* can attempt to seamlessly join an ongoing playback.
* @param out An array to which the built renderers should be appended.
*/
protected void buildVideoRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, VideoRendererEventListener eventListener,
long allowedVideoJoiningTimeMs, ArrayList<Renderer> out) {
out.add(new MediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT,
allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return;
}
int extensionRendererIndex = out.size();
if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) {
extensionRendererIndex--;
}
try {
Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer");
Constructor<?> constructor = clazz.getConstructor(boolean.class, long.class, Handler.class,
VideoRendererEventListener.class, int.class);
renderersList.add((Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs,
mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
Renderer renderer = (Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs,
mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibvpxVideoRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Builds audio renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* not be used for DRM protected playbacks.
* @param extensionRendererMode The extension renderer mode.
* @param eventListener An event listener.
* @param out An array to which the built renderers should be appended.
*/
protected void buildAudioRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener,
ArrayList<Renderer> out) {
out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true,
mainHandler, eventListener, AudioCapabilities.getCapabilities(context)));
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return;
}
int extensionRendererIndex = out.size();
if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) {
extensionRendererIndex--;
}
try {
Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
renderersList.add((Renderer) constructor.newInstance(mainHandler, componentListener));
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibopusAudioRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
@ -653,7 +721,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
renderersList.add((Renderer) constructor.newInstance(mainHandler, componentListener));
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibflacAudioRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
@ -666,7 +735,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
renderersList.add((Renderer) constructor.newInstance(mainHandler, componentListener));
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded FfmpegAudioRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
@ -675,6 +745,51 @@ public final class SimpleExoPlayer implements ExoPlayer {
}
}
/**
* Builds text renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param extensionRendererMode The extension renderer mode.
* @param output An output for the renderers.
* @param out An array to which the built renderers should be appended.
*/
protected void buildTextRenderers(Context context, Handler mainHandler,
@ExtensionRendererMode int extensionRendererMode, TextRenderer.Output output,
ArrayList<Renderer> out) {
out.add(new TextRenderer(output, mainHandler.getLooper()));
}
/**
* Builds metadata renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param extensionRendererMode The extension renderer mode.
* @param output An output for the renderers.
* @param out An array to which the built renderers should be appended.
*/
protected void buildMetadataRenderers(Context context, Handler mainHandler,
@ExtensionRendererMode int extensionRendererMode, MetadataRenderer.Output output,
ArrayList<Renderer> out) {
out.add(new MetadataRenderer(output, mainHandler.getLooper(), new Id3Decoder()));
}
/**
* Builds any miscellaneous renderers used by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param extensionRendererMode The extension renderer mode.
* @param out An array to which the built renderers should be appended.
*/
protected void buildMiscellaneousRenderers(Context context, Handler mainHandler,
@ExtensionRendererMode int extensionRendererMode, ArrayList<Renderer> out) {
// Do nothing.
}
// Internal methods.
private void removeSurfaceCallbacks() {
if (textureView != null) {
if (textureView.getSurfaceTextureListener() != componentListener) {

View File

@ -21,11 +21,15 @@ import android.media.UnsupportedSchemeException;
import android.net.Uri;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import android.view.Surface;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
@ -34,6 +38,7 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer2.playbacktests.util.ActionSchedule;
import com.google.android.exoplayer2.playbacktests.util.DebugSimpleExoPlayer;
import com.google.android.exoplayer2.playbacktests.util.DecoderCountersUtil;
import com.google.android.exoplayer2.playbacktests.util.ExoHostedTest;
import com.google.android.exoplayer2.playbacktests.util.HostActivity;
@ -727,7 +732,17 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
}
@Override
public MediaSource buildSource(HostActivity host, String userAgent,
protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface,
MappingTrackSelector trackSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
SimpleExoPlayer player = new DebugSimpleExoPlayer(host, trackSelector,
new DefaultLoadControl(), drmSessionManager);
player.setVideoSurface(surface);
return player;
}
@Override
protected MediaSource buildSource(HostActivity host, String userAgent,
TransferListener<? super DataSource> mediaTransferListener) {
DataSource.Factory manifestDataSourceFactory = new DefaultDataSourceFactory(host, userAgent);
DataSource.Factory mediaDataSourceFactory = new DefaultDataSourceFactory(host, userAgent,

View File

@ -1,111 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.playbacktests.util;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Handler;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
/**
* Decodes and renders video using {@link MediaCodecVideoRenderer}. Provides buffer timestamp
* assertions.
*/
@TargetApi(16)
public class DebugMediaCodecVideoRenderer extends MediaCodecVideoRenderer {
private static final int ARRAY_SIZE = 1000;
private final long[] timestampsList = new long[ARRAY_SIZE];
private int startIndex;
private int queueSize;
private int bufferCount;
public DebugMediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector,
long allowedJoiningTimeMs, Handler eventHandler, VideoRendererEventListener eventListener,
int maxDroppedFrameCountToNotify) {
super(context, mediaCodecSelector, allowedJoiningTimeMs, null, false, eventHandler,
eventListener, maxDroppedFrameCountToNotify);
startIndex = 0;
queueSize = 0;
}
@Override
protected void releaseCodec() {
super.releaseCodec();
clearTimestamps();
}
@Override
protected void flushCodec() throws ExoPlaybackException {
super.flushCodec();
clearTimestamps();
}
@Override
protected void onQueueInputBuffer(DecoderInputBuffer buffer) {
insertTimestamp(buffer.timeUs);
maybeShiftTimestampsList();
}
@Override
protected void onProcessedOutputBuffer(long presentationTimeUs) {
bufferCount++;
long expectedTimestampUs = dequeueTimestamp();
if (expectedTimestampUs != presentationTimeUs) {
throw new IllegalStateException("Expected to dequeue video buffer with presentation "
+ "timestamp: " + expectedTimestampUs + ". Instead got: " + presentationTimeUs
+ " (Processed buffers since last flush: " + bufferCount + ").");
}
}
private void clearTimestamps() {
startIndex = 0;
queueSize = 0;
bufferCount = 0;
}
private void insertTimestamp(long presentationTimeUs) {
for (int i = startIndex + queueSize - 1; i >= startIndex; i--) {
if (presentationTimeUs >= timestampsList[i]) {
timestampsList[i + 1] = presentationTimeUs;
queueSize++;
return;
}
timestampsList[i + 1] = timestampsList[i];
}
timestampsList[startIndex] = presentationTimeUs;
queueSize++;
}
private void maybeShiftTimestampsList() {
if (startIndex + queueSize == ARRAY_SIZE) {
System.arraycopy(timestampsList, startIndex, timestampsList, 0, queueSize);
startIndex = 0;
}
}
private long dequeueTimestamp() {
startIndex++;
queueSize--;
return timestampsList[startIndex - 1];
}
}

View File

@ -0,0 +1,142 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.playbacktests.util;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Handler;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.util.ArrayList;
/**
* A debug extension of {@link SimpleExoPlayer}. Provides video buffer timestamp assertions.
*/
@TargetApi(16)
public class DebugSimpleExoPlayer extends SimpleExoPlayer {
public DebugSimpleExoPlayer(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
super(context, trackSelector, loadControl, drmSessionManager,
SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF, 0);
}
@Override
protected void buildVideoRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, VideoRendererEventListener eventListener,
long allowedVideoJoiningTimeMs, ArrayList<Renderer> out) {
out.add(new DebugMediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT,
allowedVideoJoiningTimeMs, mainHandler, eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
}
/**
* Decodes and renders video using {@link MediaCodecVideoRenderer}. Provides buffer timestamp
* assertions.
*/
private static class DebugMediaCodecVideoRenderer extends MediaCodecVideoRenderer {
private static final int ARRAY_SIZE = 1000;
private final long[] timestampsList = new long[ARRAY_SIZE];
private int startIndex;
private int queueSize;
private int bufferCount;
public DebugMediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector,
long allowedJoiningTimeMs, Handler eventHandler, VideoRendererEventListener eventListener,
int maxDroppedFrameCountToNotify) {
super(context, mediaCodecSelector, allowedJoiningTimeMs, null, false, eventHandler,
eventListener, maxDroppedFrameCountToNotify);
startIndex = 0;
queueSize = 0;
}
@Override
protected void releaseCodec() {
super.releaseCodec();
clearTimestamps();
}
@Override
protected void flushCodec() throws ExoPlaybackException {
super.flushCodec();
clearTimestamps();
}
@Override
protected void onQueueInputBuffer(DecoderInputBuffer buffer) {
insertTimestamp(buffer.timeUs);
maybeShiftTimestampsList();
}
@Override
protected void onProcessedOutputBuffer(long presentationTimeUs) {
bufferCount++;
long expectedTimestampUs = dequeueTimestamp();
if (expectedTimestampUs != presentationTimeUs) {
throw new IllegalStateException("Expected to dequeue video buffer with presentation "
+ "timestamp: " + expectedTimestampUs + ". Instead got: " + presentationTimeUs
+ " (Processed buffers since last flush: " + bufferCount + ").");
}
}
private void clearTimestamps() {
startIndex = 0;
queueSize = 0;
bufferCount = 0;
}
private void insertTimestamp(long presentationTimeUs) {
for (int i = startIndex + queueSize - 1; i >= startIndex; i--) {
if (presentationTimeUs >= timestampsList[i]) {
timestampsList[i + 1] = presentationTimeUs;
queueSize++;
return;
}
timestampsList[i + 1] = timestampsList[i];
}
timestampsList[startIndex] = presentationTimeUs;
queueSize++;
}
private void maybeShiftTimestampsList() {
if (startIndex + queueSize == ARRAY_SIZE) {
System.arraycopy(timestampsList, startIndex, timestampsList, 0, queueSize);
startIndex = 0;
}
}
private long dequeueTimestamp() {
startIndex++;
queueSize--;
return timestampsList[startIndex - 1];
}
}
}

View File

@ -320,7 +320,8 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
MappingTrackSelector trackSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(host, trackSelector,
new DefaultLoadControl(), drmSessionManager, false, 0);
new DefaultLoadControl(), drmSessionManager, SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF,
0);
player.setVideoSurface(surface);
return player;
}