From f72833476e1501323fb5a05355718046e0d07bf8 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 11 Jul 2017 11:28:08 -0700 Subject: [PATCH] Add DRM support to RendererCapabilities This CL also makes DefaultTrackSelector take it into account when RendererCapabilities sets it to unsupported. A following CL could add a DefaultTrackSelector parameter to force DRM "known support" for specific track types. Issue:#1661 Issue:#1989 Issue:#2089 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=161556467 --- .../exoplayer2/demo/PlayerActivity.java | 4 +-- .../java/com/google/android/exoplayer2/C.java | 20 +++++++++++ .../exoplayer2/RendererCapabilities.java | 33 ++++++++++++------- .../drm/DefaultDrmSessionManager.java | 21 ++++++++++++ .../exoplayer2/drm/DrmSessionManager.java | 10 ++++++ .../extractor/mp4/TrackEncryptionBox.java | 8 ++--- .../mediacodec/MediaCodecRenderer.java | 30 ++++++++++++++++- 7 files changed, 107 insertions(+), 19 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 619fc202daa..d7e22f00ec8 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -386,8 +386,8 @@ private DrmSessionManager buildDrmSessionManager(UUID uuid keyRequestPropertiesArray[i + 1]); } } - return new DefaultDrmSessionManager<>(uuid, - FrameworkMediaDrm.newInstance(uuid), drmCallback, null, mainHandler, eventLogger); + return new DefaultDrmSessionManager<>(uuid, FrameworkMediaDrm.newInstance(uuid), drmCallback, + null, mainHandler, eventLogger); } private void releasePlayer() { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index e8c47d98115..d7d0ed40aaf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -578,6 +578,26 @@ private C() {} public static final int DEFAULT_MUXED_BUFFER_SIZE = DEFAULT_VIDEO_BUFFER_SIZE + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE; + /** + * "cenc" scheme type name as defined in ISO/IEC 23001-7:2016. + */ + public static final String CENC_TYPE_cenc = "cenc"; + + /** + * "cbc1" scheme type name as defined in ISO/IEC 23001-7:2016. + */ + public static final String CENC_TYPE_cbc1 = "cbc1"; + + /** + * "cens" scheme type name as defined in ISO/IEC 23001-7:2016. + */ + public static final String CENC_TYPE_cens = "cens"; + + /** + * "cbcs" scheme type name as defined in ISO/IEC 23001-7:2016. + */ + public static final String CENC_TYPE_cbcs = "cbcs"; + /** * The Nil UUID as defined by * RFC4122. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java b/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java index 151453c12c9..f841a1b8b5f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java @@ -27,11 +27,11 @@ public interface RendererCapabilities { * {@link #FORMAT_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES}, * {@link #FORMAT_UNSUPPORTED_SUBTYPE} and {@link #FORMAT_UNSUPPORTED_TYPE}. */ - int FORMAT_SUPPORT_MASK = 0b11; + int FORMAT_SUPPORT_MASK = 0b111; /** * The {@link Renderer} is capable of rendering the format. */ - int FORMAT_HANDLED = 0b11; + int FORMAT_HANDLED = 0b100; /** * The {@link Renderer} is capable of rendering formats with the same mime type, but the * properties of the format exceed the renderer's capability. @@ -40,7 +40,16 @@ public interface RendererCapabilities { * {@link MimeTypes#VIDEO_H264}, but the format's resolution exceeds the maximum limit supported * by the underlying H264 decoder. */ - int FORMAT_EXCEEDS_CAPABILITIES = 0b10; + int FORMAT_EXCEEDS_CAPABILITIES = 0b011; + /** + * The {@link Renderer} is capable of rendering formats with the same mime type, but the + * drm scheme used is not supported. + *

