diff --git a/library/core/src/androidTest/assets/mkv/subsample_encrypted_altref.webm.0.dump b/library/core/src/androidTest/assets/mkv/subsample_encrypted_altref.webm.0.dump index 1932ab78f7..f533e14c3f 100644 --- a/library/core/src/androidTest/assets/mkv/subsample_encrypted_altref.webm.0.dump +++ b/library/core/src/androidTest/assets/mkv/subsample_encrypted_altref.webm.0.dump @@ -30,5 +30,6 @@ track 1: time = 0 flags = 1073741824 data = length 39, hash B7FE77F4 + crypto mode = 1 encryption key = length 16, hash 4CE944CF tracksEnded = true diff --git a/library/core/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm.0.dump b/library/core/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm.0.dump index 8751c99b20..d84c549dea 100644 --- a/library/core/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm.0.dump +++ b/library/core/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm.0.dump @@ -30,5 +30,6 @@ track 1: time = 0 flags = 1073741824 data = length 24, hash E58668B1 + crypto mode = 1 encryption key = length 16, hash 4CE944CF tracksEnded = true 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 35a69df39e..a9eeea6be3 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 @@ -83,12 +83,12 @@ public final class C { public static final String UTF16_NAME = "UTF-16"; /** - * * The name of the serif font family. + * The name of the serif font family. */ public static final String SERIF_NAME = "serif"; /** - * * The name of the sans-serif font family. + * The name of the sans-serif font family. */ public static final String SANS_SERIF_NAME = "sans-serif"; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java index 1c9a148226..c879d8e695 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java @@ -366,8 +366,9 @@ public final class DefaultTrackOutput implements TrackOutput { } // Populate the cryptoInfo. + CryptoData cryptoData = extrasHolder.cryptoData; buffer.cryptoInfo.set(subsampleCount, clearDataSizes, encryptedDataSizes, - extrasHolder.encryptionKeyId, buffer.cryptoInfo.iv, C.CRYPTO_MODE_AES_CTR); + cryptoData.encryptionKey, buffer.cryptoInfo.iv, cryptoData.cryptoMode); // Adjust the offset and size to take into account the bytes read. int bytesRead = (int) (offset - extrasHolder.offset); @@ -516,7 +517,7 @@ public final class DefaultTrackOutput implements TrackOutput { @Override public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, - byte[] encryptionKey) { + CryptoData cryptoData) { if (pendingFormatAdjustment) { format(lastUnadjustedFormat); } @@ -533,7 +534,7 @@ public final class DefaultTrackOutput implements TrackOutput { } timeUs += sampleOffsetUs; long absoluteOffset = totalBytesWritten - size - offset; - infoQueue.commitSample(timeUs, flags, absoluteOffset, size, encryptionKey); + infoQueue.commitSample(timeUs, flags, absoluteOffset, size, cryptoData); } finally { endWriteOperation(); } @@ -606,7 +607,7 @@ public final class DefaultTrackOutput implements TrackOutput { private int[] sizes; private int[] flags; private long[] timesUs; - private byte[][] encryptionKeys; + private CryptoData[] cryptoDatas; private Format[] formats; private int queueSize; @@ -628,7 +629,7 @@ public final class DefaultTrackOutput implements TrackOutput { timesUs = new long[capacity]; flags = new int[capacity]; sizes = new int[capacity]; - encryptionKeys = new byte[capacity][]; + cryptoDatas = new CryptoData[capacity]; formats = new Format[capacity]; largestDequeuedTimestampUs = Long.MIN_VALUE; largestQueuedTimestampUs = Long.MIN_VALUE; @@ -792,7 +793,7 @@ public final class DefaultTrackOutput implements TrackOutput { buffer.setFlags(flags[relativeReadIndex]); extrasHolder.size = sizes[relativeReadIndex]; extrasHolder.offset = offsets[relativeReadIndex]; - extrasHolder.encryptionKeyId = encryptionKeys[relativeReadIndex]; + extrasHolder.cryptoData = cryptoDatas[relativeReadIndex]; largestDequeuedTimestampUs = Math.max(largestDequeuedTimestampUs, buffer.timeUs); queueSize--; @@ -892,7 +893,7 @@ public final class DefaultTrackOutput implements TrackOutput { } public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset, - int size, byte[] encryptionKey) { + int size, CryptoData cryptoData) { if (upstreamKeyframeRequired) { if ((sampleFlags & C.BUFFER_FLAG_KEY_FRAME) == 0) { return; @@ -905,7 +906,7 @@ public final class DefaultTrackOutput implements TrackOutput { offsets[relativeWriteIndex] = offset; sizes[relativeWriteIndex] = size; flags[relativeWriteIndex] = sampleFlags; - encryptionKeys[relativeWriteIndex] = encryptionKey; + cryptoDatas[relativeWriteIndex] = cryptoData; formats[relativeWriteIndex] = upstreamFormat; sourceIds[relativeWriteIndex] = upstreamSourceId; // Increment the write index. @@ -918,14 +919,14 @@ public final class DefaultTrackOutput implements TrackOutput { long[] newTimesUs = new long[newCapacity]; int[] newFlags = new int[newCapacity]; int[] newSizes = new int[newCapacity]; - byte[][] newEncryptionKeys = new byte[newCapacity][]; + CryptoData[] newCryptoDatas = new CryptoData[newCapacity]; Format[] newFormats = new Format[newCapacity]; int beforeWrap = capacity - relativeReadIndex; System.arraycopy(offsets, relativeReadIndex, newOffsets, 0, beforeWrap); System.arraycopy(timesUs, relativeReadIndex, newTimesUs, 0, beforeWrap); System.arraycopy(flags, relativeReadIndex, newFlags, 0, beforeWrap); System.arraycopy(sizes, relativeReadIndex, newSizes, 0, beforeWrap); - System.arraycopy(encryptionKeys, relativeReadIndex, newEncryptionKeys, 0, beforeWrap); + System.arraycopy(cryptoDatas, relativeReadIndex, newCryptoDatas, 0, beforeWrap); System.arraycopy(formats, relativeReadIndex, newFormats, 0, beforeWrap); System.arraycopy(sourceIds, relativeReadIndex, newSourceIds, 0, beforeWrap); int afterWrap = relativeReadIndex; @@ -933,14 +934,14 @@ public final class DefaultTrackOutput implements TrackOutput { System.arraycopy(timesUs, 0, newTimesUs, beforeWrap, afterWrap); System.arraycopy(flags, 0, newFlags, beforeWrap, afterWrap); System.arraycopy(sizes, 0, newSizes, beforeWrap, afterWrap); - System.arraycopy(encryptionKeys, 0, newEncryptionKeys, beforeWrap, afterWrap); + System.arraycopy(cryptoDatas, 0, newCryptoDatas, beforeWrap, afterWrap); System.arraycopy(formats, 0, newFormats, beforeWrap, afterWrap); System.arraycopy(sourceIds, 0, newSourceIds, beforeWrap, afterWrap); offsets = newOffsets; timesUs = newTimesUs; flags = newFlags; sizes = newSizes; - encryptionKeys = newEncryptionKeys; + cryptoDatas = newCryptoDatas; formats = newFormats; sourceIds = newSourceIds; relativeReadIndex = 0; @@ -990,7 +991,7 @@ public final class DefaultTrackOutput implements TrackOutput { public int size; public long offset; public long nextOffset; - public byte[] encryptionKeyId; + public CryptoData cryptoData; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java index 61f97887be..c023b0de95 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java @@ -51,7 +51,7 @@ public final class DummyTrackOutput implements TrackOutput { @Override public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, - byte[] encryptionKey) { + CryptoData cryptoData) { // Do nothing. } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java index c4dee4b6a7..2054854796 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java @@ -20,12 +20,54 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.EOFException; import java.io.IOException; +import java.util.Arrays; /** * Receives track level data extracted by an {@link Extractor}. */ public interface TrackOutput { + /** + * Holds data required to decrypt a sample. + */ + final class CryptoData { + + /** + * The encryption mode used for the sample. + */ + @C.CryptoMode public final int cryptoMode; + + /** + * The encryption key associated with the sample. Its contents must not be modified. + */ + public final byte[] encryptionKey; + + public CryptoData(@C.CryptoMode int cryptoMode, byte[] encryptionKey) { + this.cryptoMode = cryptoMode; + this.encryptionKey = encryptionKey; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + CryptoData other = (CryptoData) obj; + return cryptoMode == other.cryptoMode && Arrays.equals(encryptionKey, other.encryptionKey); + } + + @Override + public int hashCode() { + int result = cryptoMode; + result = 31 * result + Arrays.hashCode(encryptionKey); + return result; + } + + } + /** * Called when the {@link Format} of the track has been extracted from the stream. * @@ -70,9 +112,9 @@ public interface TrackOutput { * {@link #sampleData(ExtractorInput, int, boolean)} or * {@link #sampleData(ParsableByteArray, int)} since the last byte belonging to the sample * whose metadata is being passed. - * @param encryptionKey The encryption key associated with the sample. May be null. + * @param encryptionData The encryption data required to decrypt the sample. May be null. */ void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, - byte[] encryptionKey); + CryptoData encryptionData); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 8f3abf4688..227cbd6f0c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -580,11 +580,11 @@ public final class MatroskaExtractor implements Extractor { break; case ID_CONTENT_ENCODING: if (currentTrack.hasContentEncryption) { - if (currentTrack.encryptionKeyId == null) { + if (currentTrack.cryptoData == null) { throw new ParserException("Encrypted Track found but ContentEncKeyID was not found"); } - currentTrack.drmInitData = new DrmInitData( - new SchemeData(C.UUID_NIL, MimeTypes.VIDEO_WEBM, currentTrack.encryptionKeyId)); + currentTrack.drmInitData = new DrmInitData(new SchemeData(C.UUID_NIL, + MimeTypes.VIDEO_WEBM, currentTrack.cryptoData.encryptionKey)); } break; case ID_CONTENT_ENCODINGS: @@ -888,8 +888,9 @@ public final class MatroskaExtractor implements Extractor { input.readFully(currentTrack.sampleStrippedBytes, 0, contentSize); break; case ID_CONTENT_ENCRYPTION_KEY_ID: - currentTrack.encryptionKeyId = new byte[contentSize]; - input.readFully(currentTrack.encryptionKeyId, 0, contentSize); + byte[] encryptionKey = new byte[contentSize]; + input.readFully(encryptionKey, 0, contentSize); + currentTrack.cryptoData = new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, encryptionKey); break; case ID_SIMPLE_BLOCK: case ID_BLOCK: @@ -1033,7 +1034,7 @@ public final class MatroskaExtractor implements Extractor { if (CODEC_ID_SUBRIP.equals(track.codecId)) { writeSubripSample(track); } - track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.encryptionKeyId); + track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.cryptoData); sampleRead = true; resetSample(); } @@ -1470,7 +1471,7 @@ public final class MatroskaExtractor implements Extractor { public int defaultSampleDurationNs; public boolean hasContentEncryption; public byte[] sampleStrippedBytes; - public byte[] encryptionKeyId; + public TrackOutput.CryptoData cryptoData; public byte[] codecPrivate; public DrmInitData drmInitData; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index a228a9b775..fe1d4b04af 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -1122,19 +1122,30 @@ public final class FragmentedMp4Extractor implements Extractor { } long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L; - @C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0) - | (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0); - int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex; - byte[] encryptionKey = null; - if (fragment.definesEncryptionData) { - encryptionKey = fragment.trackEncryptionBox != null - ? fragment.trackEncryptionBox.keyId - : track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId; - } if (timestampAdjuster != null) { sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs); } - output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey); + + @C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0) + | (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0); + + // Encryption data. + TrackOutput.CryptoData cryptoData = null; + TrackEncryptionBox encryptionBox = null; + if (fragment.definesEncryptionData) { + encryptionBox = fragment.trackEncryptionBox != null + ? fragment.trackEncryptionBox + : track.sampleDescriptionEncryptionBoxes[fragment.header.sampleDescriptionIndex]; + if (encryptionBox != currentTrackBundle.cachedEncryptionBox) { + cryptoData = new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, encryptionBox.keyId); + } else { + cryptoData = currentTrackBundle.cachedCryptoData; + } + } + currentTrackBundle.cachedCryptoData = cryptoData; + currentTrackBundle.cachedEncryptionBox = encryptionBox; + + output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, cryptoData); while (!pendingMetadataSampleInfos.isEmpty()) { MetadataSampleInfo sampleInfo = pendingMetadataSampleInfos.removeFirst(); @@ -1288,6 +1299,10 @@ public final class FragmentedMp4Extractor implements Extractor { public int currentSampleInTrackRun; public int currentTrackRunIndex; + // Auxiliary references. + public TrackOutput.CryptoData cachedCryptoData; + public TrackEncryptionBox cachedEncryptionBox; + public TrackBundle(TrackOutput output) { fragment = new TrackFragment(); this.output = output; @@ -1305,6 +1320,8 @@ public final class FragmentedMp4Extractor implements Extractor { currentSampleIndex = 0; currentTrackRunIndex = 0; currentSampleInTrackRun = 0; + cachedCryptoData = null; + cachedEncryptionBox = null; } public void updateDrmInitData(DrmInitData drmInitData) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java index 501f4998cf..07d1cce8cb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java @@ -186,8 +186,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput { @Override public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, - byte[] encryptionKey) { - trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey); + CryptoData cryptoData) { + trackOutput.sampleMetadata(timeUs, flags, size, offset, cryptoData); } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java index b399d79e8d..b14e6f60ef 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java @@ -36,7 +36,7 @@ public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable { private final ArrayList sampleFlags; private final ArrayList sampleStartOffsets; private final ArrayList sampleEndOffsets; - private final ArrayList sampleEncryptionKeys; + private final ArrayList cryptoDatas; private byte[] sampleData; public Format format; @@ -47,7 +47,7 @@ public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable { sampleFlags = new ArrayList<>(); sampleStartOffsets = new ArrayList<>(); sampleEndOffsets = new ArrayList<>(); - sampleEncryptionKeys = new ArrayList<>(); + cryptoDatas = new ArrayList<>(); } public void clear() { @@ -56,7 +56,7 @@ public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable { sampleFlags.clear(); sampleStartOffsets.clear(); sampleEndOffsets.clear(); - sampleEncryptionKeys.clear(); + cryptoDatas.clear(); } @Override @@ -89,29 +89,24 @@ public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable { @Override public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, - byte[] encryptionKey) { + CryptoData cryptoData) { sampleTimesUs.add(timeUs); sampleFlags.add(flags); sampleStartOffsets.add(sampleData.length - offset - size); sampleEndOffsets.add(sampleData.length - offset); - sampleEncryptionKeys.add(encryptionKey); + cryptoDatas.add(cryptoData); } public void assertSampleCount(int count) { Assert.assertEquals(count, sampleTimesUs.size()); } - public void assertSample(int index, byte[] data, long timeUs, int flags, byte[] encryptionKey) { + public void assertSample(int index, byte[] data, long timeUs, int flags, CryptoData cryptoData) { byte[] actualData = getSampleData(index); MoreAsserts.assertEquals(data, actualData); Assert.assertEquals(timeUs, (long) sampleTimesUs.get(index)); Assert.assertEquals(flags, (int) sampleFlags.get(index)); - byte[] sampleEncryptionKey = sampleEncryptionKeys.get(index); - if (encryptionKey == null) { - Assert.assertEquals(null, sampleEncryptionKey); - } else { - MoreAsserts.assertEquals(encryptionKey, sampleEncryptionKey); - } + Assert.assertEquals(cryptoData, cryptoDatas.get(index)); } public byte[] getSampleData(int index) { @@ -128,10 +123,10 @@ public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable { Assert.assertEquals(expected.sampleFlags.get(i), sampleFlags.get(i)); Assert.assertEquals(expected.sampleStartOffsets.get(i), sampleStartOffsets.get(i)); Assert.assertEquals(expected.sampleEndOffsets.get(i), sampleEndOffsets.get(i)); - if (expected.sampleEncryptionKeys.get(i) == null) { - Assert.assertNull(sampleEncryptionKeys.get(i)); + if (expected.cryptoDatas.get(i) == null) { + Assert.assertNull(cryptoDatas.get(i)); } else { - MoreAsserts.assertEquals(expected.sampleEncryptionKeys.get(i), sampleEncryptionKeys.get(i)); + Assert.assertEquals(expected.cryptoDatas.get(i), cryptoDatas.get(i)); } } } @@ -172,9 +167,10 @@ public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable { .add("time", sampleTimesUs.get(i)) .add("flags", sampleFlags.get(i)) .add("data", getSampleData(i)); - byte[] key = sampleEncryptionKeys.get(i); - if (key != null) { - dumper.add("encryption key", key); + CryptoData cryptoData = cryptoDatas.get(i); + if (cryptoData != null) { + dumper.add("crypto mode", cryptoData.cryptoMode); + dumper.add("encryption key", cryptoData.encryptionKey); } dumper.endBlock(); }