Commit 8c424798 by aquilescanta Committed by Oliver Woodman

Fill manifest drm info with media files' pssh when needed

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=174185407
parent 46172ffd
...@@ -21,6 +21,7 @@ import android.media.MediaFormat; ...@@ -21,6 +21,7 @@ import android.media.MediaFormat;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -464,8 +465,8 @@ public final class Format implements Parcelable { ...@@ -464,8 +465,8 @@ public final class Format implements Parcelable {
float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate; float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate;
@C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags; @C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
String language = this.language == null ? manifestFormat.language : this.language; String language = this.language == null ? manifestFormat.language : this.language;
DrmInitData drmInitData = manifestFormat.drmInitData != null ? manifestFormat.drmInitData DrmInitData drmInitData = manifestFormat.drmInitData != null
: this.drmInitData; ? getFilledManifestDrmData(manifestFormat.drmInitData) : this.drmInitData;
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode,
colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
...@@ -731,4 +732,42 @@ public final class Format implements Parcelable { ...@@ -731,4 +732,42 @@ public final class Format implements Parcelable {
}; };
private DrmInitData getFilledManifestDrmData(DrmInitData manifestDrmData) {
// All exposed SchemeDatas must include key request information.
ArrayList<SchemeData> exposedSchemeDatas = new ArrayList<>();
ArrayList<SchemeData> emptySchemeDatas = new ArrayList<>();
for (int i = 0; i < manifestDrmData.schemeDataCount; i++) {
SchemeData schemeData = manifestDrmData.get(i);
if (schemeData.hasData()) {
exposedSchemeDatas.add(schemeData);
} else /* needs initialization data filling */ {
emptySchemeDatas.add(schemeData);
}
}
if (emptySchemeDatas.isEmpty()) {
// Manifest DRM information is complete.
return manifestDrmData;
} else if (drmInitData == null) {
// The manifest DRM data needs filling but this format does not include enough information to
// do it. A subset of the manifest's scheme datas should not be exposed because a
// DrmSessionManager could decide it does not support the format, while the missing
// information comes in a format feed immediately after.
return null;
}
int needFillingCount = emptySchemeDatas.size();
for (int i = 0; i < drmInitData.schemeDataCount; i++) {
SchemeData mediaSchemeData = drmInitData.get(i);
for (int j = 0; j < needFillingCount; j++) {
if (mediaSchemeData.canReplace(emptySchemeDatas.get(j))) {
exposedSchemeDatas.add(mediaSchemeData);
break;
}
}
}
return exposedSchemeDatas.isEmpty() ? null : new DrmInitData(manifestDrmData.schemeType,
exposedSchemeDatas.toArray(new SchemeData[exposedSchemeDatas.size()]));
}
} }
...@@ -340,7 +340,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -340,7 +340,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
@Override @Override
public boolean canAcquireSession(@NonNull DrmInitData drmInitData) { public boolean canAcquireSession(@NonNull DrmInitData drmInitData) {
SchemeData schemeData = getSchemeData(drmInitData, uuid); SchemeData schemeData = getSchemeData(drmInitData, uuid, true);
if (schemeData == null) { if (schemeData == null) {
// No data for this manager's scheme. // No data for this manager's scheme.
return false; return false;
...@@ -371,7 +371,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -371,7 +371,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
byte[] initData = null; byte[] initData = null;
String mimeType = null; String mimeType = null;
if (offlineLicenseKeySetId == null) { if (offlineLicenseKeySetId == null) {
SchemeData data = getSchemeData(drmInitData, uuid); SchemeData data = getSchemeData(drmInitData, uuid, false);
if (data == null) { if (data == null) {
final IllegalStateException error = new IllegalStateException( final IllegalStateException error = new IllegalStateException(
"Media does not support uuid: " + uuid); "Media does not support uuid: " + uuid);
...@@ -467,15 +467,19 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -467,15 +467,19 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
* *
* @param drmInitData The {@link DrmInitData} from which to extract the {@link SchemeData}. * @param drmInitData The {@link DrmInitData} from which to extract the {@link SchemeData}.
* @param uuid The UUID. * @param uuid The UUID.
* @param allowMissingData Whether a {@link SchemeData} with null {@link SchemeData#data} may be
* returned.
* @return The extracted {@link SchemeData}, or null if no suitable data is present. * @return The extracted {@link SchemeData}, or null if no suitable data is present.
*/ */
private static SchemeData getSchemeData(DrmInitData drmInitData, UUID uuid) { private static SchemeData getSchemeData(DrmInitData drmInitData, UUID uuid,
boolean allowMissingData) {
// Look for matching scheme data (matching the Common PSSH box for ClearKey). // Look for matching scheme data (matching the Common PSSH box for ClearKey).
List<SchemeData> matchingSchemeDatas = new ArrayList<>(drmInitData.schemeDataCount); List<SchemeData> matchingSchemeDatas = new ArrayList<>(drmInitData.schemeDataCount);
for (int i = 0; i < drmInitData.schemeDataCount; i++) { for (int i = 0; i < drmInitData.schemeDataCount; i++) {
SchemeData schemeData = drmInitData.get(i); SchemeData schemeData = drmInitData.get(i);
if (schemeData.matches(uuid) boolean uuidMatches = schemeData.matches(uuid)
|| (C.CLEARKEY_UUID.equals(uuid) && schemeData.matches(C.COMMON_PSSH_UUID))) { || (C.CLEARKEY_UUID.equals(uuid) && schemeData.matches(C.COMMON_PSSH_UUID));
if (uuidMatches && (schemeData.data != null || allowMissingData)) {
matchingSchemeDatas.add(schemeData); matchingSchemeDatas.add(schemeData);
} }
} }
...@@ -488,7 +492,8 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -488,7 +492,8 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
if (C.WIDEVINE_UUID.equals(uuid)) { if (C.WIDEVINE_UUID.equals(uuid)) {
for (int i = 0; i < matchingSchemeDatas.size(); i++) { for (int i = 0; i < matchingSchemeDatas.size(); i++) {
SchemeData matchingSchemeData = matchingSchemeDatas.get(i); SchemeData matchingSchemeData = matchingSchemeDatas.get(i);
int version = PsshAtomUtil.parseVersion(matchingSchemeData.data); int version = matchingSchemeData.hasData()
? PsshAtomUtil.parseVersion(matchingSchemeData.data) : -1;
if (Util.SDK_INT < 23 && version == 0) { if (Util.SDK_INT < 23 && version == 0) {
return matchingSchemeData; return matchingSchemeData;
} else if (Util.SDK_INT >= 23 && version == 1) { } else if (Util.SDK_INT >= 23 && version == 1) {
......
...@@ -55,6 +55,14 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable { ...@@ -55,6 +55,14 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
} }
/** /**
* @param schemeType See {@link #schemeType}.
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
*/
public DrmInitData(String schemeType, List<SchemeData> schemeDatas) {
this(schemeType, false, schemeDatas.toArray(new SchemeData[schemeDatas.size()]));
}
/**
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
*/ */
public DrmInitData(SchemeData... schemeDatas) { public DrmInitData(SchemeData... schemeDatas) {
...@@ -62,7 +70,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable { ...@@ -62,7 +70,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
} }
/** /**
* @param schemeType The protection scheme type, or null if not applicable or unknown. * @param schemeType See {@link #schemeType}.
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
*/ */
public DrmInitData(@Nullable String schemeType, SchemeData... schemeDatas) { public DrmInitData(@Nullable String schemeType, SchemeData... schemeDatas) {
...@@ -203,7 +211,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable { ...@@ -203,7 +211,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
*/ */
public final String mimeType; public final String mimeType;
/** /**
* The initialization data. * The initialization data. May be null for scheme support checks only.
*/ */
public final byte[] data; public final byte[] data;
/** /**
...@@ -214,8 +222,8 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable { ...@@ -214,8 +222,8 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
/** /**
* @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is
* universal (i.e. applies to all schemes). * universal (i.e. applies to all schemes).
* @param mimeType The mimeType of the initialization data. * @param mimeType See {@link #mimeType}.
* @param data The initialization data. * @param data See {@link #data}.
*/ */
public SchemeData(UUID uuid, String mimeType, byte[] data) { public SchemeData(UUID uuid, String mimeType, byte[] data) {
this(uuid, mimeType, data, false); this(uuid, mimeType, data, false);
...@@ -224,14 +232,14 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable { ...@@ -224,14 +232,14 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
/** /**
* @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is
* universal (i.e. applies to all schemes). * universal (i.e. applies to all schemes).
* @param mimeType The mimeType of the initialization data. * @param mimeType See {@link #mimeType}.
* @param data The initialization data. * @param data See {@link #data}.
* @param requiresSecureDecryption Whether secure decryption is required. * @param requiresSecureDecryption See {@link #requiresSecureDecryption}.
*/ */
public SchemeData(UUID uuid, String mimeType, byte[] data, boolean requiresSecureDecryption) { public SchemeData(UUID uuid, String mimeType, byte[] data, boolean requiresSecureDecryption) {
this.uuid = Assertions.checkNotNull(uuid); this.uuid = Assertions.checkNotNull(uuid);
this.mimeType = Assertions.checkNotNull(mimeType); this.mimeType = Assertions.checkNotNull(mimeType);
this.data = Assertions.checkNotNull(data); this.data = data;
this.requiresSecureDecryption = requiresSecureDecryption; this.requiresSecureDecryption = requiresSecureDecryption;
} }
...@@ -252,6 +260,23 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable { ...@@ -252,6 +260,23 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
return C.UUID_NIL.equals(uuid) || schemeUuid.equals(uuid); return C.UUID_NIL.equals(uuid) || schemeUuid.equals(uuid);
} }
/**
* Returns whether this {@link SchemeData} can be used to replace {@code other}.
*
* @param other A {@link SchemeData}.
* @return Whether this {@link SchemeData} can be used to replace {@code other}.
*/
public boolean canReplace(SchemeData other) {
return hasData() && !other.hasData() && matches(other.uuid);
}
/**
* Returns whether {@link #data} is non-null.
*/
public boolean hasData() {
return data != null;
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (!(obj instanceof SchemeData)) { if (!(obj instanceof SchemeData)) {
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.drm; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.drm;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.os.Looper; import android.os.Looper;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
/** /**
* Manages a DRM session. * Manages a DRM session.
...@@ -39,7 +40,8 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> { ...@@ -39,7 +40,8 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> {
* must be returned to {@link #releaseSession(DrmSession)} when it is no longer required. * must be returned to {@link #releaseSession(DrmSession)} when it is no longer required.
* *
* @param playbackLooper The looper associated with the media playback thread. * @param playbackLooper The looper associated with the media playback thread.
* @param drmInitData DRM initialization data. * @param drmInitData DRM initialization data. All contained {@link SchemeData}s must contain
* non-null {@link SchemeData#data}.
* @return The DRM session. * @return The DRM session.
*/ */
DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData); DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData);
......
...@@ -346,45 +346,54 @@ public class DashManifestParser extends DefaultHandler ...@@ -346,45 +346,54 @@ public class DashManifestParser extends DefaultHandler
protected Pair<String, SchemeData> parseContentProtection(XmlPullParser xpp) protected Pair<String, SchemeData> parseContentProtection(XmlPullParser xpp)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri");
boolean isPlayReady = "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95".equals(schemeIdUri);
String schemeType = null; String schemeType = null;
byte[] data = null; byte[] data = null;
UUID uuid = null; UUID uuid = null;
boolean requiresSecureDecoder = false; boolean requiresSecureDecoder = false;
if ("urn:mpeg:dash:mp4protection:2011".equals(schemeIdUri)) { switch (schemeIdUri) {
schemeType = xpp.getAttributeValue(null, "value"); case "urn:mpeg:dash:mp4protection:2011":
String defaultKid = xpp.getAttributeValue(null, "cenc:default_KID"); schemeType = xpp.getAttributeValue(null, "value");
if (defaultKid != null && !"00000000-0000-0000-0000-000000000000".equals(defaultKid)) { String defaultKid = xpp.getAttributeValue(null, "cenc:default_KID");
UUID keyId = UUID.fromString(defaultKid); if (defaultKid != null && !"00000000-0000-0000-0000-000000000000".equals(defaultKid)) {
data = PsshAtomUtil.buildPsshAtom(C.COMMON_PSSH_UUID, new UUID[] {keyId}, null); UUID keyId = UUID.fromString(defaultKid);
uuid = C.COMMON_PSSH_UUID; data = PsshAtomUtil.buildPsshAtom(C.COMMON_PSSH_UUID, new UUID[] {keyId}, null);
} uuid = C.COMMON_PSSH_UUID;
}
break;
case "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95":
uuid = C.PLAYREADY_UUID;
break;
case "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed":
uuid = C.WIDEVINE_UUID;
break;
default:
break;
} }
do { do {
xpp.next(); xpp.next();
if (data == null && XmlPullParserUtil.isStartTag(xpp, "cenc:pssh") if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) {
&& xpp.next() == XmlPullParser.TEXT) {
// The cenc:pssh element is defined in 23001-7:2015.
data = Base64.decode(xpp.getText(), Base64.DEFAULT);
uuid = PsshAtomUtil.parseUuid(data);
if (uuid == null) {
Log.w(TAG, "Skipping malformed cenc:pssh data");
data = null;
}
} else if (data == null && isPlayReady && XmlPullParserUtil.isStartTag(xpp, "mspr:pro")
&& xpp.next() == XmlPullParser.TEXT) {
// The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady.
data = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID,
Base64.decode(xpp.getText(), Base64.DEFAULT));
uuid = C.PLAYREADY_UUID;
} else if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) {
String robustnessLevel = xpp.getAttributeValue(null, "robustness_level"); String robustnessLevel = xpp.getAttributeValue(null, "robustness_level");
requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW"); requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW");
} else if (data == null) {
if (XmlPullParserUtil.isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) {
// The cenc:pssh element is defined in 23001-7:2015.
data = Base64.decode(xpp.getText(), Base64.DEFAULT);
uuid = PsshAtomUtil.parseUuid(data);
if (uuid == null) {
Log.w(TAG, "Skipping malformed cenc:pssh data");
data = null;
}
} else if (uuid == C.PLAYREADY_UUID && XmlPullParserUtil.isStartTag(xpp, "mspr:pro")
&& xpp.next() == XmlPullParser.TEXT) {
// The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady.
data = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID,
Base64.decode(xpp.getText(), Base64.DEFAULT));
}
} }
} while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection")); } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection"));
SchemeData schemeData = data != null SchemeData schemeData = uuid != null
? new SchemeData(uuid, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder) : null; ? new SchemeData(uuid, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder) : null;
return Pair.create(schemeType, schemeData); return Pair.create(schemeType, schemeData);
} }
...@@ -518,10 +527,8 @@ public class DashManifestParser extends DefaultHandler ...@@ -518,10 +527,8 @@ public class DashManifestParser extends DefaultHandler
ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas; ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas;
drmSchemeDatas.addAll(extraDrmSchemeDatas); drmSchemeDatas.addAll(extraDrmSchemeDatas);
if (!drmSchemeDatas.isEmpty()) { if (!drmSchemeDatas.isEmpty()) {
DrmInitData drmInitData = new DrmInitData(drmSchemeDatas); filterRedundantIncompleteSchemeDatas(drmSchemeDatas);
if (drmSchemeType != null) { DrmInitData drmInitData = new DrmInitData(drmSchemeType, drmSchemeDatas);
drmInitData = drmInitData.copyWithSchemeType(drmSchemeType);
}
format = format.copyWithDrmInitData(drmInitData); format = format.copyWithDrmInitData(drmInitData);
} }
ArrayList<Descriptor> inbandEventStreams = representationInfo.inbandEventStreams; ArrayList<Descriptor> inbandEventStreams = representationInfo.inbandEventStreams;
...@@ -729,6 +736,25 @@ public class DashManifestParser extends DefaultHandler ...@@ -729,6 +736,25 @@ public class DashManifestParser extends DefaultHandler
// Utility methods. // Utility methods.
/** /**
* Removes unnecessary {@link SchemeData}s with null {@link SchemeData#data}.
*/
private static void filterRedundantIncompleteSchemeDatas(ArrayList<SchemeData> schemeDatas) {
for (int i = schemeDatas.size() - 1; i >= 0; i--) {
SchemeData schemeData = schemeDatas.get(i);
if (!schemeData.hasData()) {
for (int j = 0; j < schemeDatas.size(); j++) {
if (schemeDatas.get(j).canReplace(schemeData)) {
// schemeData is incomplete, but there is another matching SchemeData which does contain
// data, so we remove the incomplete one.
schemeDatas.remove(i);
break;
}
}
}
}
}
/**
* Derives a sample mimeType from a container mimeType and codecs attribute. * Derives a sample mimeType from a container mimeType and codecs attribute.
* *
* @param containerMimeType The mimeType of the container. * @param containerMimeType The mimeType of the container.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment