Use MediaCrypto.setMediaDrmSession to avoid black flicker

Issue: #3561
PiperOrigin-RevId: 229934093
This commit is contained in:
olly 2019-01-18 16:39:44 +00:00 committed by Oliver Woodman
parent 6a55fda66d
commit 14eb561e38
3 changed files with 128 additions and 41 deletions

View File

@ -28,6 +28,8 @@
* Rename TaskState to DownloadState. * Rename TaskState to DownloadState.
* Add new states to DownloadState. * Add new states to DownloadState.
* Replace DownloadState.action with DownloadAction fields. * 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 * Add support for SHOUTcast ICY metadata
([#3735](https://github.com/google/ExoPlayer/issues/3735)). ([#3735](https://github.com/google/ExoPlayer/issues/3735)).
* CEA-608: Improved conformance to the specification * CEA-608: Improved conformance to the specification

View File

@ -240,14 +240,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @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 {} private @interface DrainAction {}
/** No special action should be taken. */ /** No special action should be taken. */
private static final int DRAIN_ACTION_NONE = 0; private static final int DRAIN_ACTION_NONE = 0;
/** The codec should be flushed. */ /** The codec should be flushed. */
private static final int DRAIN_ACTION_FLUSH = 1; private static final int DRAIN_ACTION_FLUSH = 1;
/** The codec should be re-initialized. */ /** The codec should be flushed and updated to use the pending DRM session. */
private static final int DRAIN_ACTION_REINITIALIZE = 2; private static final int DRAIN_ACTION_UPDATE_DRM_SESSION = 2;
/** The codec should be reinitialized. */
private static final int DRAIN_ACTION_REINITIALIZE = 3;
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@ -547,7 +554,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
inputStreamEnded = false; inputStreamEnded = false;
outputStreamEnded = false; outputStreamEnded = false;
flushOrReinitCodec(); flushOrReinitializeCodec();
formatQueue.clear(); formatQueue.clear();
} }
@ -679,12 +686,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* <p>The implementation of this method calls {@link #flushOrReleaseCodec()}, and {@link * <p>The implementation of this method calls {@link #flushOrReleaseCodec()}, and {@link
* #maybeInitCodec()} if the codec needs to be re-instantiated. * #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. * @throws ExoPlaybackException If an error occurs re-instantiating the codec.
*/ */
protected final void flushOrReinitCodec() throws ExoPlaybackException { protected final boolean flushOrReinitializeCodec() throws ExoPlaybackException {
if (flushOrReleaseCodec()) { boolean released = flushOrReleaseCodec();
if (released) {
maybeInitCodec(); maybeInitCodec();
} }
return released;
} }
/** /**
@ -1163,17 +1173,30 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// We have an existing codec that we may need to reconfigure or re-initialize. If the existing // 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. // 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(); drainAndReinitializeCodec();
} else { return;
}
switch (canKeepCodec(codec, codecInfo, codecFormat, newFormat)) { switch (canKeepCodec(codec, codecInfo, codecFormat, newFormat)) {
case KEEP_CODEC_RESULT_NO: case KEEP_CODEC_RESULT_NO:
drainAndReinitializeCodec(); drainAndReinitializeCodec();
break; break;
case KEEP_CODEC_RESULT_YES_WITH_FLUSH: case KEEP_CODEC_RESULT_YES_WITH_FLUSH:
drainAndFlushCodec();
codecFormat = newFormat; codecFormat = newFormat;
updateCodecOperatingRate(); updateCodecOperatingRate();
if (sourceDrmSession != codecDrmSession) {
drainAndUpdateCodecDrmSession();
} else {
drainAndFlushCodec();
}
break; break;
case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION: case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION:
if (codecNeedsReconfigureWorkaround) { if (codecNeedsReconfigureWorkaround) {
@ -1188,17 +1211,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
&& newFormat.height == codecFormat.height); && newFormat.height == codecFormat.height);
codecFormat = newFormat; codecFormat = newFormat;
updateCodecOperatingRate(); updateCodecOperatingRate();
if (sourceDrmSession != codecDrmSession) {
drainAndUpdateCodecDrmSession();
}
} }
break; break;
case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION: case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION:
codecFormat = newFormat; codecFormat = newFormat;
updateCodecOperatingRate(); updateCodecOperatingRate();
if (sourceDrmSession != codecDrmSession) {
drainAndUpdateCodecDrmSession();
}
break; break;
default: default:
throw new IllegalStateException(); // Never happens. throw new IllegalStateException(); // Never happens.
} }
} }
}
/** /**
* Called when the output format of the {@link MediaCodec} changes. * Called when the output format of the {@link MediaCodec} changes.
@ -1331,6 +1359,27 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
} }
/**
* 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 * Starts draining the codec for re-initialization. Re-initialization may occur immediately if no
* buffers have been queued to the codec. * buffers have been queued to the codec.
@ -1343,8 +1392,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecDrainAction = DRAIN_ACTION_REINITIALIZE; codecDrainAction = DRAIN_ACTION_REINITIALIZE;
} else { } else {
// Nothing has been queued to the decoder, so we can re-initialize immediately. // Nothing has been queued to the decoder, so we can re-initialize immediately.
releaseCodec(); reinitializeCodec();
maybeInitCodec();
} }
} }
@ -1548,11 +1596,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private void processEndOfStream() throws ExoPlaybackException { private void processEndOfStream() throws ExoPlaybackException {
switch (codecDrainAction) { switch (codecDrainAction) {
case DRAIN_ACTION_REINITIALIZE: case DRAIN_ACTION_REINITIALIZE:
releaseCodec(); reinitializeCodec();
maybeInitCodec(); break;
case DRAIN_ACTION_UPDATE_DRM_SESSION:
updateDrmSessionOrReinitializeCodecV23();
break; break;
case DRAIN_ACTION_FLUSH: case DRAIN_ACTION_FLUSH:
flushOrReinitCodec(); flushOrReinitializeCodec();
break; break;
case DRAIN_ACTION_NONE: case DRAIN_ACTION_NONE:
default: default:
@ -1562,6 +1612,41 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
} }
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) { private boolean shouldSkipOutputBuffer(long presentationTimeUs) {
// We avoid using decodeOnlyPresentationTimestamps.remove(presentationTimeUs) because it would // We avoid using decodeOnlyPresentationTimestamps.remove(presentationTimeUs) because it would
// box presentationTimeUs, creating a Long object that would need to be garbage collected. // box presentationTimeUs, creating a Long object that would need to be garbage collected.

View File

@ -867,7 +867,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
// We dropped some buffers to catch up, so update the decoder counters and flush the codec, // 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. // which releases all pending buffers buffers including the current output buffer.
updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount); updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount);
flushOrReinitCodec(); flushOrReinitializeCodec();
return true; return true;
} }