Fix parsing of version 1 pssh boxes, and ignore version 2+.

Issue: #1195
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=117155301
This commit is contained in:
olly 2016-03-14 11:49:55 -07:00 committed by Oliver Woodman
parent 64d7f2f846
commit 2b4dcbef3f
3 changed files with 78 additions and 39 deletions

View File

@ -32,6 +32,7 @@ import com.google.android.exoplayer.util.Util;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64; import android.util.Base64;
import android.util.Log;
import android.util.Pair; import android.util.Pair;
import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.DefaultHandler;
@ -56,6 +57,8 @@ import java.util.regex.Pattern;
public class MediaPresentationDescriptionParser extends DefaultHandler public class MediaPresentationDescriptionParser extends DefaultHandler
implements UriLoadable.Parser<MediaPresentationDescription> { implements UriLoadable.Parser<MediaPresentationDescription> {
private static final String TAG = "MediaPresentationDescriptionParser";
private static final Pattern FRAME_RATE_PATTERN = Pattern.compile("(\\d+)(?:/(\\d+))?"); private static final Pattern FRAME_RATE_PATTERN = Pattern.compile("(\\d+)(?:/(\\d+))?");
private final String contentId; private final String contentId;
@ -244,7 +247,10 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
seenFirstBaseUrl = true; seenFirstBaseUrl = true;
} }
} else if (ParserUtil.isStartTag(xpp, "ContentProtection")) { } else if (ParserUtil.isStartTag(xpp, "ContentProtection")) {
contentProtectionsBuilder.addAdaptationSetProtection(parseContentProtection(xpp)); ContentProtection contentProtection = parseContentProtection(xpp);
if (contentProtection != null) {
contentProtectionsBuilder.addAdaptationSetProtection(contentProtection);
}
} else if (ParserUtil.isStartTag(xpp, "ContentComponent")) { } else if (ParserUtil.isStartTag(xpp, "ContentComponent")) {
language = checkLanguageConsistency(language, xpp.getAttributeValue(null, "lang")); language = checkLanguageConsistency(language, xpp.getAttributeValue(null, "lang"));
contentType = checkContentTypeConsistency(contentType, parseContentType(xpp)); contentType = checkContentTypeConsistency(contentType, parseContentType(xpp));
@ -300,10 +306,11 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} }
/** /**
* Parses a ContentProtection element. * Parses a {@link ContentProtection} element.
* *
* @throws XmlPullParserException If an error occurs parsing the element. * @throws XmlPullParserException If an error occurs parsing the element.
* @throws IOException If an error occurs reading the element. * @throws IOException If an error occurs reading the element.
* @return The parsed {@link ContentProtection} element, or null if the element is unsupported.
**/ **/
protected ContentProtection parseContentProtection(XmlPullParser xpp) protected ContentProtection parseContentProtection(XmlPullParser xpp)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
@ -312,16 +319,17 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
SchemeInitData data = null; SchemeInitData data = null;
do { do {
xpp.next(); xpp.next();
// The cenc:pssh element is defined in 23001-7:2015 // The cenc:pssh element is defined in 23001-7:2015.
if (ParserUtil.isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) { if (ParserUtil.isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) {
data = new SchemeInitData(MimeTypes.VIDEO_MP4, data = new SchemeInitData(MimeTypes.VIDEO_MP4,
Base64.decode(xpp.getText(), Base64.DEFAULT)); Base64.decode(xpp.getText(), Base64.DEFAULT));
uuid = PsshAtomUtil.parseUuid(data.data); uuid = PsshAtomUtil.parseUuid(data.data);
if (uuid == null) {
throw new ParserException("Invalid pssh atom in cenc:pssh element");
}
} }
} while (!ParserUtil.isEndTag(xpp, "ContentProtection")); } while (!ParserUtil.isEndTag(xpp, "ContentProtection"));
if (uuid == null) {
Log.w(TAG, "Skipped unsupported ContentProtection element");
return null;
}
return buildContentProtection(schemeIdUri, uuid, data); return buildContentProtection(schemeIdUri, uuid, data);
} }
@ -379,7 +387,10 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} else if (ParserUtil.isStartTag(xpp, "SegmentTemplate")) { } else if (ParserUtil.isStartTag(xpp, "SegmentTemplate")) {
segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase); segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase);
} else if (ParserUtil.isStartTag(xpp, "ContentProtection")) { } else if (ParserUtil.isStartTag(xpp, "ContentProtection")) {
contentProtectionsBuilder.addRepresentationProtection(parseContentProtection(xpp)); ContentProtection contentProtection = parseContentProtection(xpp);
if (contentProtection != null) {
contentProtectionsBuilder.addAdaptationSetProtection(contentProtection);
}
} }
} while (!ParserUtil.isEndTag(xpp, "Representation")); } while (!ParserUtil.isEndTag(xpp, "Representation"));

