Soften MediaCodecRenderer's assumptions about using framework DRM

#minor-release

PiperOrigin-RevId: 536724725
This commit is contained in:
ibaker 2023-05-31 15:45:56 +00:00 committed by Tofunmi Adigun-Hameed
parent 208eefc0fd
commit 0ddc024c69

View File

@ -309,7 +309,20 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Nullable private Format outputFormat; @Nullable private Format outputFormat;
@Nullable private DrmSession codecDrmSession; @Nullable private DrmSession codecDrmSession;
@Nullable private DrmSession sourceDrmSession; @Nullable private DrmSession sourceDrmSession;
/**
* A framework {@link MediaCrypto} for use with {@link MediaCodec#queueSecureInputBuffer(int, int,
* MediaCodec.CryptoInfo, long, int)} to play encrypted content.
*
* <p>Non-null if framework decryption is being used (i.e. {@link DrmSession#getCryptoConfig()
* codecDrmSession.getCryptoConfig()} returns an instance of {@link FrameworkCryptoConfig}).
*
* <p>Can be null if the content is not encrypted (in which case {@link #codecDrmSession} and
* {@link #sourceDrmSession} will also be null), or if decryption is happening without framework
* support ({@link #codecDrmSession} and {@link #sourceDrmSession} will be non-null).
*/
@Nullable private MediaCrypto mediaCrypto; @Nullable private MediaCrypto mediaCrypto;
private boolean mediaCryptoRequiresSecureDecoder; private boolean mediaCryptoRequiresSecureDecoder;
private long renderTimeLimitMs; private long renderTimeLimitMs;
private float currentPlaybackSpeed; private float currentPlaybackSpeed;
@ -500,10 +513,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
String mimeType = inputFormat.sampleMimeType; String mimeType = inputFormat.sampleMimeType;
if (codecDrmSession != null) { if (codecDrmSession != null) {
@Nullable CryptoConfig cryptoConfig = codecDrmSession.getCryptoConfig();
if (mediaCrypto == null) { if (mediaCrypto == null) {
@Nullable if (cryptoConfig == null) {
FrameworkCryptoConfig sessionCryptoConfig = getFrameworkCryptoConfig(codecDrmSession);
if (sessionCryptoConfig == null) {
@Nullable DrmSessionException drmError = codecDrmSession.getError(); @Nullable DrmSessionException drmError = codecDrmSession.getError();
if (drmError != null) { if (drmError != null) {
// Continue for now. We may be able to avoid failure if a new input format causes the // Continue for now. We may be able to avoid failure if a new input format causes the
@ -512,19 +524,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// The drm session isn't open yet. // The drm session isn't open yet.
return; return;
} }
} else { } else if (cryptoConfig instanceof FrameworkCryptoConfig) {
FrameworkCryptoConfig frameworkCryptoConfig = (FrameworkCryptoConfig) cryptoConfig;
try { try {
mediaCrypto = new MediaCrypto(sessionCryptoConfig.uuid, sessionCryptoConfig.sessionId); mediaCrypto =
new MediaCrypto(frameworkCryptoConfig.uuid, frameworkCryptoConfig.sessionId);
} catch (MediaCryptoException e) { } catch (MediaCryptoException e) {
throw createRendererException( throw createRendererException(
e, inputFormat, PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR); e, inputFormat, PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR);
} }
mediaCryptoRequiresSecureDecoder = mediaCryptoRequiresSecureDecoder =
!sessionCryptoConfig.forceAllowInsecureDecoderComponents !frameworkCryptoConfig.forceAllowInsecureDecoderComponents
&& mediaCrypto.requiresSecureDecoderComponent(mimeType); && mediaCrypto.requiresSecureDecoderComponent(mimeType);
} }
} }
if (FrameworkCryptoConfig.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC) { if (FrameworkCryptoConfig.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC
&& cryptoConfig instanceof FrameworkCryptoConfig) {
@DrmSession.State int drmSessionState = codecDrmSession.getState(); @DrmSession.State int drmSessionState = codecDrmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) { if (drmSessionState == DrmSession.STATE_ERROR) {
DrmSessionException drmSessionException = DrmSessionException drmSessionException =
@ -985,7 +1000,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
private void maybeInitCodecWithFallback( private void maybeInitCodecWithFallback(
MediaCrypto crypto, boolean mediaCryptoRequiresSecureDecoder) @Nullable MediaCrypto crypto, boolean mediaCryptoRequiresSecureDecoder)
throws DecoderInitializationException { throws DecoderInitializationException {
if (availableCodecInfos == null) { if (availableCodecInfos == null) {
try { try {
@ -1101,7 +1116,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
bypassEnabled = true; bypassEnabled = true;
} }
private void initCodec(MediaCodecInfo codecInfo, MediaCrypto crypto) throws Exception { private void initCodec(MediaCodecInfo codecInfo, @Nullable MediaCrypto crypto) throws Exception {
long codecInitializingTimestamp; long codecInitializingTimestamp;
long codecInitializedTimestamp; long codecInitializedTimestamp;
String codecName = codecInfo.name; String codecName = codecInfo.name;
@ -2116,6 +2131,36 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return true; return true;
} }
@Nullable CryptoConfig newCryptoConfig = newSession.getCryptoConfig();
if (newCryptoConfig == null) {
// We'd only expect this to happen if the CDM from which newSession 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 without codec re-initialization, but it would require the
// re-use code path to be able to wait for provisioning to finish before calling
// MediaCrypto.setMediaDrmSession. The extra complexity is not warranted given how unlikely
// the case is to occur, so we re-initialize in this case.
return true;
}
@Nullable CryptoConfig oldCryptoConfig = oldSession.getCryptoConfig();
if (oldCryptoConfig == null || !newCryptoConfig.getClass().equals(oldCryptoConfig.getClass())) {
// Switching between different CryptoConfig implementations suggests we're switching between
// different forms of decryption (e.g. in-app vs framework-provided), which requires codec
// re-initialization.
return true;
}
if (!(newCryptoConfig instanceof FrameworkCryptoConfig)) {
// Assume that non-framework CryptoConfig implementations indicate the codec can be re-used
// (since it suggests that decryption is happening in-app before the data is passed to
// MediaCodec, and therefore a change in DRM keys has no affect on the (decrypted) data seen
// by MediaCodec).
return false;
}
FrameworkCryptoConfig newFrameworkCryptoConfig = (FrameworkCryptoConfig) newCryptoConfig;
// Note: Both oldSession and newSession are non-null, and they are different sessions. // Note: Both oldSession and newSession are non-null, and they are different sessions.
if (!newSession.getSchemeUuid().equals(oldSession.getSchemeUuid())) { if (!newSession.getSchemeUuid().equals(oldSession.getSchemeUuid())) {
@ -2135,20 +2180,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// TODO: Add an API check once [Internal ref: b/128835874] is fixed. // TODO: Add an API check once [Internal ref: b/128835874] is fixed.
return true; return true;
} }
@Nullable FrameworkCryptoConfig newCryptoConfig = getFrameworkCryptoConfig(newSession);
if (newCryptoConfig == null) {
// We'd only expect this to happen if the CDM from which newSession 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 without codec re-initialization, but it would require the
// re-use code path to be able to wait for provisioning to finish before calling
// MediaCrypto.setMediaDrmSession. The extra complexity is not warranted given how unlikely
// the case is to occur, so we re-initialize in this case.
return true;
}
boolean requiresSecureDecoder; boolean requiresSecureDecoder;
if (newCryptoConfig.forceAllowInsecureDecoderComponents) { if (newFrameworkCryptoConfig.forceAllowInsecureDecoderComponents) {
requiresSecureDecoder = false; requiresSecureDecoder = false;
} else { } else {
requiresSecureDecoder = newSession.requiresSecureDecoder(newFormat.sampleMimeType); requiresSecureDecoder = newSession.requiresSecureDecoder(newFormat.sampleMimeType);
@ -2182,32 +2216,20 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@RequiresApi(23) @RequiresApi(23)
private void updateDrmSessionV23() throws ExoPlaybackException { private void updateDrmSessionV23() throws ExoPlaybackException {
CryptoConfig cryptoConfig = sourceDrmSession.getCryptoConfig();
if (cryptoConfig instanceof FrameworkCryptoConfig) {
try { try {
mediaCrypto.setMediaDrmSession(getFrameworkCryptoConfig(sourceDrmSession).sessionId); mediaCrypto.setMediaDrmSession(((FrameworkCryptoConfig) cryptoConfig).sessionId);
} catch (MediaCryptoException e) { } catch (MediaCryptoException e) {
throw createRendererException(e, inputFormat, PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR); throw createRendererException(
e, inputFormat, PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR);
}
} }
setCodecDrmSession(sourceDrmSession); setCodecDrmSession(sourceDrmSession);
codecDrainState = DRAIN_STATE_NONE; codecDrainState = DRAIN_STATE_NONE;
codecDrainAction = DRAIN_ACTION_NONE; codecDrainAction = DRAIN_ACTION_NONE;
} }
@Nullable
private FrameworkCryptoConfig getFrameworkCryptoConfig(DrmSession drmSession)
throws ExoPlaybackException {
@Nullable CryptoConfig cryptoConfig = drmSession.getCryptoConfig();
if (cryptoConfig != null && !(cryptoConfig instanceof FrameworkCryptoConfig)) {
// This should not happen if the track went through a supportsFormatDrm() check, during track
// selection.
throw createRendererException(
new IllegalArgumentException(
"Expecting FrameworkCryptoConfig but found: " + cryptoConfig),
inputFormat,
PlaybackException.ERROR_CODE_DRM_SCHEME_UNSUPPORTED);
}
return (FrameworkCryptoConfig) cryptoConfig;
}
/** /**
* Processes any pending batch of buffers without using a decoder, and drains a new batch of * Processes any pending batch of buffers without using a decoder, and drains a new batch of
* buffers from the source. * buffers from the source.