Skip to content

Commit

Permalink
Use MediaCrypto.setMediaDrmSession to avoid black flicker
Browse files Browse the repository at this point in the history
Issue: #3561
PiperOrigin-RevId: 229934093
  • Loading branch information
ojw28 committed Jan 21, 2019
1 parent 6a55fda commit 14eb561
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 41 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -679,12 +686,15 @@ public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackEx
* <p>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;
}

/**
Expand Down Expand Up @@ -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.
}
}

Expand Down Expand Up @@ -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.
Expand All @@ -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();
}
}

Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down

0 comments on commit 14eb561

Please sign in to comment.