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:
parent
64d7f2f846
commit
2b4dcbef3f
@ -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"));
|
||||||
|
|
||||||
|
@ -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,10 +304,15 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
drmInitData = new DrmInitData.Mapped();
|
drmInitData = new DrmInitData.Mapped();
|
||||||
}
|
}
|
||||||
byte[] psshData = child.data.data;
|
byte[] psshData = child.data.data;
|
||||||
|
UUID uuid = PsshAtomUtil.parseUuid(psshData);
|
||||||
|
if (uuid == null) {
|
||||||
|
Log.w(TAG, "Skipped pssh atom (failed to extract uuid)");
|
||||||
|
} else {
|
||||||
drmInitData.put(PsshAtomUtil.parseUuid(psshData),
|
drmInitData.put(PsshAtomUtil.parseUuid(psshData),
|
||||||
new SchemeInitData(MimeTypes.VIDEO_MP4, psshData));
|
new SchemeInitData(MimeTypes.VIDEO_MP4, psshData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (drmInitData != null) {
|
if (drmInitData != null) {
|
||||||
extractorOutput.drmInitData(drmInitData);
|
extractorOutput.drmInitData(drmInitData);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user