From 2b4dcbef3fc94b2c3ec1614a34b7460b28dfdc46 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 14 Mar 2016 11:49:55 -0700 Subject: [PATCH] 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 --- .../MediaPresentationDescriptionParser.java | 25 ++++-- .../extractor/mp4/FragmentedMp4Extractor.java | 14 +++- .../exoplayer/extractor/mp4/PsshAtomUtil.java | 78 ++++++++++++------- 3 files changed, 78 insertions(+), 39 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java index 3c26842d09..5c5c9e8f03 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java @@ -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 { + 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")); 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 a44130d598..1a3f5c005c 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 @@ -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) { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/PsshAtomUtil.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/PsshAtomUtil.java index 03b910d7f7..d250cb4a3a 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/PsshAtomUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/PsshAtomUtil.java @@ -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. *

* 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 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. *

* 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 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 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); } }