+ * Example: The {@link Renderer} is capable of rendering H264 and the format's mime type is + * {@link MimeTypes#VIDEO_H264}, but the format indicates cbcs encryption, which is not supported + * by the underlying content decryption module. + */ + int FORMAT_UNSUPPORTED_DRM = 0b010; /** * The {@link Renderer} is a general purpose renderer for formats of the same top-level type, * but is not capable of rendering the format or any other format with the same mime type because @@ -49,7 +58,7 @@ public interface RendererCapabilities { * Example: The {@link Renderer} is a general purpose audio renderer and the format's * mime type matches audio/[subtype], but there does not exist a suitable decoder for [subtype]. */ - int FORMAT_UNSUPPORTED_SUBTYPE = 0b01; + int FORMAT_UNSUPPORTED_SUBTYPE = 0b001; /** * The {@link Renderer} is not capable of rendering the format, either because it does not * support the format's top-level type, or because it's a specialized renderer for a different @@ -58,40 +67,40 @@ public interface RendererCapabilities { * Example: The {@link Renderer} is a general purpose video renderer, but the format has an * audio mime type. */ - int FORMAT_UNSUPPORTED_TYPE = 0b00; + int FORMAT_UNSUPPORTED_TYPE = 0b000; /** * A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of * {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and {@link #ADAPTIVE_NOT_SUPPORTED}. */ - int ADAPTIVE_SUPPORT_MASK = 0b1100; + int ADAPTIVE_SUPPORT_MASK = 0b11000; /** * The {@link Renderer} can seamlessly adapt between formats. */ - int ADAPTIVE_SEAMLESS = 0b1000; + int ADAPTIVE_SEAMLESS = 0b10000; /** * The {@link Renderer} can adapt between formats, but may suffer a brief discontinuity * (~50-100ms) when adaptation occurs. */ - int ADAPTIVE_NOT_SEAMLESS = 0b0100; + int ADAPTIVE_NOT_SEAMLESS = 0b01000; /** * The {@link Renderer} does not support adaptation between formats. */ - int ADAPTIVE_NOT_SUPPORTED = 0b0000; + int ADAPTIVE_NOT_SUPPORTED = 0b00000; /** * A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of * {@link #TUNNELING_SUPPORTED} and {@link #TUNNELING_NOT_SUPPORTED}. */ - int TUNNELING_SUPPORT_MASK = 0b10000; + int TUNNELING_SUPPORT_MASK = 0b100000; /** * The {@link Renderer} supports tunneled output. */ - int TUNNELING_SUPPORTED = 0b10000; + int TUNNELING_SUPPORTED = 0b100000; /** * The {@link Renderer} does not support tunneled output. */ - int TUNNELING_NOT_SUPPORTED = 0b00000; + int TUNNELING_NOT_SUPPORTED = 0b000000; /** * Returns the track type that the {@link Renderer} handles. For example, a video renderer will diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index 68eba76b119..cafbe6e8f7b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -25,6 +25,7 @@ import android.os.Looper; import android.os.Message; import android.support.annotation.IntDef; +import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -306,6 +307,26 @@ public void setMode(@Mode int mode, byte[] offlineLicenseKeySetId) { // DrmSessionManager implementation. + @Override + public boolean canAcquireSession(@NonNull DrmInitData drmInitData) { + SchemeData schemeData = drmInitData.get(uuid); + if (schemeData == null) { + // No data for this manager's scheme. + return false; + } + String schemeType = schemeData.type; + if (schemeType == null || C.CENC_TYPE_cenc.equals(schemeType)) { + // If there is no scheme information, assume patternless AES-CTR. + return true; + } else if (C.CENC_TYPE_cbc1.equals(schemeType) || C.CENC_TYPE_cbcs.equals(schemeType) + || C.CENC_TYPE_cens.equals(schemeType)) { + // AES-CBC and pattern encryption are supported on API 24 onwards. + return Util.SDK_INT >= 24; + } + // Unknown schemes, assume one of them is supported. + return true; + } + @Override public DrmSession acquireSession(Looper playbackLooper, DrmInitData drmInitData) { Assertions.checkState(this.playbackLooper == null || this.playbackLooper == playbackLooper); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java index 8e63fbfaae6..e4b70598609 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java @@ -24,6 +24,16 @@ @TargetApi(16) public interface DrmSessionManager { + /** + * Returns whether the manager is capable of acquiring a session for the given + * {@link DrmInitData}. + * + * @param drmInitData DRM initialization data. + * @return Whether the manager is capable of acquiring a session for the given + * {@link DrmInitData}. + */ + boolean canAcquireSession(DrmInitData drmInitData); + /** * Acquires a {@link DrmSession} for the specified {@link DrmInitData}. The {@link DrmSession} * must be returned to {@link #releaseSession(DrmSession)} when it is no longer required. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java index b987dad7fb4..d39aae0c5f3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java @@ -84,11 +84,11 @@ private static int schemeToCryptoMode(@Nullable String schemeType) { return C.CRYPTO_MODE_AES_CTR; } switch (schemeType) { - case "cenc": - case "cens": + case C.CENC_TYPE_cenc: + case C.CENC_TYPE_cens: return C.CRYPTO_MODE_AES_CTR; - case "cbc1": - case "cbcs": + case C.CENC_TYPE_cbc1: + case C.CENC_TYPE_cbcs: return C.CRYPTO_MODE_AES_CBC; default: Log.w(TAG, "Unsupported protection scheme type '" + schemeType + "'. Assuming AES-CTR " 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 49b221d5b44..01229c11044 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 @@ -23,6 +23,7 @@ import android.media.MediaFormat; import android.os.Looper; import android.os.SystemClock; +import android.support.annotation.Nullable; import android.util.Log; import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.C; @@ -31,6 +32,7 @@ import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; import com.google.android.exoplayer2.drm.DrmSessionManager; @@ -245,7 +247,14 @@ public final int supportsMixedMimeTypeAdaptation() { @Override public final int supportsFormat(Format format) throws ExoPlaybackException { try { - return supportsFormat(mediaCodecSelector, format); + int formatSupport = supportsFormat(mediaCodecSelector, format); + if ((formatSupport & FORMAT_SUPPORT_MASK) > FORMAT_UNSUPPORTED_DRM + && !isDrmSchemeSupported(drmSessionManager, format.drmInitData)) { + // The renderer advertises higher support than FORMAT_UNSUPPORTED_DRM but the DRM scheme is + // not supported. The format support is truncated to reflect this. + formatSupport = (formatSupport & ~FORMAT_SUPPORT_MASK) | FORMAT_UNSUPPORTED_DRM; + } + return formatSupport; } catch (DecoderQueryException e) { throw ExoPlaybackException.createForRenderer(e, getIndex()); } @@ -1074,6 +1083,25 @@ private boolean shouldSkipOutputBuffer(long presentationTimeUs) { return false; } + /** + * Returns whether the encryption scheme is supported, or true if {@code drmInitData} is null. + * + * @param drmSessionManager The drm session manager associated with the renderer. + * @param drmInitData {@link DrmInitData} of the format to check for support. + * @return Whether the encryption scheme is supported, or true if {@code drmInitData} is null. + */ + private static boolean isDrmSchemeSupported(DrmSessionManager drmSessionManager, + @Nullable DrmInitData drmInitData) { + if (drmInitData == null) { + // Content is unencrypted. + return true; + } else if (drmSessionManager == null) { + // Content is encrypted, but no drm session manager is available. + return false; + } + return drmSessionManager.canAcquireSession(drmInitData); + } + /** * Returns whether the decoder is known to fail when flushed. *