View File

@ -33,10 +33,13 @@ import com.google.android.exoplayer.util.NalUnitUtil;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Stack; import java.util.Stack;
import java.util.UUID;
/** /**
* Facilitates the extraction of data from the fragmented mp4 container format. * Facilitates the extraction of data from the fragmented mp4 container format.
@ -45,6 +48,8 @@ import java.util.Stack;
*/ */
public final class FragmentedMp4Extractor implements Extractor { public final class FragmentedMp4Extractor implements Extractor {
private static final String TAG = "FragmentedMp4Extractor";
/** /**
* Flag to work around an issue in some video streams where every frame is marked as a sync frame. * Flag to work around an issue in some video streams where every frame is marked as a sync frame.
* The workaround overrides the sync frame flags in the stream, forcing them to false except for * The workaround overrides the sync frame flags in the stream, forcing them to false except for
@ -299,8 +304,13 @@ public final class FragmentedMp4Extractor implements Extractor {
drmInitData = new DrmInitData.Mapped(); drmInitData = new DrmInitData.Mapped();
} }
byte[] psshData = child.data.data; byte[] psshData = child.data.data;
drmInitData.put(PsshAtomUtil.parseUuid(psshData), UUID uuid = PsshAtomUtil.parseUuid(psshData);
new SchemeInitData(MimeTypes.VIDEO_MP4, psshData)); if (uuid == null) {
Log.w(TAG, "Skipped pssh atom (failed to extract uuid)");
} else {
drmInitData.put(PsshAtomUtil.parseUuid(psshData),
new SchemeInitData(MimeTypes.VIDEO_MP4, psshData));
}
} }
} }
if (drmInitData != null) { if (drmInitData != null) {

View File

@ -17,6 +17,9 @@ package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import android.util.Log;
import android.util.Pair;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.UUID; import java.util.UUID;
@ -25,6 +28,8 @@ import java.util.UUID;
*/ */
public final class PsshAtomUtil { public final class PsshAtomUtil {
private static final String TAG = "PsshAtomUtil";
private PsshAtomUtil() {} private PsshAtomUtil() {}
/** /**
@ -48,75 +53,88 @@ public final class PsshAtomUtil {
} }
/** /**
* Parses the UUID from a PSSH atom. * Parses the UUID from a PSSH atom. Version 0 and 1 PSSH atoms are supported.
* <p> * <p>
* The UUID is only parsed if the data is a valid PSSH atom. * The UUID is only parsed if the data is a valid PSSH atom.
* *
* @param atom The atom to parse. * @param atom The atom to parse.
* @return The parsed UUID. Null if the data is not a valid PSSH atom. * @return The parsed UUID. Null if the input is not a valid PSSH atom, or if the PSSH atom has
* an unsupported version.
*/ */
public static UUID parseUuid(byte[] atom) { public static UUID parseUuid(byte[] atom) {
ParsableByteArray atomData = new ParsableByteArray(atom); Pair<UUID, byte[]> parsedAtom = parsePsshAtom(atom);
if (!isPsshAtom(atomData, null)) { if (parsedAtom == null) {
return null; return null;
} }
atomData.setPosition(Atom.FULL_HEADER_SIZE); return parsedAtom.first;
return new UUID(atomData.readLong(), atomData.readLong());
} }
/** /**
* Parses the scheme specific data from a PSSH atom. * Parses the scheme specific data from a PSSH atom. Version 0 and 1 PSSH atoms are supported.
* <p> * <p>
* The scheme specific data is only parsed if the data is a valid PSSH atom matching the given * The scheme specific data is only parsed if the data is a valid PSSH atom matching the given
* UUID, or if the data is a valid PSSH atom of any type in the case that the passed UUID is null. * UUID, or if the data is a valid PSSH atom of any type in the case that the passed UUID is null.
* *
* @param atom The atom to parse. * @param atom The atom to parse.
* @param uuid The required UUID of the PSSH atom, or null to accept any UUID. * @param uuid The required UUID of the PSSH atom, or null to accept any UUID.
* @return The parsed scheme specific data. Null if the data is not a valid PSSH atom or if its * @return The parsed scheme specific data. Null if the input is not a valid PSSH atom, or if the
* UUID does not match the one provided. * PSSH atom has an unsupported version, or if the PSSH atom does not match the passed UUID.
*/ */
public static byte[] parseSchemeSpecificData(byte[] atom, UUID uuid) { public static byte[] parseSchemeSpecificData(byte[] atom, UUID uuid) {
ParsableByteArray atomData = new ParsableByteArray(atom); Pair<UUID, byte[]> parsedAtom = parsePsshAtom(atom);
if (!isPsshAtom(atomData, uuid)) { if (parsedAtom == null) {
return null; return null;
} }
atomData.setPosition(Atom.FULL_HEADER_SIZE + 16 /* UUID */); if (uuid != null && !uuid.equals(parsedAtom.first)) {
int dataSize = atomData.readInt(); Log.w(TAG, "UUID mismatch. Expected: " + uuid + ", got: " + parsedAtom.first + ".");
byte[] data = new byte[dataSize]; return null;
atomData.readBytes(data, 0, dataSize); }
return data; return parsedAtom.second;
} }
private static boolean isPsshAtom(ParsableByteArray atomData, UUID uuid) { /**
* Parses the UUID and scheme specific data from a PSSH atom. Version 0 and 1 PSSH atoms are
* supported.
*
* @param atom The atom to parse.
* @return A pair consisting of the parsed UUID and scheme specific data. Null if the input is
* not a valid PSSH atom, or if the PSSH atom has an unsupported version.
*/
private static Pair<UUID, byte[]> parsePsshAtom(byte[] atom) {
ParsableByteArray atomData = new ParsableByteArray(atom);
if (atomData.limit() < Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) { if (atomData.limit() < Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) {
// Data too short. // Data too short.
return false; return null;
} }
atomData.setPosition(0); atomData.setPosition(0);
int atomSize = atomData.readInt(); int atomSize = atomData.readInt();
if (atomSize != atomData.bytesLeft() + 4) { if (atomSize != atomData.bytesLeft() + 4) {
// Not an atom, or incorrect atom size. // Not an atom, or incorrect atom size.
return false; return null;
} }
int atomType = atomData.readInt(); int atomType = atomData.readInt();
if (atomType != Atom.TYPE_pssh) { if (atomType != Atom.TYPE_pssh) {
// Not an atom, or incorrect atom type. // Not an atom, or incorrect atom type.
return false; return null;
} }
atomData.setPosition(Atom.FULL_HEADER_SIZE); int atomVersion = Atom.parseFullAtomVersion(atomData.readInt());
if (uuid == null) { if (atomVersion > 1) {
atomData.skipBytes(16); Log.w(TAG, "Unsupported pssh version: " + atomVersion);
} else if (atomData.readLong() != uuid.getMostSignificantBits() return null;
|| atomData.readLong() != uuid.getLeastSignificantBits()) {
// UUID doesn't match.
return false;
} }
int dataSize = atomData.readInt(); UUID uuid = new UUID(atomData.readLong(), atomData.readLong());
if (atomVersion == 1) {
int keyIdCount = atomData.readUnsignedIntToInt();
atomData.skipBytes(16 * keyIdCount);
}
int dataSize = atomData.readUnsignedIntToInt();
if (dataSize != atomData.bytesLeft()) { if (dataSize != atomData.bytesLeft()) {
// Incorrect dataSize. // Incorrect dataSize.
return false; return null;
} }
return true; byte[] data = new byte[dataSize];
atomData.readBytes(data, 0, dataSize);
return Pair.create(uuid, data);
} }
} }