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.util.Base64;
import android.util.Log;
import android.util.Pair;
import org.xml.sax.helpers.DefaultHandler;
@ -56,6 +57,8 @@ import java.util.regex.Pattern;
public class MediaPresentationDescriptionParser extends DefaultHandler
implements UriLoadable.Parser<MediaPresentationDescription> {
private static final String TAG = "MediaPresentationDescriptionParser";
private static final Pattern FRAME_RATE_PATTERN = Pattern.compile("(\\d+)(?:/(\\d+))?");
private final String contentId;
@ -244,7 +247,10 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
seenFirstBaseUrl = true;
}
} 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")) {
language = checkLanguageConsistency(language, xpp.getAttributeValue(null, "lang"));
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 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)
throws XmlPullParserException, IOException {
@ -312,16 +319,17 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
SchemeInitData data = null;
do {
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) {
data = new SchemeInitData(MimeTypes.VIDEO_MP4,
Base64.decode(xpp.getText(), Base64.DEFAULT));
uuid = PsshAtomUtil.parseUuid(data.data);
if (uuid == null) {
throw new ParserException("Invalid pssh atom in cenc:pssh element");
}
}
} while (!ParserUtil.isEndTag(xpp, "ContentProtection"));
if (uuid == null) {
Log.w(TAG, "Skipped unsupported ContentProtection element");
return null;
}
return buildContentProtection(schemeIdUri, uuid, data);
}
@ -379,7 +387,10 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} else if (ParserUtil.isStartTag(xpp, "SegmentTemplate")) {
segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase);
} 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"));

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.Util;
import android.util.Log;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import java.util.UUID;
/**
* 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 {
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.
* 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();
}
byte[] psshData = child.data.data;
drmInitData.put(PsshAtomUtil.parseUuid(psshData),
new SchemeInitData(MimeTypes.VIDEO_MP4, psshData));
UUID uuid = PsshAtomUtil.parseUuid(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) {

View File

@ -17,6 +17,9 @@ package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.util.ParsableByteArray;
import android.util.Log;
import android.util.Pair;
import java.nio.ByteBuffer;
import java.util.UUID;
@ -25,6 +28,8 @@ import java.util.UUID;
*/
public final class PsshAtomUtil {
private static final String TAG = "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>
* The UUID is only parsed if the data is a valid PSSH atom.
*
* @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) {
ParsableByteArray atomData = new ParsableByteArray(atom);
if (!isPsshAtom(atomData, null)) {
Pair<UUID, byte[]> parsedAtom = parsePsshAtom(atom);
if (parsedAtom == null) {
return null;
}
atomData.setPosition(Atom.FULL_HEADER_SIZE);
return new UUID(atomData.readLong(), atomData.readLong());
return parsedAtom.first;
}
/**
* 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>
* 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.
*
* @param atom The atom to parse.
* @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
* UUID does not match the one provided.
* @return The parsed scheme specific data. Null if the input is not a valid PSSH atom, or if the
* 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) {
ParsableByteArray atomData = new ParsableByteArray(atom);
if (!isPsshAtom(atomData, uuid)) {
Pair<UUID, byte[]> parsedAtom = parsePsshAtom(atom);
if (parsedAtom == null) {
return null;
}
atomData.setPosition(Atom.FULL_HEADER_SIZE + 16 /* UUID */);
int dataSize = atomData.readInt();
byte[] data = new byte[dataSize];
atomData.readBytes(data, 0, dataSize);
return data;
if (uuid != null && !uuid.equals(parsedAtom.first)) {
Log.w(TAG, "UUID mismatch. Expected: " + uuid + ", got: " + parsedAtom.first + ".");
return null;
}
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 */) {
// Data too short.
return false;
return null;
}
atomData.setPosition(0);
int atomSize = atomData.readInt();
if (atomSize != atomData.bytesLeft() + 4) {
// Not an atom, or incorrect atom size.
return false;
return null;
}
int atomType = atomData.readInt();
if (atomType != Atom.TYPE_pssh) {
// Not an atom, or incorrect atom type.
return false;
return null;
}
atomData.setPosition(Atom.FULL_HEADER_SIZE);
if (uuid == null) {
atomData.skipBytes(16);
} else if (atomData.readLong() != uuid.getMostSignificantBits()
|| atomData.readLong() != uuid.getLeastSignificantBits()) {
// UUID doesn't match.
return false;
int atomVersion = Atom.parseFullAtomVersion(atomData.readInt());
if (atomVersion > 1) {
Log.w(TAG, "Unsupported pssh version: " + atomVersion);
return null;
}
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()) {
// Incorrect dataSize.
return false;
return null;
}
return true;
byte[] data = new byte[dataSize];
atomData.readBytes(data, 0, dataSize);
return Pair.create(uuid, data);
}
}