diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java index 7a0bd4a63f..dcb6654e73 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java @@ -99,6 +99,8 @@ import java.util.List; public static final int TYPE_frma = Util.getIntegerCodeForString("frma"); public static final int TYPE_saiz = Util.getIntegerCodeForString("saiz"); public static final int TYPE_saio = Util.getIntegerCodeForString("saio"); + public static final int TYPE_sbgp = Util.getIntegerCodeForString("sbgp"); + public static final int TYPE_sgpd = Util.getIntegerCodeForString("sgpd"); public static final int TYPE_uuid = Util.getIntegerCodeForString("uuid"); public static final int TYPE_senc = Util.getIntegerCodeForString("senc"); public static final int TYPE_pasp = Util.getIntegerCodeForString("pasp"); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java index f33d95bbc5..c5a5b3434c 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java @@ -1075,10 +1075,9 @@ import java.util.List; int childAtomSize = parent.readInt(); int childAtomType = parent.readInt(); if (childAtomType == Atom.TYPE_tenc) { - parent.skipBytes(4); - int firstInt = parent.readInt(); - boolean defaultIsEncrypted = (firstInt >> 8) == 1; - int defaultInitVectorSize = firstInt & 0xFF; + parent.skipBytes(6); + boolean defaultIsEncrypted = parent.readUnsignedByte() == 1; + int defaultInitVectorSize = parent.readUnsignedByte(); byte[] defaultKeyId = new byte[16]; parent.readBytes(defaultKeyId, 0, defaultKeyId.length); return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java index f09c80d475..5a6d36934c 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java @@ -51,6 +51,7 @@ import java.util.UUID; public final class FragmentedMp4Extractor implements Extractor { private static final String TAG = "FragmentedMp4Extractor"; + private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); /** * Flag to work around an issue in some video streams where every frame is marked as a sync frame. @@ -474,6 +475,12 @@ public final class FragmentedMp4Extractor implements Extractor { parseSenc(senc.data, fragment); } + LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp); + LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd); + if (sbgp != null && sgpd != null) { + parseSgpd(sbgp.data, sgpd.data, fragment); + } + int childrenSize = traf.leafChildren.size(); for (int i = 0; i < childrenSize; i++) { LeafAtom atom = traf.leafChildren.get(i); @@ -721,6 +728,51 @@ public final class FragmentedMp4Extractor implements Extractor { out.fillEncryptionData(senc); } + private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, TrackFragment out) + throws ParserException { + sbgp.setPosition(Atom.HEADER_SIZE); + int sbgpFullAtom = sbgp.readInt(); + if (sbgp.readInt() != SAMPLE_GROUP_TYPE_seig) { + // Only seig grouping type is supported. + return; + } + if (Atom.parseFullAtomVersion(sbgpFullAtom) == 1) { + sbgp.skipBytes(4); + } + if (sbgp.readInt() != 1) { + throw new ParserException("Entry count in sbgp != 1 (unsupported)."); + } + + sgpd.setPosition(Atom.HEADER_SIZE); + int sgpdFullAtom = sgpd.readInt(); + if (sgpd.readInt() != SAMPLE_GROUP_TYPE_seig) { + // Only seig grouping type is supported. + return; + } + int sgpdVersion = Atom.parseFullAtomVersion(sgpdFullAtom); + if (sgpdVersion == 1) { + if (sgpd.readUnsignedInt() == 0) { + throw new ParserException("Variable length decription in sgpd found (unsupported)"); + } + } else if (sgpdVersion >= 2) { + sgpd.skipBytes(4); + } + if (sgpd.readUnsignedInt() != 1) { + throw new ParserException("Entry count in sgpd != 1 (unsupported)."); + } + // CencSampleEncryptionInformationGroupEntry + sgpd.skipBytes(2); + boolean isProtected = sgpd.readUnsignedByte() == 1; + if (!isProtected) { + return; + } + int initVectorSize = sgpd.readUnsignedByte(); + byte[] keyId = new byte[16]; + sgpd.readBytes(keyId, 0, keyId.length); + out.definesEncryptionData = true; + out.trackEncryptionBox = new TrackEncryptionBox(isProtected, initVectorSize, keyId); + } + /** * Parses a sidx atom (defined in 14496-12). */ @@ -897,8 +949,12 @@ public final class FragmentedMp4Extractor implements Extractor { 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 = fragment.definesEncryptionData - ? track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId : null; + byte[] encryptionKey = null; + if (fragment.definesEncryptionData) { + encryptionKey = fragment.trackEncryptionBox != null + ? fragment.trackEncryptionBox.keyId + : track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId; + } output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey); currentTrackBundle.currentSampleIndex++; @@ -945,8 +1001,9 @@ public final class FragmentedMp4Extractor implements Extractor { TrackFragment trackFragment = trackBundle.fragment; ParsableByteArray sampleEncryptionData = trackFragment.sampleEncryptionData; int sampleDescriptionIndex = trackFragment.header.sampleDescriptionIndex; - TrackEncryptionBox encryptionBox = trackBundle.track - .sampleDescriptionEncryptionBoxes[sampleDescriptionIndex]; + TrackEncryptionBox encryptionBox = trackFragment.trackEncryptionBox != null + ? trackFragment.trackEncryptionBox + : trackBundle.track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex]; int vectorSize = encryptionBox.initializationVectorSize; boolean subsampleEncryption = trackFragment .sampleHasSubsampleEncryptionTable[trackBundle.currentSampleIndex]; @@ -977,7 +1034,8 @@ public final class FragmentedMp4Extractor implements Extractor { || atom == Atom.TYPE_tfhd || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_trex || atom == Atom.TYPE_trun || atom == Atom.TYPE_pssh || atom == Atom.TYPE_saiz || atom == Atom.TYPE_saio || atom == Atom.TYPE_senc || atom == Atom.TYPE_uuid - || atom == Atom.TYPE_elst || atom == Atom.TYPE_mehd; + || atom == Atom.TYPE_sbgp || atom == Atom.TYPE_sgpd || atom == Atom.TYPE_elst + || atom == Atom.TYPE_mehd; } /** Returns whether the extractor should parse a container atom with type {@code atom}. */ diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackEncryptionBox.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackEncryptionBox.java index 074802924e..fed1dca88c 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackEncryptionBox.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackEncryptionBox.java @@ -16,7 +16,8 @@ package com.google.android.exoplayer.extractor.mp4; /** - * Encapsulates information parsed from a track encryption (tenc) box in an MP4 stream. + * Encapsulates information parsed from a track encryption (tenc) box or sample group description + * (sgpd) box in an MP4 stream. */ public final class TrackEncryptionBox { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java index b8e203315a..597e23fc8f 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java @@ -66,6 +66,10 @@ import java.io.IOException; * Undefined otherwise. */ public boolean[] sampleHasSubsampleEncryptionTable; + /** + * Fragment specific track encryption. May be null. + */ + public TrackEncryptionBox trackEncryptionBox; /** * If {@link #definesEncryptionData} is true, indicates the length of the sample encryption data. * Undefined otherwise. @@ -89,13 +93,15 @@ import java.io.IOException; * Resets the fragment. *

* {@link #length} and {@link #nextFragmentDecodeTime} are set to 0, and both - * {@link #definesEncryptionData} and {@link #sampleEncryptionDataNeedsFill} is set to false. + * {@link #definesEncryptionData} and {@link #sampleEncryptionDataNeedsFill} is set to false, + * and {@link #trackEncryptionBox} is set to null. */ public void reset() { length = 0; nextFragmentDecodeTime = 0; definesEncryptionData = false; sampleEncryptionDataNeedsFill = false; + trackEncryptionBox = null; } /**