diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java index 686718f9e0..87c85f6800 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java @@ -281,7 +281,7 @@ import java.util.Locale; @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { - // Do nothing. + Log.d(TAG, "videoSizeChanged [" + width + ", " + height + "]"); } @Override diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index 467c19b06a..e661aae892 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -255,7 +255,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) { // 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(); return true; } @@ -280,7 +280,6 @@ public final class LibvpxVideoRenderer extends BaseRenderer { return false; } - /** * Returns whether the current frame should be dropped. * @@ -293,10 +292,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer { */ protected boolean shouldDropOutputBuffer(long outputBufferTimeUs, long nextOutputBufferTimeUs, long positionUs, long joiningDeadlineMs) { - // Drop the frame if we're joining and are more than 30ms late, or if we have the next frame - // and that's also late. Else we'll render what we have. - return (joiningDeadlineMs != C.TIME_UNSET && outputBufferTimeUs < positionUs - 30000) - || (nextOutputBufferTimeUs != C.TIME_UNSET && nextOutputBufferTimeUs < positionUs); + return isBufferLate(outputBufferTimeUs - positionUs) + && (joiningDeadlineMs != C.TIME_UNSET || nextOutputBufferTimeUs != C.TIME_UNSET); } private void renderBuffer() { @@ -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; + } + } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java index 3c788a60a4..6ff5082cbd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java @@ -61,6 +61,14 @@ public final class MediaCodecInfo { */ 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 CodecCapabilities capabilities; @@ -71,7 +79,7 @@ public final class MediaCodecInfo { * @return The created instance. */ 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 { */ public static MediaCodecInfo newInstance(String name, String mimeType, 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 { * @param mimeType A mime type supported by the {@link MediaCodec}. * @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 forceSecure Whether {@link #secure} should be forced to {@code true}. * @return The created instance. */ public static MediaCodecInfo newInstance(String name, String mimeType, - CodecCapabilities capabilities, boolean forceDisableAdaptive) { - return new MediaCodecInfo(name, mimeType, capabilities, forceDisableAdaptive); + CodecCapabilities capabilities, boolean forceDisableAdaptive, boolean forceSecure) { + return new MediaCodecInfo(name, mimeType, capabilities, forceDisableAdaptive, forceSecure); } private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities, - boolean forceDisableAdaptive) { + boolean forceDisableAdaptive, boolean forceSecure) { this.name = Assertions.checkNotNull(name); this.mimeType = mimeType; this.capabilities = capabilities; adaptive = !forceDisableAdaptive && capabilities != null && isAdaptive(capabilities); tunneling = capabilities != null && isTunneling(capabilities); + secure = forceSecure || (capabilities != null && isSecure(capabilities)); } /** @@ -176,12 +186,12 @@ public final class MediaCodecInfo { logNoSupport("sizeAndRate.vCaps"); 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 // (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. if (width >= height - || !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) { + || !areSizeAndRateSupportedV21(videoCapabilities, height, width, frameRate)) { logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate); return false; } @@ -290,14 +300,6 @@ public final class MediaCodecInfo { 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) { return Util.SDK_INT >= 21 && isTunnelingV21(capabilities); } @@ -307,4 +309,21 @@ public final class MediaCodecInfo { 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); + } + } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index ebc0a0cda3..6c0010407b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -175,10 +175,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private final MediaCodec.BufferInfo outputBufferInfo; private Format format; - private MediaCodec codec; private DrmSession drmSession; private DrmSession pendingDrmSession; - private boolean codecIsAdaptive; + private MediaCodec codec; + private MediaCodecInfo codecInfo; private boolean codecNeedsDiscardToSpsWorkaround; private boolean codecNeedsFlushWorkaround; private boolean codecNeedsAdaptationWorkaround; @@ -291,7 +291,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @SuppressWarnings("deprecation") 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; } @@ -313,18 +314,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } } - MediaCodecInfo decoderInfo = null; + MediaCodecInfo codecInfo = null; try { - decoderInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder); - if (decoderInfo == null && drmSessionRequiresSecureDecoder) { + codecInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder); + if (codecInfo == null && drmSessionRequiresSecureDecoder) { // 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 // 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. - decoderInfo = getDecoderInfo(mediaCodecSelector, format, false); - if (decoderInfo != null) { + codecInfo = getDecoderInfo(mediaCodecSelector, format, false); + if (codecInfo != null) { 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) { @@ -332,14 +333,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer { drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR)); } - if (decoderInfo == null) { + if (codecInfo == null) { throwDecoderInitError(new DecoderInitializationException(format, null, drmSessionRequiresSecureDecoder, DecoderInitializationException.NO_SUITABLE_DECODER_ERROR)); } - String codecName = decoderInfo.name; - codecIsAdaptive = decoderInfo.adaptive; + if (!shouldInitCodec(codecInfo)) { + return; + } + + this.codecInfo = codecInfo; + String codecName = codecInfo.name; codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName); @@ -353,7 +358,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codec = MediaCodec.createByCodecName(codecName); TraceUtil.endSection(); TraceUtil.beginSection("configureCodec"); - configureCodec(decoderInfo, codec, format, mediaCrypto); + configureCodec(codecInfo, codec, format, mediaCrypto); TraceUtil.endSection(); TraceUtil.beginSection("startCodec"); codec.start(); @@ -380,14 +385,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer { throw ExoPlaybackException.createForRenderer(e, getIndex()); } - protected boolean shouldInitCodec() { - return codec == null && format != null; + protected boolean shouldInitCodec(MediaCodecInfo codecInfo) { + return true; } protected final MediaCodec getCodec() { return codec; } + protected final MediaCodecInfo getCodecInfo() { + return codecInfo; + } + @Override protected void onEnabled(boolean joining) throws ExoPlaybackException { decoderCounters = new DecoderCounters(); @@ -426,31 +435,31 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } protected void releaseCodec() { + codecHotswapDeadlineMs = C.TIME_UNSET; + inputIndex = C.INDEX_UNSET; + outputIndex = C.INDEX_UNSET; + waitingForKeys = false; + shouldSkipOutputBuffer = false; + decodeOnlyPresentationTimestamps.clear(); + inputBuffers = null; + outputBuffers = null; + codecInfo = null; + codecReconfigured = false; + codecReceivedBuffers = false; + codecNeedsDiscardToSpsWorkaround = false; + codecNeedsFlushWorkaround = false; + codecNeedsAdaptationWorkaround = false; + codecNeedsEosPropagationWorkaround = false; + codecNeedsEosFlushWorkaround = false; + codecNeedsMonoChannelCountWorkaround = false; + codecNeedsAdaptationWorkaroundBuffer = false; + shouldSkipAdaptationWorkaroundOutputBuffer = false; + codecReceivedEos = false; + codecReconfigurationState = RECONFIGURATION_STATE_NONE; + codecReinitializationState = REINITIALIZATION_STATE_NONE; + buffer.data = null; if (codec != null) { - codecHotswapDeadlineMs = C.TIME_UNSET; - inputIndex = C.INDEX_UNSET; - outputIndex = C.INDEX_UNSET; - waitingForKeys = false; - shouldSkipOutputBuffer = false; - decodeOnlyPresentationTimestamps.clear(); - inputBuffers = null; - outputBuffers = null; - codecReconfigured = false; - codecReceivedBuffers = false; - codecIsAdaptive = false; - codecNeedsDiscardToSpsWorkaround = false; - codecNeedsFlushWorkaround = false; - codecNeedsAdaptationWorkaround = false; - codecNeedsEosPropagationWorkaround = false; - codecNeedsEosFlushWorkaround = false; - codecNeedsMonoChannelCountWorkaround = false; - codecNeedsAdaptationWorkaroundBuffer = false; - shouldSkipAdaptationWorkaroundOutputBuffer = false; - codecReceivedEos = false; - codecReconfigurationState = RECONFIGURATION_STATE_NONE; - codecReinitializationState = REINITIALIZATION_STATE_NONE; decoderCounters.decoderReleaseCount++; - buffer.data = null; try { codec.stop(); } finally { @@ -781,7 +790,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } if (pendingDrmSession == drmSession && codec != null - && canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) { + && canReconfigureCodec(codec, codecInfo.adaptive, oldFormat, format)) { codecReconfigured = true; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index 5369dffeb6..6d34da2c2e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -230,10 +230,10 @@ public final class MediaCodecUtil { if ((secureDecodersExplicit && key.secure == secure) || (!secureDecodersExplicit && !key.secure)) { decoderInfos.add(MediaCodecInfo.newInstance(codecName, mimeType, capabilities, - forceDisableAdaptive)); + forceDisableAdaptive, false)); } else if (!secureDecodersExplicit && secure) { decoderInfos.add(MediaCodecInfo.newInstance(codecName + ".secure", mimeType, - capabilities, forceDisableAdaptive)); + capabilities, forceDisableAdaptive, true)); // It only makes sense to have one synthesized secure decoder, return immediately. return decoderInfos; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java b/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java index 5298c82f61..81b396cfc7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java @@ -255,8 +255,8 @@ public final class DummySurface extends Surface { if (secure) { glAttributes = new int[] { EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_PROTECTED_CONTENT_EXT, - EGL_TRUE, EGL_NONE}; + EGL_PROTECTED_CONTENT_EXT, EGL_TRUE, + EGL_NONE}; } else { glAttributes = new int[] { EGL_CONTEXT_CLIENT_VERSION, 2, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index aabec0eaa7..cb248fd142 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -40,6 +40,7 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; 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.TraceUtil; import com.google.android.exoplayer2.util.Util; @@ -77,6 +78,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { private CodecMaxValues codecMaxValues; private Surface surface; + private Surface dummySurface; @C.VideoScalingMode private int scalingMode; private boolean renderedFirstFrame; @@ -263,7 +265,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Override 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. joiningDeadlineMs = C.TIME_UNSET; return true; @@ -306,6 +309,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { clearRenderedFirstFrame(); frameReleaseTimeHelper.disable(); tunnelingOnFrameRenderedListener = null; + tunneling = false; try { super.onDisabled(); } finally { @@ -330,6 +334,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } 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. if (this.surface != surface) { this.surface = surface; @@ -343,7 +359,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { maybeInitCodec(); } } - if (surface != null) { + if (surface != null && surface != dummySurface) { // If we know the video size, report it again immediately. maybeRenotifyVideoSizeChanged(); // We haven't rendered to the new surface yet. @@ -356,17 +372,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { clearReportedVideoSize(); clearRenderedFirstFrame(); } - } else if (surface != null) { - // The surface is unchanged and non-null. If we know the video size and/or have already - // rendered to the surface, report these again immediately. + } else if (surface != null && surface != dummySurface) { + // The surface is set and unchanged. If we know the video size and/or have already rendered to + // the surface, report these again immediately. maybeRenotifyVideoSizeChanged(); maybeRenotifyRenderedFirstFrame(); } } @Override - protected boolean shouldInitCodec() { - return super.shouldInitCodec() && surface != null && surface.isValid(); + protected boolean shouldInitCodec(MediaCodecInfo codecInfo) { + return surface != null || shouldUseDummySurface(codecInfo.secure); } @Override @@ -375,12 +391,34 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { codecMaxValues = getCodecMaxValues(codecInfo, format, streamFormats); MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround, 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); if (Util.SDK_INT >= 23 && tunneling) { tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec); } } + @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, long initializationDurationMs) { @@ -452,11 +490,22 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { pendingOutputStreamOffsetCount); } long presentationTimeUs = bufferPresentationTimeUs - outputStreamOffsetUs; + if (shouldSkip) { skipOutputBuffer(codec, bufferIndex, presentationTimeUs); 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 (Util.SDK_INT >= 21) { renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, System.nanoTime()); @@ -470,9 +519,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { 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 earlyUs = bufferPresentationTimeUs - positionUs - elapsedSinceStartOfLoopUs; + earlyUs -= elapsedSinceStartOfLoopUs; // Compute the buffer's desired release time in nanoseconds. long systemTimeNs = System.nanoTime(); @@ -484,7 +534,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000; if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) { - // We're more than 30ms late rendering the frame. dropOutputBuffer(codec, bufferIndex, presentationTimeUs); return true; } @@ -526,8 +575,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { * measured at the start of the current iteration of the rendering loop. */ protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) { - // Drop the frame if we're more than 30ms late rendering the frame. - return earlyUs < -30000; + return isBufferLate(earlyUs); } /** @@ -604,6 +652,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { 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() { joiningDeadlineMs = allowedJoiningTimeMs > 0 ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; @@ -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") private static MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues, boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) {