Commit aca80b83 by aquilescanta Committed by Oliver Woodman

Add support for pattern encryption and default initialization vectors

This will extend our CENC modes support to cbcs and cens. The change was
not split into two different CLs due to lack of test content for
default initialization vectors, aside from AES-CBCS encrypted ones.

Issue:#1661
Issue:#1989
Issue:#2089

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=159810371
parent 73b17a7e
......@@ -184,6 +184,30 @@
"uri": "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1_uhd.mpd",
"drm_scheme": "widevine",
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
},
{
"name": "WV: Secure SD & HD (cbcs,MP4,H264)",
"uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd",
"drm_scheme": "widevine",
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
},
{
"name": "WV: Secure SD (cbcs,MP4,H264)",
"uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs_sd.mpd",
"drm_scheme": "widevine",
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
},
{
"name": "WV: Secure HD (cbcs,MP4,H264)",
"uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs_hd.mpd",
"drm_scheme": "widevine",
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
},
{
"name": "WV: Secure UHD (cbcs,MP4,H264)",
"uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs_uhd.mpd",
"drm_scheme": "widevine",
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
}
]
},
......
......@@ -52,11 +52,11 @@ public final class CryptoInfo {
/**
* @see android.media.MediaCodec.CryptoInfo.Pattern
*/
public int patternBlocksToEncrypt;
public int encryptedBlocks;
/**
* @see android.media.MediaCodec.CryptoInfo.Pattern
*/
public int patternBlocksToSkip;
public int clearBlocks;
private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo;
private final PatternHolderV24 patternHolder;
......@@ -70,28 +70,20 @@ public final class CryptoInfo {
* @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int)
*/
public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
byte[] key, byte[] iv, @C.CryptoMode int mode) {
byte[] key, byte[] iv, @C.CryptoMode int mode, int encryptedBlocks, int clearBlocks) {
this.numSubSamples = numSubSamples;
this.numBytesOfClearData = numBytesOfClearData;
this.numBytesOfEncryptedData = numBytesOfEncryptedData;
this.key = key;
this.iv = iv;
this.mode = mode;
patternBlocksToEncrypt = 0;
patternBlocksToSkip = 0;
this.encryptedBlocks = encryptedBlocks;
this.clearBlocks = clearBlocks;
if (Util.SDK_INT >= 16) {
updateFrameworkCryptoInfoV16();
}
}
public void setPattern(int patternBlocksToEncrypt, int patternBlocksToSkip) {
this.patternBlocksToEncrypt = patternBlocksToEncrypt;
this.patternBlocksToSkip = patternBlocksToSkip;
if (Util.SDK_INT >= 24) {
patternHolder.set(patternBlocksToEncrypt, patternBlocksToSkip);
}
}
/**
* Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance.
* <p>
......@@ -122,7 +114,7 @@ public final class CryptoInfo {
frameworkCryptoInfo.iv = iv;
frameworkCryptoInfo.mode = mode;
if (Util.SDK_INT >= 24) {
patternHolder.set(patternBlocksToEncrypt, patternBlocksToSkip);
patternHolder.set(encryptedBlocks, clearBlocks);
}
}
......@@ -137,8 +129,8 @@ public final class CryptoInfo {
pattern = new android.media.MediaCodec.CryptoInfo.Pattern(0, 0);
}
private void set(int blocksToEncrypt, int blocksToSkip) {
pattern.set(blocksToEncrypt, blocksToSkip);
private void set(int encryptedBlocks, int clearBlocks) {
pattern.set(encryptedBlocks, clearBlocks);
frameworkCryptoInfo.setPattern(pattern);
}
......
......@@ -42,9 +42,30 @@ public interface TrackOutput {
*/
public final byte[] encryptionKey;
public CryptoData(@C.CryptoMode int cryptoMode, byte[] encryptionKey) {
/**
* The number of encrypted blocks in the encryption pattern, 0 if pattern encryption does not
* apply.
*/
public final int encryptedBlocks;
/**
* The number of clear blocks in the encryption pattern, 0 if pattern encryption does not
* apply.
*/
public final int clearBlocks;
/**
* @param cryptoMode See {@link #cryptoMode}.
* @param encryptionKey See {@link #encryptionKey}.
* @param encryptedBlocks See {@link #encryptedBlocks}.
* @param clearBlocks See {@link #clearBlocks}.
*/
public CryptoData(@C.CryptoMode int cryptoMode, byte[] encryptionKey, int encryptedBlocks,
int clearBlocks) {
this.cryptoMode = cryptoMode;
this.encryptionKey = encryptionKey;
this.encryptedBlocks = encryptedBlocks;
this.clearBlocks = clearBlocks;
}
@Override
......@@ -56,13 +77,16 @@ public interface TrackOutput {
return false;
}
CryptoData other = (CryptoData) obj;
return cryptoMode == other.cryptoMode && Arrays.equals(encryptionKey, other.encryptionKey);
return cryptoMode == other.cryptoMode && encryptedBlocks == other.encryptedBlocks
&& clearBlocks == other.clearBlocks && Arrays.equals(encryptionKey, other.encryptionKey);
}
@Override
public int hashCode() {
int result = cryptoMode;
result = 31 * result + Arrays.hashCode(encryptionKey);
result = 31 * result + encryptedBlocks;
result = 31 * result + clearBlocks;
return result;
}
......
......@@ -893,7 +893,8 @@ public final class MatroskaExtractor implements Extractor {
case ID_CONTENT_ENCRYPTION_KEY_ID:
byte[] encryptionKey = new byte[contentSize];
input.readFully(encryptionKey, 0, contentSize);
currentTrack.cryptoData = new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, encryptionKey);
currentTrack.cryptoData = new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, encryptionKey,
0, 0); // We assume patternless AES-CTR.
break;
case ID_SIMPLE_BLOCK:
case ID_BLOCK:
......
......@@ -1105,13 +1105,30 @@ import java.util.List;
int childAtomSize = parent.readInt();
int childAtomType = parent.readInt();
if (childAtomType == Atom.TYPE_tenc) {
parent.skipBytes(6);
boolean defaultIsEncrypted = parent.readUnsignedByte() == 1;
int defaultInitVectorSize = parent.readUnsignedByte();
int fullAtom = parent.readInt();
int version = Atom.parseFullAtomVersion(fullAtom);
parent.skipBytes(1); // reserved = 0.
int defaultCryptByteBlock = 0;
int defaultSkipByteBlock = 0;
if (version == 0) {
parent.skipBytes(1); // reserved = 0.
} else /* version 1 or greater */ {
int patternByte = parent.readUnsignedByte();
defaultCryptByteBlock = (patternByte & 0xF0) >> 4;
defaultSkipByteBlock = patternByte & 0x0F;
}
boolean defaultIsProtected = parent.readUnsignedByte() == 1;
int defaultPerSampleIvSize = parent.readUnsignedByte();
byte[] defaultKeyId = new byte[16];
parent.readBytes(defaultKeyId, 0, defaultKeyId.length);
return new TrackEncryptionBox(defaultIsEncrypted, schemeType, defaultInitVectorSize,
defaultKeyId);
byte[] constantIv = null;
if (defaultIsProtected && defaultPerSampleIvSize == 0) {
int constantIvSize = parent.readUnsignedByte();
constantIv = new byte[constantIvSize];
parent.readBytes(constantIv, 0, constantIvSize);
}
return new TrackEncryptionBox(defaultIsProtected, schemeType, defaultPerSampleIvSize,
defaultKeyId, defaultCryptByteBlock, defaultSkipByteBlock, constantIv);
}
childPosition += childAtomSize;
}
......
......@@ -128,6 +128,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private final ParsableByteArray nalPrefix;
private final ParsableByteArray nalBuffer;
private final ParsableByteArray encryptionSignalByte;
private final ParsableByteArray defaultInitializationVector;
// Adjusts sample timestamps.
private final TimestampAdjuster timestampAdjuster;
......@@ -197,6 +198,7 @@ public final class FragmentedMp4Extractor implements Extractor {
nalPrefix = new ParsableByteArray(5);
nalBuffer = new ParsableByteArray();
encryptionSignalByte = new ParsableByteArray(1);
defaultInitializationVector = new ParsableByteArray();
extendedTypeScratch = new byte[16];
containerAtoms = new Stack<>();
pendingMetadataSampleInfos = new LinkedList<>();
......@@ -879,9 +881,9 @@ public final class FragmentedMp4Extractor implements Extractor {
return;
}
if (Atom.parseFullAtomVersion(sbgpFullAtom) == 1) {
sbgp.skipBytes(4);
sbgp.skipBytes(4); // default_length.
}
if (sbgp.readInt() != 1) {
if (sbgp.readInt() != 1) { // entry_count.
throw new ParserException("Entry count in sbgp != 1 (unsupported).");
}
......@@ -894,25 +896,35 @@ public final class FragmentedMp4Extractor implements Extractor {
int sgpdVersion = Atom.parseFullAtomVersion(sgpdFullAtom);
if (sgpdVersion == 1) {
if (sgpd.readUnsignedInt() == 0) {
throw new ParserException("Variable length decription in sgpd found (unsupported)");
throw new ParserException("Variable length description in sgpd found (unsupported)");
}
} else if (sgpdVersion >= 2) {
sgpd.skipBytes(4);
sgpd.skipBytes(4); // default_sample_description_index.
}
if (sgpd.readUnsignedInt() != 1) {
if (sgpd.readUnsignedInt() != 1) { // entry_count.
throw new ParserException("Entry count in sgpd != 1 (unsupported).");
}
// CencSampleEncryptionInformationGroupEntry
sgpd.skipBytes(2);
sgpd.skipBytes(1); // reserved = 0.
int patternByte = sgpd.readUnsignedByte();
int cryptByteBlock = (patternByte & 0xF0) >> 4;
int skipByteBlock = patternByte & 0x0F;
boolean isProtected = sgpd.readUnsignedByte() == 1;
if (!isProtected) {
return;
}
int initVectorSize = sgpd.readUnsignedByte();
int perSampleIvSize = sgpd.readUnsignedByte();
byte[] keyId = new byte[16];
sgpd.readBytes(keyId, 0, keyId.length);
byte[] constantIv = null;
if (isProtected && perSampleIvSize == 0) {
int constantIvSize = sgpd.readUnsignedByte();
constantIv = new byte[constantIvSize];
sgpd.readBytes(constantIv, 0, constantIvSize);
}
out.definesEncryptionData = true;
out.trackEncryptionBox = new TrackEncryptionBox(isProtected, schemeType, initVectorSize, keyId);
out.trackEncryptionBox = new TrackEncryptionBox(isProtected, schemeType, perSampleIvSize, keyId,
cryptByteBlock, skipByteBlock, constantIv);
}
/**
......@@ -1197,12 +1209,24 @@ public final class FragmentedMp4Extractor implements Extractor {
*/
private int appendSampleEncryptionData(TrackBundle trackBundle) {
TrackFragment trackFragment = trackBundle.fragment;
ParsableByteArray sampleEncryptionData = trackFragment.sampleEncryptionData;
int sampleDescriptionIndex = trackFragment.header.sampleDescriptionIndex;
TrackEncryptionBox encryptionBox = trackFragment.trackEncryptionBox != null
? trackFragment.trackEncryptionBox
: trackBundle.track.getSampleDescriptionEncryptionBox(sampleDescriptionIndex);
int vectorSize = encryptionBox.initializationVectorSize;
ParsableByteArray initializationVectorData;
int vectorSize;
if (encryptionBox.initializationVectorSize != 0) {
initializationVectorData = trackFragment.sampleEncryptionData;
vectorSize = encryptionBox.initializationVectorSize;
} else {
// The default initialization vector should be used.
byte[] initVectorData = encryptionBox.defaultInitializationVector;
defaultInitializationVector.reset(initVectorData, initVectorData.length);
initializationVectorData = defaultInitializationVector;
vectorSize = initVectorData.length;
}
boolean subsampleEncryption = trackFragment
.sampleHasSubsampleEncryptionTable[trackBundle.currentSampleIndex];
......@@ -1212,20 +1236,20 @@ public final class FragmentedMp4Extractor implements Extractor {
TrackOutput output = trackBundle.output;
output.sampleData(encryptionSignalByte, 1);
// Write the vector.
output.sampleData(sampleEncryptionData, vectorSize);
output.sampleData(initializationVectorData, vectorSize);
// If we don't have subsample encryption data, we're done.
if (!subsampleEncryption) {
return 1 + vectorSize;
}
// Write the subsample encryption data.
int subsampleCount = sampleEncryptionData.readUnsignedShort();
sampleEncryptionData.skipBytes(-2);
ParsableByteArray subsampleEncryptionData = trackFragment.sampleEncryptionData;
int subsampleCount = subsampleEncryptionData.readUnsignedShort();
subsampleEncryptionData.skipBytes(-2);
int subsampleDataLength = 2 + 6 * subsampleCount;
output.sampleData(sampleEncryptionData, subsampleDataLength);
output.sampleData(subsampleEncryptionData, subsampleDataLength);
return 1 + vectorSize + subsampleDataLength;
}
/** Returns DrmInitData from leaf atoms. */
private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) {
ArrayList<SchemeData> schemeDatas = null;
......
......@@ -19,6 +19,7 @@ import android.support.annotation.Nullable;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.Assertions;
/**
* Encapsulates information parsed from a track encryption (tenc) box or sample group description
......@@ -49,19 +50,31 @@ public final class TrackEncryptionBox {
*/
public final int initializationVectorSize;
/**
* If {@link #initializationVectorSize} is 0, holds the default initialization vector as defined
* in the track encryption box or sample group description box. Null otherwise.
*/
public final byte[] defaultInitializationVector;
/**
* @param isEncrypted See {@link #isEncrypted}.
* @param schemeType See {@link #schemeType}.
* @param initializationVectorSize See {@link #initializationVectorSize}.
* @param keyId See {@link TrackOutput.CryptoData#encryptionKey}.
* @param defaultEncryptedBlocks See {@link TrackOutput.CryptoData#encryptedBlocks}.
* @param defaultClearBlocks See {@link TrackOutput.CryptoData#clearBlocks}.
* @param defaultInitializationVector See {@link #defaultInitializationVector}.
*/
public TrackEncryptionBox(boolean isEncrypted, @Nullable String schemeType,
int initializationVectorSize, byte[] keyId) {
int initializationVectorSize, byte[] keyId, int defaultEncryptedBlocks,
int defaultClearBlocks, @Nullable byte[] defaultInitializationVector) {
Assertions.checkArgument(initializationVectorSize == 0 ^ defaultInitializationVector == null);
this.isEncrypted = isEncrypted;
this.schemeType = schemeType;
this.initializationVectorSize = initializationVectorSize;
cryptoData = new TrackOutput.CryptoData(schemeToCryptoMode(schemeType), keyId);
this.defaultInitializationVector = defaultInitializationVector;
cryptoData = new TrackOutput.CryptoData(schemeToCryptoMode(schemeType), keyId,
defaultEncryptedBlocks, defaultClearBlocks);
}
@C.CryptoMode
......@@ -72,8 +85,10 @@ public final class TrackEncryptionBox {
}
switch (schemeType) {
case "cenc":
case "cens":
return C.CRYPTO_MODE_AES_CTR;
case "cbc1":
case "cbcs":
return C.CRYPTO_MODE_AES_CBC;
default:
Log.w(TAG, "Unsupported protection scheme type '" + schemeType + "'. Assuming AES-CTR "
......
......@@ -426,7 +426,8 @@ public final class SampleQueue implements TrackOutput {
// Populate the cryptoInfo.
CryptoData cryptoData = extrasHolder.cryptoData;
buffer.cryptoInfo.set(subsampleCount, clearDataSizes, encryptedDataSizes,
cryptoData.encryptionKey, buffer.cryptoInfo.iv, cryptoData.cryptoMode);
cryptoData.encryptionKey, buffer.cryptoInfo.iv, cryptoData.cryptoMode,
cryptoData.encryptedBlocks, cryptoData.clearBlocks);
// Adjust the offset and size to take into account the bytes read.
int bytesRead = (int) (offset - extrasHolder.offset);
......
......@@ -68,8 +68,9 @@ import java.util.ArrayList;
ProtectionElement protectionElement = manifest.protectionElement;
if (protectionElement != null) {
byte[] keyId = getProtectionElementKeyId(protectionElement.data);
// We assume pattern encryption does not apply.
trackEncryptionBoxes = new TrackEncryptionBox[] {
new TrackEncryptionBox(true, null, INITIALIZATION_VECTOR_SIZE, keyId)};
new TrackEncryptionBox(true, null, INITIALIZATION_VECTOR_SIZE, keyId, 0, 0, null)};
} else {
trackEncryptionBoxes = null;
}
......
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