mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +08:00
Pass whole PSSH box to MediaDrm (except in the WV+L case).
This fixes SmoothStreaming on AndroidTV, and also removes a warning that gets logged when using Widevine/FMP4.
This commit is contained in:
parent
d0ba265290
commit
4c4782c72d
@ -19,6 +19,7 @@ import com.google.android.exoplayer.SampleSource.SampleSourceReader;
|
|||||||
import com.google.android.exoplayer.drm.DrmInitData;
|
import com.google.android.exoplayer.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
|
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
|
||||||
import com.google.android.exoplayer.extractor.mp4.Mp4Extractor;
|
import com.google.android.exoplayer.extractor.mp4.Mp4Extractor;
|
||||||
|
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
@ -266,7 +267,10 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
DrmInitData.Mapped drmInitData = new DrmInitData.Mapped(MimeTypes.VIDEO_MP4);
|
DrmInitData.Mapped drmInitData = new DrmInitData.Mapped(MimeTypes.VIDEO_MP4);
|
||||||
drmInitData.putAll(psshInfo);
|
for (UUID uuid : psshInfo.keySet()) {
|
||||||
|
byte[] psshAtom = PsshAtomUtil.buildPsshAtom(uuid, psshInfo.get(uuid));
|
||||||
|
drmInitData.put(uuid, psshAtom);
|
||||||
|
}
|
||||||
return drmInitData;
|
return drmInitData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,10 +21,10 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList;
|
|||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate;
|
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate;
|
||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement;
|
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement;
|
||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
|
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
|
||||||
|
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
|
||||||
import com.google.android.exoplayer.upstream.UriLoadable;
|
import com.google.android.exoplayer.upstream.UriLoadable;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
|
||||||
import com.google.android.exoplayer.util.UriUtil;
|
import com.google.android.exoplayer.util.UriUtil;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
@ -258,22 +258,20 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
throws XmlPullParserException, IOException {
|
throws XmlPullParserException, IOException {
|
||||||
String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri");
|
String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri");
|
||||||
UUID uuid = null;
|
UUID uuid = null;
|
||||||
byte[] data = null;
|
byte[] psshAtom = 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 (isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) {
|
if (isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) {
|
||||||
byte[] decodedData = Base64.decode(xpp.getText(), Base64.DEFAULT);
|
psshAtom = Base64.decode(xpp.getText(), Base64.DEFAULT);
|
||||||
ParsableByteArray psshAtom = new ParsableByteArray(decodedData);
|
uuid = PsshAtomUtil.parseUuid(psshAtom);
|
||||||
psshAtom.skipBytes(12);
|
if (uuid == null) {
|
||||||
uuid = new UUID(psshAtom.readLong(), psshAtom.readLong());
|
throw new ParserException("Invalid pssh atom in cenc:pssh element");
|
||||||
int dataSize = psshAtom.readInt();
|
}
|
||||||
data = new byte[dataSize];
|
|
||||||
psshAtom.readBytes(data, 0, dataSize);
|
|
||||||
}
|
}
|
||||||
} while (!isEndTag(xpp, "ContentProtection"));
|
} while (!isEndTag(xpp, "ContentProtection"));
|
||||||
|
|
||||||
return buildContentProtection(schemeIdUri, uuid, data);
|
return buildContentProtection(schemeIdUri, uuid, psshAtom);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ContentProtection buildContentProtection(String schemeIdUri, UUID uuid, byte[] data) {
|
protected ContentProtection buildContentProtection(String schemeIdUri, UUID uuid, byte[] data) {
|
||||||
|
@ -70,15 +70,6 @@ public abstract class DrmInitData {
|
|||||||
schemeData.put(schemeUuid, data);
|
schemeData.put(schemeUuid, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts scheme specific initialization data.
|
|
||||||
*
|
|
||||||
* @param data A mapping from scheme UUID to initialization data.
|
|
||||||
*/
|
|
||||||
public void putAll(Map<UUID, byte[]> data) {
|
|
||||||
schemeData.putAll(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.drm;
|
package com.google.android.exoplayer.drm;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
|
||||||
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.media.DeniedByServerException;
|
import android.media.DeniedByServerException;
|
||||||
@ -96,7 +99,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
|
|||||||
private MediaCrypto mediaCrypto;
|
private MediaCrypto mediaCrypto;
|
||||||
private Exception lastException;
|
private Exception lastException;
|
||||||
private String mimeType;
|
private String mimeType;
|
||||||
private byte[] schemePsshData;
|
private byte[] schemeData;
|
||||||
private byte[] sessionId;
|
private byte[] sessionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -265,13 +268,22 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
|
|||||||
requestHandlerThread.start();
|
requestHandlerThread.start();
|
||||||
postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper());
|
postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper());
|
||||||
}
|
}
|
||||||
if (this.schemePsshData == null) {
|
if (schemeData == null) {
|
||||||
mimeType = drmInitData.mimeType;
|
mimeType = drmInitData.mimeType;
|
||||||
schemePsshData = drmInitData.get(uuid);
|
schemeData = drmInitData.get(uuid);
|
||||||
if (schemePsshData == null) {
|
if (schemeData == null) {
|
||||||
onError(new IllegalStateException("Media does not support uuid: " + uuid));
|
onError(new IllegalStateException("Media does not support uuid: " + uuid));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (Util.SDK_INT < 21) {
|
||||||
|
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom.
|
||||||
|
byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(schemeData, WIDEVINE_UUID);
|
||||||
|
if (psshData == null) {
|
||||||
|
// Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged.
|
||||||
|
} else {
|
||||||
|
schemeData = psshData;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
state = STATE_OPENING;
|
state = STATE_OPENING;
|
||||||
openInternal(true);
|
openInternal(true);
|
||||||
@ -290,7 +302,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
|
|||||||
postRequestHandler = null;
|
postRequestHandler = null;
|
||||||
requestHandlerThread.quit();
|
requestHandlerThread.quit();
|
||||||
requestHandlerThread = null;
|
requestHandlerThread = null;
|
||||||
schemePsshData = null;
|
schemeData = null;
|
||||||
mediaCrypto = null;
|
mediaCrypto = null;
|
||||||
lastException = null;
|
lastException = null;
|
||||||
if (sessionId != null) {
|
if (sessionId != null) {
|
||||||
@ -352,7 +364,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
|
|||||||
private void postKeyRequest() {
|
private void postKeyRequest() {
|
||||||
KeyRequest keyRequest;
|
KeyRequest keyRequest;
|
||||||
try {
|
try {
|
||||||
keyRequest = mediaDrm.getKeyRequest(sessionId, schemePsshData, mimeType,
|
keyRequest = mediaDrm.getKeyRequest(sessionId, schemeData, mimeType,
|
||||||
MediaDrm.KEY_TYPE_STREAMING, optionalKeyRequestParameters);
|
MediaDrm.KEY_TYPE_STREAMING, optionalKeyRequestParameters);
|
||||||
postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget();
|
postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget();
|
||||||
} catch (NotProvisionedException e) {
|
} catch (NotProvisionedException e) {
|
||||||
|
@ -35,7 +35,6 @@ 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.
|
||||||
@ -250,16 +249,11 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
for (int i = 0; i < moovChildrenSize; i++) {
|
for (int i = 0; i < moovChildrenSize; i++) {
|
||||||
LeafAtom child = moovChildren.get(i);
|
LeafAtom child = moovChildren.get(i);
|
||||||
if (child.type == Atom.TYPE_pssh) {
|
if (child.type == Atom.TYPE_pssh) {
|
||||||
ParsableByteArray psshAtom = child.data;
|
|
||||||
psshAtom.setPosition(Atom.FULL_HEADER_SIZE);
|
|
||||||
UUID uuid = new UUID(psshAtom.readLong(), psshAtom.readLong());
|
|
||||||
int dataSize = psshAtom.readInt();
|
|
||||||
byte[] data = new byte[dataSize];
|
|
||||||
psshAtom.readBytes(data, 0, dataSize);
|
|
||||||
if (drmInitData == null) {
|
if (drmInitData == null) {
|
||||||
drmInitData = new DrmInitData.Mapped(MimeTypes.VIDEO_MP4);
|
drmInitData = new DrmInitData.Mapped(MimeTypes.VIDEO_MP4);
|
||||||
}
|
}
|
||||||
drmInitData.put(uuid, data);
|
byte[] psshData = child.data.data;
|
||||||
|
drmInitData.put(PsshAtomUtil.parseUuid(psshData), psshData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (drmInitData != null) {
|
if (drmInitData != null) {
|
||||||
|
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.extractor.mp4;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for handling PSSH atoms.
|
||||||
|
*/
|
||||||
|
public final class PsshAtomUtil {
|
||||||
|
|
||||||
|
private PsshAtomUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a PSSH atom for a given {@link UUID} containing the given scheme specific data.
|
||||||
|
*
|
||||||
|
* @param uuid The UUID of the scheme.
|
||||||
|
* @param data The scheme specific data.
|
||||||
|
* @return The PSSH atom.
|
||||||
|
*/
|
||||||
|
public static byte[] buildPsshAtom(UUID uuid, byte[] data) {
|
||||||
|
int psshBoxLength = Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */ + data.length;
|
||||||
|
ByteBuffer psshBox = ByteBuffer.allocate(psshBoxLength);
|
||||||
|
psshBox.putInt(psshBoxLength);
|
||||||
|
psshBox.putInt(Atom.TYPE_pssh);
|
||||||
|
psshBox.putInt(0 /* version=0, flags=0 */);
|
||||||
|
psshBox.putLong(uuid.getMostSignificantBits());
|
||||||
|
psshBox.putLong(uuid.getLeastSignificantBits());
|
||||||
|
psshBox.putInt(data.length);
|
||||||
|
psshBox.put(data);
|
||||||
|
return psshBox.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the UUID from a PSSH atom.
|
||||||
|
* <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.
|
||||||
|
*/
|
||||||
|
public static UUID parseUuid(byte[] atom) {
|
||||||
|
ParsableByteArray atomData = new ParsableByteArray(atom);
|
||||||
|
if (!isPsshAtom(atomData, null)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
atomData.setPosition(Atom.FULL_HEADER_SIZE);
|
||||||
|
return new UUID(atomData.readLong(), atomData.readLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the scheme specific data from a PSSH atom.
|
||||||
|
* <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.
|
||||||
|
*/
|
||||||
|
public static byte[] parseSchemeSpecificData(byte[] atom, UUID uuid) {
|
||||||
|
ParsableByteArray atomData = new ParsableByteArray(atom);
|
||||||
|
if (!isPsshAtom(atomData, uuid)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPsshAtom(ParsableByteArray atomData, UUID uuid) {
|
||||||
|
if (atomData.limit() < Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) {
|
||||||
|
// Data too short.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
atomData.setPosition(0);
|
||||||
|
int atomSize = atomData.readInt();
|
||||||
|
if (atomSize != atomData.bytesLeft() + 4) {
|
||||||
|
// Not an atom, or incorrect atom size.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int atomType = atomData.readInt();
|
||||||
|
if (atomType != Atom.TYPE_pssh) {
|
||||||
|
// Not an atom, or incorrect atom type.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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 dataSize = atomData.readInt();
|
||||||
|
if (dataSize != atomData.bytesLeft()) {
|
||||||
|
// Incorrect dataSize.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer.smoothstreaming;
|
package com.google.android.exoplayer.smoothstreaming;
|
||||||
|
|
||||||
import com.google.android.exoplayer.ParserException;
|
import com.google.android.exoplayer.ParserException;
|
||||||
|
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
|
||||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
|
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
|
||||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
|
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
|
||||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
|
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
|
||||||
@ -423,7 +424,7 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object build() {
|
public Object build() {
|
||||||
return new ProtectionElement(uuid, initData);
|
return new ProtectionElement(uuid, PsshAtomUtil.buildPsshAtom(uuid, initData));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user