From 14eb561e3818579ab77ff727b612f0f9b55dcecc Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 18 Jan 2019 16:39:44 +0000 Subject: [PATCH] Use MediaCrypto.setMediaDrmSession to avoid black flicker Issue: #3561 PiperOrigin-RevId: 229934093 --- RELEASENOTES.md | 2 + .../mediacodec/MediaCodecRenderer.java | 165 +++++++++++++----- .../video/MediaCodecVideoRenderer.java | 2 +- 3 files changed, 128 insertions(+), 41 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f45bd03dec1..cfded4c9d6a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -28,6 +28,8 @@ * Rename TaskState to DownloadState. * Add new states to DownloadState. * Replace DownloadState.action with DownloadAction fields. +* DRM: Fix black flicker when keys rotate in DRM protected content + ([#3561](https://github.com/google/ExoPlayer/issues/3561)). * Add support for SHOUTcast ICY metadata ([#3735](https://github.com/google/ExoPlayer/issues/3735)). * CEA-608: Improved conformance to the specification 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 77d3b31ab7e..35f5c14f3f4 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 @@ -240,14 +240,21 @@ private static String buildCustomDiagnosticInfo(int errorCode) { @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({DRAIN_ACTION_NONE, DRAIN_ACTION_FLUSH, DRAIN_ACTION_REINITIALIZE}) + @IntDef({ + DRAIN_ACTION_NONE, + DRAIN_ACTION_FLUSH, + DRAIN_ACTION_UPDATE_DRM_SESSION, + DRAIN_ACTION_REINITIALIZE + }) private @interface DrainAction {} /** No special action should be taken. */ private static final int DRAIN_ACTION_NONE = 0; /** The codec should be flushed. */ private static final int DRAIN_ACTION_FLUSH = 1; - /** The codec should be re-initialized. */ - private static final int DRAIN_ACTION_REINITIALIZE = 2; + /** The codec should be flushed and updated to use the pending DRM session. */ + private static final int DRAIN_ACTION_UPDATE_DRM_SESSION = 2; + /** The codec should be reinitialized. */ + private static final int DRAIN_ACTION_REINITIALIZE = 3; @Documented @Retention(RetentionPolicy.SOURCE) @@ -547,7 +554,7 @@ protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { inputStreamEnded = false; outputStreamEnded = false; - flushOrReinitCodec(); + flushOrReinitializeCodec(); formatQueue.clear(); } @@ -679,12 +686,15 @@ public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackEx *

The implementation of this method calls {@link #flushOrReleaseCodec()}, and {@link * #maybeInitCodec()} if the codec needs to be re-instantiated. * + * @return Whether the codec was released and reinitialized, rather than being flushed. * @throws ExoPlaybackException If an error occurs re-instantiating the codec. */ - protected final void flushOrReinitCodec() throws ExoPlaybackException { - if (flushOrReleaseCodec()) { + protected final boolean flushOrReinitializeCodec() throws ExoPlaybackException { + boolean released = flushOrReleaseCodec(); + if (released) { maybeInitCodec(); } + return released; } /** @@ -1163,40 +1173,58 @@ protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackExceptio // We have an existing codec that we may need to reconfigure or re-initialize. If the existing // codec instance is being kept then its operating rate may need to be updated. - if (sourceDrmSession != codecDrmSession) { + + if ((sourceDrmSession == null && codecDrmSession != null) + || (sourceDrmSession != null && codecDrmSession == null) + || (sourceDrmSession != null && !codecInfo.secure) + || (Util.SDK_INT < 23 && sourceDrmSession != codecDrmSession)) { + // We might need to switch between the clear and protected output paths, or we're using DRM + // prior to API level 23 where the codec needs to be re-initialized to switch to the new DRM + // session. drainAndReinitializeCodec(); - } else { - switch (canKeepCodec(codec, codecInfo, codecFormat, newFormat)) { - case KEEP_CODEC_RESULT_NO: - drainAndReinitializeCodec(); - break; - case KEEP_CODEC_RESULT_YES_WITH_FLUSH: + return; + } + + switch (canKeepCodec(codec, codecInfo, codecFormat, newFormat)) { + case KEEP_CODEC_RESULT_NO: + drainAndReinitializeCodec(); + break; + case KEEP_CODEC_RESULT_YES_WITH_FLUSH: + codecFormat = newFormat; + updateCodecOperatingRate(); + if (sourceDrmSession != codecDrmSession) { + drainAndUpdateCodecDrmSession(); + } else { drainAndFlushCodec(); + } + break; + case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION: + if (codecNeedsReconfigureWorkaround) { + drainAndReinitializeCodec(); + } else { + codecReconfigured = true; + codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; + codecNeedsAdaptationWorkaroundBuffer = + codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS + || (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION + && newFormat.width == codecFormat.width + && newFormat.height == codecFormat.height); codecFormat = newFormat; updateCodecOperatingRate(); - break; - case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION: - if (codecNeedsReconfigureWorkaround) { - drainAndReinitializeCodec(); - } else { - codecReconfigured = true; - codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; - codecNeedsAdaptationWorkaroundBuffer = - codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS - || (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION - && newFormat.width == codecFormat.width - && newFormat.height == codecFormat.height); - codecFormat = newFormat; - updateCodecOperatingRate(); + if (sourceDrmSession != codecDrmSession) { + drainAndUpdateCodecDrmSession(); } - break; - case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION: - codecFormat = newFormat; - updateCodecOperatingRate(); - break; - default: - throw new IllegalStateException(); // Never happens. - } + } + break; + case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION: + codecFormat = newFormat; + updateCodecOperatingRate(); + if (sourceDrmSession != codecDrmSession) { + drainAndUpdateCodecDrmSession(); + } + break; + default: + throw new IllegalStateException(); // Never happens. } } @@ -1331,6 +1359,27 @@ private void drainAndFlushCodec() { } } + /** + * Starts draining the codec to update its DRM session. The update may occur immediately if no + * buffers have been queued to the codec. + * + * @throws ExoPlaybackException If an error occurs updating the codec's DRM session. + */ + private void drainAndUpdateCodecDrmSession() throws ExoPlaybackException { + if (Util.SDK_INT < 23) { + // The codec needs to be re-initialized to switch to the source DRM session. + drainAndReinitializeCodec(); + return; + } + if (codecReceivedBuffers) { + codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM; + codecDrainAction = DRAIN_ACTION_UPDATE_DRM_SESSION; + } else { + // Nothing has been queued to the decoder, so we can do the update immediately. + updateDrmSessionOrReinitializeCodecV23(); + } + } + /** * Starts draining the codec for re-initialization. Re-initialization may occur immediately if no * buffers have been queued to the codec. @@ -1343,8 +1392,7 @@ private void drainAndReinitializeCodec() throws ExoPlaybackException { codecDrainAction = DRAIN_ACTION_REINITIALIZE; } else { // Nothing has been queued to the decoder, so we can re-initialize immediately. - releaseCodec(); - maybeInitCodec(); + reinitializeCodec(); } } @@ -1548,11 +1596,13 @@ protected void renderToEndOfStream() throws ExoPlaybackException { private void processEndOfStream() throws ExoPlaybackException { switch (codecDrainAction) { case DRAIN_ACTION_REINITIALIZE: - releaseCodec(); - maybeInitCodec(); + reinitializeCodec(); + break; + case DRAIN_ACTION_UPDATE_DRM_SESSION: + updateDrmSessionOrReinitializeCodecV23(); break; case DRAIN_ACTION_FLUSH: - flushOrReinitCodec(); + flushOrReinitializeCodec(); break; case DRAIN_ACTION_NONE: default: @@ -1562,6 +1612,41 @@ private void processEndOfStream() throws ExoPlaybackException { } } + private void reinitializeCodec() throws ExoPlaybackException { + releaseCodec(); + maybeInitCodec(); + } + + @TargetApi(23) + private void updateDrmSessionOrReinitializeCodecV23() throws ExoPlaybackException { + FrameworkMediaCrypto sessionMediaCrypto = sourceDrmSession.getMediaCrypto(); + if (sessionMediaCrypto == null) { + // We'd only expect this to happen if the CDM from which the pending session is obtained needs + // provisioning. This is unlikely to happen (it probably requires a switch from one DRM scheme + // to another, where the new CDM hasn't been used before and needs provisioning). It would be + // possible to handle this case more efficiently (i.e. with a new renderer state that waits + // for provisioning to finish and then calls mediaCrypto.setMediaDrmSession), but the extra + // complexity is not warranted given how unlikely the case is to occur. + reinitializeCodec(); + return; + } + + if (flushOrReinitializeCodec()) { + // The codec was reinitialized. The new codec will be using the new DRM session, so there's + // nothing more to do. + return; + } + + try { + mediaCrypto.setMediaDrmSession(sessionMediaCrypto.sessionId); + } catch (MediaCryptoException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + setCodecDrmSession(sourceDrmSession); + codecDrainState = DRAIN_STATE_NONE; + codecDrainAction = DRAIN_ACTION_NONE; + } + private boolean shouldSkipOutputBuffer(long presentationTimeUs) { // We avoid using decodeOnlyPresentationTimestamps.remove(presentationTimeUs) because it would // box presentationTimeUs, creating a Long object that would need to be garbage collected. 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 b92dd44eb24..9084547d0f1 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 @@ -867,7 +867,7 @@ protected boolean maybeDropBuffersToKeyframe(MediaCodec codec, int index, long p // We dropped some buffers to catch up, so update the decoder counters and flush the codec, // which releases all pending buffers buffers including the current output buffer. updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount); - flushOrReinitCodec(); + flushOrReinitializeCodec(); return true; }