Commit ba9114c9 by olly Committed by Oliver Woodman

Automatically use DummySurface when possible

Issue: #677

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=157831796
parent 32b5a802
...@@ -281,7 +281,7 @@ import java.util.Locale; ...@@ -281,7 +281,7 @@ import java.util.Locale;
@Override @Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) { float pixelWidthHeightRatio) {
// Do nothing. Log.d(TAG, "videoSizeChanged [" + width + ", " + height + "]");
} }
@Override @Override
......
...@@ -255,7 +255,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -255,7 +255,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) { if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) {
// Skip frames in sync with playback, so we'll be at the right frame if the mode changes. // Skip frames in sync with playback, so we'll be at the right frame if the mode changes.
if (outputBuffer.timeUs <= positionUs) { if (isBufferLate(outputBuffer.timeUs - positionUs)) {
skipBuffer(); skipBuffer();
return true; return true;
} }
...@@ -280,7 +280,6 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -280,7 +280,6 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
return false; return false;
} }
/** /**
* Returns whether the current frame should be dropped. * Returns whether the current frame should be dropped.
* *
...@@ -293,10 +292,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -293,10 +292,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
*/ */
protected boolean shouldDropOutputBuffer(long outputBufferTimeUs, long nextOutputBufferTimeUs, protected boolean shouldDropOutputBuffer(long outputBufferTimeUs, long nextOutputBufferTimeUs,
long positionUs, long joiningDeadlineMs) { long positionUs, long joiningDeadlineMs) {
// Drop the frame if we're joining and are more than 30ms late, or if we have the next frame return isBufferLate(outputBufferTimeUs - positionUs)
// and that's also late. Else we'll render what we have. && (joiningDeadlineMs != C.TIME_UNSET || nextOutputBufferTimeUs != C.TIME_UNSET);
return (joiningDeadlineMs != C.TIME_UNSET && outputBufferTimeUs < positionUs - 30000)
|| (nextOutputBufferTimeUs != C.TIME_UNSET && nextOutputBufferTimeUs < positionUs);
} }
private void renderBuffer() { private void renderBuffer() {
...@@ -655,4 +652,9 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -655,4 +652,9 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
} }
} }
private static boolean isBufferLate(long earlyUs) {
// Class a buffer as late if it should have been presented more than 30ms ago.
return earlyUs < -30000;
}
} }
...@@ -61,6 +61,14 @@ public final class MediaCodecInfo { ...@@ -61,6 +61,14 @@ public final class MediaCodecInfo {
*/ */
public final boolean tunneling; public final boolean tunneling;
/**
* Whether the decoder is secure.
*
* @see CodecCapabilities#isFeatureRequired(String)
* @see CodecCapabilities#FEATURE_SecurePlayback
*/
public final boolean secure;
private final String mimeType; private final String mimeType;
private final CodecCapabilities capabilities; private final CodecCapabilities capabilities;
...@@ -71,7 +79,7 @@ public final class MediaCodecInfo { ...@@ -71,7 +79,7 @@ public final class MediaCodecInfo {
* @return The created instance. * @return The created instance.
*/ */
public static MediaCodecInfo newPassthroughInstance(String name) { public static MediaCodecInfo newPassthroughInstance(String name) {
return new MediaCodecInfo(name, null, null, false); return new MediaCodecInfo(name, null, null, false, false);
} }
/** /**
...@@ -84,7 +92,7 @@ public final class MediaCodecInfo { ...@@ -84,7 +92,7 @@ public final class MediaCodecInfo {
*/ */
public static MediaCodecInfo newInstance(String name, String mimeType, public static MediaCodecInfo newInstance(String name, String mimeType,
CodecCapabilities capabilities) { CodecCapabilities capabilities) {
return new MediaCodecInfo(name, mimeType, capabilities, false); return new MediaCodecInfo(name, mimeType, capabilities, false, false);
} }
/** /**
...@@ -94,20 +102,22 @@ public final class MediaCodecInfo { ...@@ -94,20 +102,22 @@ public final class MediaCodecInfo {
* @param mimeType A mime type supported by the {@link MediaCodec}. * @param mimeType A mime type supported by the {@link MediaCodec}.
* @param capabilities The capabilities of the {@link MediaCodec} for the specified mime type. * @param capabilities The capabilities of the {@link MediaCodec} for the specified mime type.
* @param forceDisableAdaptive Whether {@link #adaptive} should be forced to {@code false}. * @param forceDisableAdaptive Whether {@link #adaptive} should be forced to {@code false}.
* @param forceSecure Whether {@link #secure} should be forced to {@code true}.
* @return The created instance. * @return The created instance.
*/ */
public static MediaCodecInfo newInstance(String name, String mimeType, public static MediaCodecInfo newInstance(String name, String mimeType,
CodecCapabilities capabilities, boolean forceDisableAdaptive) { CodecCapabilities capabilities, boolean forceDisableAdaptive, boolean forceSecure) {
return new MediaCodecInfo(name, mimeType, capabilities, forceDisableAdaptive); return new MediaCodecInfo(name, mimeType, capabilities, forceDisableAdaptive, forceSecure);
} }
private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities, private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities,
boolean forceDisableAdaptive) { boolean forceDisableAdaptive, boolean forceSecure) {
this.name = Assertions.checkNotNull(name); this.name = Assertions.checkNotNull(name);
this.mimeType = mimeType; this.mimeType = mimeType;
this.capabilities = capabilities; this.capabilities = capabilities;
adaptive = !forceDisableAdaptive && capabilities != null && isAdaptive(capabilities); adaptive = !forceDisableAdaptive && capabilities != null && isAdaptive(capabilities);
tunneling = capabilities != null && isTunneling(capabilities); tunneling = capabilities != null && isTunneling(capabilities);
secure = forceSecure || (capabilities != null && isSecure(capabilities));
} }
/** /**
...@@ -176,12 +186,12 @@ public final class MediaCodecInfo { ...@@ -176,12 +186,12 @@ public final class MediaCodecInfo {
logNoSupport("sizeAndRate.vCaps"); logNoSupport("sizeAndRate.vCaps");
return false; return false;
} }
if (!areSizeAndRateSupported(videoCapabilities, width, height, frameRate)) { if (!areSizeAndRateSupportedV21(videoCapabilities, width, height, frameRate)) {
// Capabilities are known to be inaccurately reported for vertical resolutions on some devices // Capabilities are known to be inaccurately reported for vertical resolutions on some devices
// (b/31387661). If the video is vertical and the capabilities indicate support if the width // (b/31387661). If the video is vertical and the capabilities indicate support if the width
// and height are swapped, we assume that the vertical resolution is also supported. // and height are swapped, we assume that the vertical resolution is also supported.
if (width >= height if (width >= height
|| !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) { || !areSizeAndRateSupportedV21(videoCapabilities, height, width, frameRate)) {
logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate); logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate);
return false; return false;
} }
...@@ -290,14 +300,6 @@ public final class MediaCodecInfo { ...@@ -290,14 +300,6 @@ public final class MediaCodecInfo {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback); return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
} }
@TargetApi(21)
private static boolean areSizeAndRateSupported(VideoCapabilities capabilities, int width,
int height, double frameRate) {
return frameRate == Format.NO_VALUE || frameRate <= 0
? capabilities.isSizeSupported(width, height)
: capabilities.areSizeAndRateSupported(width, height, frameRate);
}
private static boolean isTunneling(CodecCapabilities capabilities) { private static boolean isTunneling(CodecCapabilities capabilities) {
return Util.SDK_INT >= 21 && isTunnelingV21(capabilities); return Util.SDK_INT >= 21 && isTunnelingV21(capabilities);
} }
...@@ -307,4 +309,21 @@ public final class MediaCodecInfo { ...@@ -307,4 +309,21 @@ public final class MediaCodecInfo {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback); return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback);
} }
private static boolean isSecure(CodecCapabilities capabilities) {
return Util.SDK_INT >= 21 && isSecureV21(capabilities);
}
@TargetApi(21)
private static boolean isSecureV21(CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
}
@TargetApi(21)
private static boolean areSizeAndRateSupportedV21(VideoCapabilities capabilities, int width,
int height, double frameRate) {
return frameRate == Format.NO_VALUE || frameRate <= 0
? capabilities.isSizeSupported(width, height)
: capabilities.areSizeAndRateSupported(width, height, frameRate);
}
} }
...@@ -175,10 +175,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -175,10 +175,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private final MediaCodec.BufferInfo outputBufferInfo; private final MediaCodec.BufferInfo outputBufferInfo;
private Format format; private Format format;
private MediaCodec codec;
private DrmSession<FrameworkMediaCrypto> drmSession; private DrmSession<FrameworkMediaCrypto> drmSession;
private DrmSession<FrameworkMediaCrypto> pendingDrmSession; private DrmSession<FrameworkMediaCrypto> pendingDrmSession;
private boolean codecIsAdaptive; private MediaCodec codec;
private MediaCodecInfo codecInfo;
private boolean codecNeedsDiscardToSpsWorkaround; private boolean codecNeedsDiscardToSpsWorkaround;
private boolean codecNeedsFlushWorkaround; private boolean codecNeedsFlushWorkaround;
private boolean codecNeedsAdaptationWorkaround; private boolean codecNeedsAdaptationWorkaround;
...@@ -291,7 +291,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -291,7 +291,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
protected final void maybeInitCodec() throws ExoPlaybackException { protected final void maybeInitCodec() throws ExoPlaybackException {
if (!shouldInitCodec()) { if (codec != null || format == null) {
// We have a codec already, or we don't have a format with which to instantiate one.
return; return;
} }
...@@ -313,18 +314,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -313,18 +314,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
} }
MediaCodecInfo decoderInfo = null; MediaCodecInfo codecInfo = null;
try { try {
decoderInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder); codecInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder);
if (decoderInfo == null && drmSessionRequiresSecureDecoder) { if (codecInfo == null && drmSessionRequiresSecureDecoder) {
// The drm session indicates that a secure decoder is required, but the device does not have // The drm session indicates that a secure decoder is required, but the device does not have
// one. Assuming that supportsFormat indicated support for the media being played, we know // one. Assuming that supportsFormat indicated support for the media being played, we know
// that it does not require a secure output path. Most CDM implementations allow playback to // that it does not require a secure output path. Most CDM implementations allow playback to
// proceed with a non-secure decoder in this case, so we try our luck. // proceed with a non-secure decoder in this case, so we try our luck.
decoderInfo = getDecoderInfo(mediaCodecSelector, format, false); codecInfo = getDecoderInfo(mediaCodecSelector, format, false);
if (decoderInfo != null) { if (codecInfo != null) {
Log.w(TAG, "Drm session requires secure decoder for " + mimeType + ", but " Log.w(TAG, "Drm session requires secure decoder for " + mimeType + ", but "
+ "no secure decoder available. Trying to proceed with " + decoderInfo.name + "."); + "no secure decoder available. Trying to proceed with " + codecInfo.name + ".");
} }
} }
} catch (DecoderQueryException e) { } catch (DecoderQueryException e) {
...@@ -332,14 +333,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -332,14 +333,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR)); drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR));
} }
if (decoderInfo == null) { if (codecInfo == null) {
throwDecoderInitError(new DecoderInitializationException(format, null, throwDecoderInitError(new DecoderInitializationException(format, null,
drmSessionRequiresSecureDecoder, drmSessionRequiresSecureDecoder,
DecoderInitializationException.NO_SUITABLE_DECODER_ERROR)); DecoderInitializationException.NO_SUITABLE_DECODER_ERROR));
} }
String codecName = decoderInfo.name; if (!shouldInitCodec(codecInfo)) {
codecIsAdaptive = decoderInfo.adaptive; return;
}
this.codecInfo = codecInfo;
String codecName = codecInfo.name;
codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format);
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName); codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName);
...@@ -353,7 +358,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -353,7 +358,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codec = MediaCodec.createByCodecName(codecName); codec = MediaCodec.createByCodecName(codecName);
TraceUtil.endSection(); TraceUtil.endSection();
TraceUtil.beginSection("configureCodec"); TraceUtil.beginSection("configureCodec");
configureCodec(decoderInfo, codec, format, mediaCrypto); configureCodec(codecInfo, codec, format, mediaCrypto);
TraceUtil.endSection(); TraceUtil.endSection();
TraceUtil.beginSection("startCodec"); TraceUtil.beginSection("startCodec");
codec.start(); codec.start();
...@@ -380,14 +385,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -380,14 +385,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
protected boolean shouldInitCodec() { protected boolean shouldInitCodec(MediaCodecInfo codecInfo) {
return codec == null && format != null; return true;
} }
protected final MediaCodec getCodec() { protected final MediaCodec getCodec() {
return codec; return codec;
} }
protected final MediaCodecInfo getCodecInfo() {
return codecInfo;
}
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining) throws ExoPlaybackException {
decoderCounters = new DecoderCounters(); decoderCounters = new DecoderCounters();
...@@ -426,7 +435,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -426,7 +435,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
protected void releaseCodec() { protected void releaseCodec() {
if (codec != null) {
codecHotswapDeadlineMs = C.TIME_UNSET; codecHotswapDeadlineMs = C.TIME_UNSET;
inputIndex = C.INDEX_UNSET; inputIndex = C.INDEX_UNSET;
outputIndex = C.INDEX_UNSET; outputIndex = C.INDEX_UNSET;
...@@ -435,9 +443,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -435,9 +443,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
decodeOnlyPresentationTimestamps.clear(); decodeOnlyPresentationTimestamps.clear();
inputBuffers = null; inputBuffers = null;
outputBuffers = null; outputBuffers = null;
codecInfo = null;
codecReconfigured = false; codecReconfigured = false;
codecReceivedBuffers = false; codecReceivedBuffers = false;
codecIsAdaptive = false;
codecNeedsDiscardToSpsWorkaround = false; codecNeedsDiscardToSpsWorkaround = false;
codecNeedsFlushWorkaround = false; codecNeedsFlushWorkaround = false;
codecNeedsAdaptationWorkaround = false; codecNeedsAdaptationWorkaround = false;
...@@ -449,8 +457,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -449,8 +457,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecReceivedEos = false; codecReceivedEos = false;
codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReconfigurationState = RECONFIGURATION_STATE_NONE;
codecReinitializationState = REINITIALIZATION_STATE_NONE; codecReinitializationState = REINITIALIZATION_STATE_NONE;
decoderCounters.decoderReleaseCount++;
buffer.data = null; buffer.data = null;
if (codec != null) {
decoderCounters.decoderReleaseCount++;
try { try {
codec.stop(); codec.stop();
} finally { } finally {
...@@ -781,7 +790,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -781,7 +790,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
if (pendingDrmSession == drmSession && codec != null if (pendingDrmSession == drmSession && codec != null
&& canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) { && canReconfigureCodec(codec, codecInfo.adaptive, oldFormat, format)) {
codecReconfigured = true; codecReconfigured = true;
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround
......
...@@ -230,10 +230,10 @@ public final class MediaCodecUtil { ...@@ -230,10 +230,10 @@ public final class MediaCodecUtil {
if ((secureDecodersExplicit && key.secure == secure) if ((secureDecodersExplicit && key.secure == secure)
|| (!secureDecodersExplicit && !key.secure)) { || (!secureDecodersExplicit && !key.secure)) {
decoderInfos.add(MediaCodecInfo.newInstance(codecName, mimeType, capabilities, decoderInfos.add(MediaCodecInfo.newInstance(codecName, mimeType, capabilities,
forceDisableAdaptive)); forceDisableAdaptive, false));
} else if (!secureDecodersExplicit && secure) { } else if (!secureDecodersExplicit && secure) {
decoderInfos.add(MediaCodecInfo.newInstance(codecName + ".secure", mimeType, decoderInfos.add(MediaCodecInfo.newInstance(codecName + ".secure", mimeType,
capabilities, forceDisableAdaptive)); capabilities, forceDisableAdaptive, true));
// It only makes sense to have one synthesized secure decoder, return immediately. // It only makes sense to have one synthesized secure decoder, return immediately.
return decoderInfos; return decoderInfos;
} }
......
...@@ -255,8 +255,8 @@ public final class DummySurface extends Surface { ...@@ -255,8 +255,8 @@ public final class DummySurface extends Surface {
if (secure) { if (secure) {
glAttributes = new int[] { glAttributes = new int[] {
EGL_CONTEXT_CLIENT_VERSION, 2, EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_PROTECTED_CONTENT_EXT, EGL_PROTECTED_CONTENT_EXT, EGL_TRUE,
EGL_TRUE, EGL_NONE}; EGL_NONE};
} else { } else {
glAttributes = new int[] { glAttributes = new int[] {
EGL_CONTEXT_CLIENT_VERSION, 2, EGL_CONTEXT_CLIENT_VERSION, 2,
......
...@@ -40,6 +40,7 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; ...@@ -40,6 +40,7 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -77,6 +78,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -77,6 +78,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
private CodecMaxValues codecMaxValues; private CodecMaxValues codecMaxValues;
private Surface surface; private Surface surface;
private Surface dummySurface;
@C.VideoScalingMode @C.VideoScalingMode
private int scalingMode; private int scalingMode;
private boolean renderedFirstFrame; private boolean renderedFirstFrame;
...@@ -263,7 +265,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -263,7 +265,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override @Override
public boolean isReady() { public boolean isReady() {
if ((renderedFirstFrame || super.shouldInitCodec()) && super.isReady()) { if (super.isReady() && (renderedFirstFrame || (dummySurface != null && surface == dummySurface)
|| getCodec() == null)) {
// Ready. If we were joining then we've now joined, so clear the joining deadline. // Ready. If we were joining then we've now joined, so clear the joining deadline.
joiningDeadlineMs = C.TIME_UNSET; joiningDeadlineMs = C.TIME_UNSET;
return true; return true;
...@@ -306,6 +309,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -306,6 +309,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
clearRenderedFirstFrame(); clearRenderedFirstFrame();
frameReleaseTimeHelper.disable(); frameReleaseTimeHelper.disable();
tunnelingOnFrameRenderedListener = null; tunnelingOnFrameRenderedListener = null;
tunneling = false;
try { try {
super.onDisabled(); super.onDisabled();
} finally { } finally {
...@@ -330,6 +334,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -330,6 +334,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
private void setSurface(Surface surface) throws ExoPlaybackException { private void setSurface(Surface surface) throws ExoPlaybackException {
if (surface == null) {
// Use a dummy surface if possible.
if (dummySurface != null) {
surface = dummySurface;
} else {
MediaCodecInfo codecInfo = getCodecInfo();
if (codecInfo != null && shouldUseDummySurface(codecInfo.secure)) {
dummySurface = DummySurface.newInstanceV17(codecInfo.secure);
surface = dummySurface;
}
}
}
// We only need to update the codec if the surface has changed. // We only need to update the codec if the surface has changed.
if (this.surface != surface) { if (this.surface != surface) {
this.surface = surface; this.surface = surface;
...@@ -343,7 +359,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -343,7 +359,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
maybeInitCodec(); maybeInitCodec();
} }
} }
if (surface != null) { if (surface != null && surface != dummySurface) {
// If we know the video size, report it again immediately. // If we know the video size, report it again immediately.
maybeRenotifyVideoSizeChanged(); maybeRenotifyVideoSizeChanged();
// We haven't rendered to the new surface yet. // We haven't rendered to the new surface yet.
...@@ -356,17 +372,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -356,17 +372,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
clearReportedVideoSize(); clearReportedVideoSize();
clearRenderedFirstFrame(); clearRenderedFirstFrame();
} }
} else if (surface != null) { } else if (surface != null && surface != dummySurface) {
// The surface is unchanged and non-null. If we know the video size and/or have already // The surface is set and unchanged. If we know the video size and/or have already rendered to
// rendered to the surface, report these again immediately. // the surface, report these again immediately.
maybeRenotifyVideoSizeChanged(); maybeRenotifyVideoSizeChanged();
maybeRenotifyRenderedFirstFrame(); maybeRenotifyRenderedFirstFrame();
} }
} }
@Override @Override
protected boolean shouldInitCodec() { protected boolean shouldInitCodec(MediaCodecInfo codecInfo) {
return super.shouldInitCodec() && surface != null && surface.isValid(); return surface != null || shouldUseDummySurface(codecInfo.secure);
} }
@Override @Override
...@@ -375,6 +391,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -375,6 +391,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
codecMaxValues = getCodecMaxValues(codecInfo, format, streamFormats); codecMaxValues = getCodecMaxValues(codecInfo, format, streamFormats);
MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround, MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround,
tunnelingAudioSessionId); tunnelingAudioSessionId);
if (surface == null) {
Assertions.checkState(shouldUseDummySurface(codecInfo.secure));
if (dummySurface == null) {
dummySurface = DummySurface.newInstanceV17(codecInfo.secure);
}
surface = dummySurface;
}
codec.configure(mediaFormat, surface, crypto, 0); codec.configure(mediaFormat, surface, crypto, 0);
if (Util.SDK_INT >= 23 && tunneling) { if (Util.SDK_INT >= 23 && tunneling) {
tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec); tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec);
...@@ -382,6 +405,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -382,6 +405,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
@Override @Override
protected void releaseCodec() {
try {
super.releaseCodec();
} finally {
if (dummySurface != null) {
if (surface == dummySurface) {
surface = null;
}
dummySurface.release();
dummySurface = null;
}
}
}
@Override
protected void onCodecInitialized(String name, long initializedTimestampMs, protected void onCodecInitialized(String name, long initializedTimestampMs,
long initializationDurationMs) { long initializationDurationMs) {
eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs); eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
...@@ -452,11 +490,22 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -452,11 +490,22 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
pendingOutputStreamOffsetCount); pendingOutputStreamOffsetCount);
} }
long presentationTimeUs = bufferPresentationTimeUs - outputStreamOffsetUs; long presentationTimeUs = bufferPresentationTimeUs - outputStreamOffsetUs;
if (shouldSkip) { if (shouldSkip) {
skipOutputBuffer(codec, bufferIndex, presentationTimeUs); skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
return true; return true;
} }
long earlyUs = bufferPresentationTimeUs - positionUs;
if (surface == dummySurface) {
// Skip frames in sync with playback, so we'll be at the right frame if the mode changes.
if (isBufferLate(earlyUs)) {
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
return true;
}
return false;
}
if (!renderedFirstFrame) { if (!renderedFirstFrame) {
if (Util.SDK_INT >= 21) { if (Util.SDK_INT >= 21) {
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, System.nanoTime()); renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, System.nanoTime());
...@@ -470,9 +519,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -470,9 +519,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
return false; return false;
} }
// Compute how many microseconds it is until the buffer's presentation time. // Fine-grained adjustment of earlyUs based on the elapsed time since the start of the current
// iteration of the rendering loop.
long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs; long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs;
long earlyUs = bufferPresentationTimeUs - positionUs - elapsedSinceStartOfLoopUs; earlyUs -= elapsedSinceStartOfLoopUs;
// Compute the buffer's desired release time in nanoseconds. // Compute the buffer's desired release time in nanoseconds.
long systemTimeNs = System.nanoTime(); long systemTimeNs = System.nanoTime();
...@@ -484,7 +534,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -484,7 +534,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000; earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) { if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) {
// We're more than 30ms late rendering the frame.
dropOutputBuffer(codec, bufferIndex, presentationTimeUs); dropOutputBuffer(codec, bufferIndex, presentationTimeUs);
return true; return true;
} }
...@@ -526,8 +575,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -526,8 +575,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
* measured at the start of the current iteration of the rendering loop. * measured at the start of the current iteration of the rendering loop.
*/ */
protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) { protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) {
// Drop the frame if we're more than 30ms late rendering the frame. return isBufferLate(earlyUs);
return earlyUs < -30000;
} }
/** /**
...@@ -604,6 +652,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -604,6 +652,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
maybeNotifyRenderedFirstFrame(); maybeNotifyRenderedFirstFrame();
} }
private boolean shouldUseDummySurface(boolean codecIsSecure) {
// TODO: Work out when we can safely uncomment the secure case below. This case is currently
// broken on Galaxy S8 [Internal: b/37197802].
return Util.SDK_INT >= 23 && !tunneling
&& (!codecIsSecure /* || DummySurface.SECURE_SUPPORTED */);
}
private void setJoiningDeadlineMs() { private void setJoiningDeadlineMs() {
joiningDeadlineMs = allowedJoiningTimeMs > 0 joiningDeadlineMs = allowedJoiningTimeMs > 0
? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET;
...@@ -674,6 +729,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -674,6 +729,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
} }
private static boolean isBufferLate(long earlyUs) {
// Class a buffer as late if it should have been presented more than 30ms ago.
return earlyUs < -30000;
}
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
private static MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues, private static MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues,
boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) { boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment