| /* |
| * Copyright (C) 2016 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.exoplayer2.extractor.mp4; |
| |
| import androidx.annotation.Nullable; |
| import com.google.android.exoplayer2.util.Log; |
| import com.google.android.exoplayer2.util.ParsableByteArray; |
| import java.nio.ByteBuffer; |
| import java.util.UUID; |
| |
| /** Utility methods for handling PSSH atoms. */ |
| public final class PsshAtomUtil { |
| |
| private static final String TAG = "PsshAtomUtil"; |
| |
| private PsshAtomUtil() {} |
| |
| /** |
| * Builds a version 0 PSSH atom for a given system id, containing the given data. |
| * |
| * @param systemId The system id of the scheme. |
| * @param data The scheme specific data. |
| * @return The PSSH atom. |
| */ |
| public static byte[] buildPsshAtom(UUID systemId, @Nullable byte[] data) { |
| return buildPsshAtom(systemId, null, data); |
| } |
| |
| /** |
| * Builds a PSSH atom for the given system id, containing the given key ids and data. |
| * |
| * @param systemId The system id of the scheme. |
| * @param keyIds The key ids for a version 1 PSSH atom, or null for a version 0 PSSH atom. |
| * @param data The scheme specific data. |
| * @return The PSSH atom. |
| */ |
| public static byte[] buildPsshAtom( |
| UUID systemId, @Nullable UUID[] keyIds, @Nullable byte[] data) { |
| int dataLength = data != null ? data.length : 0; |
| int psshBoxLength = Atom.FULL_HEADER_SIZE + 16 /* SystemId */ + 4 /* DataSize */ + dataLength; |
| if (keyIds != null) { |
| psshBoxLength += 4 /* KID_count */ + (keyIds.length * 16) /* KIDs */; |
| } |
| ByteBuffer psshBox = ByteBuffer.allocate(psshBoxLength); |
| psshBox.putInt(psshBoxLength); |
| psshBox.putInt(Atom.TYPE_pssh); |
| psshBox.putInt(keyIds != null ? 0x01000000 : 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */); |
| psshBox.putLong(systemId.getMostSignificantBits()); |
| psshBox.putLong(systemId.getLeastSignificantBits()); |
| if (keyIds != null) { |
| psshBox.putInt(keyIds.length); |
| for (UUID keyId : keyIds) { |
| psshBox.putLong(keyId.getMostSignificantBits()); |
| psshBox.putLong(keyId.getLeastSignificantBits()); |
| } |
| } |
| if (data != null && data.length != 0) { |
| psshBox.putInt(data.length); |
| psshBox.put(data); |
| } // Else the last 4 bytes are a 0 DataSize. |
| return psshBox.array(); |
| } |
| |
| /** |
| * Returns whether the data is a valid PSSH atom. |
| * |
| * @param data The data to parse. |
| * @return Whether the data is a valid PSSH atom. |
| */ |
| public static boolean isPsshAtom(byte[] data) { |
| return parsePsshAtom(data) != null; |
| } |
| |
| /** |
| * 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 input is not a valid PSSH atom, or if the PSSH atom has an |
| * unsupported version. |
| */ |
| @Nullable |
| public static UUID parseUuid(byte[] atom) { |
| @Nullable PsshAtom parsedAtom = parsePsshAtom(atom); |
| if (parsedAtom == null) { |
| return null; |
| } |
| return parsedAtom.uuid; |
| } |
| |
| /** |
| * Parses the version from a PSSH atom. Version 0 and 1 PSSH atoms are supported. |
| * |
| * <p>The version is only parsed if the data is a valid PSSH atom. |
| * |
| * @param atom The atom to parse. |
| * @return The parsed version. -1 if the input is not a valid PSSH atom, or if the PSSH atom has |
| * an unsupported version. |
| */ |
| public static int parseVersion(byte[] atom) { |
| @Nullable PsshAtom parsedAtom = parsePsshAtom(atom); |
| if (parsedAtom == null) { |
| return -1; |
| } |
| return parsedAtom.version; |
| } |
| |
| /** |
| * 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 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. |
| */ |
| @Nullable |
| public static byte[] parseSchemeSpecificData(byte[] atom, UUID uuid) { |
| @Nullable PsshAtom parsedAtom = parsePsshAtom(atom); |
| if (parsedAtom == null) { |
| return null; |
| } |
| if (!uuid.equals(parsedAtom.uuid)) { |
| Log.w(TAG, "UUID mismatch. Expected: " + uuid + ", got: " + parsedAtom.uuid + "."); |
| return null; |
| } |
| return parsedAtom.schemeData; |
| } |
| |
| /** |
| * Parses a PSSH atom. Version 0 and 1 PSSH atoms are supported. |
| * |
| * @param atom The atom to parse. |
| * @return The parsed PSSH atom. Null if the input is not a valid PSSH atom, or if the PSSH atom |
| * has an unsupported version. |
| */ |
| // TODO: Support parsing of the key ids for version 1 PSSH atoms. |
| @Nullable |
| private static PsshAtom parsePsshAtom(byte[] atom) { |
| ParsableByteArray atomData = new ParsableByteArray(atom); |
| if (atomData.limit() < Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) { |
| // Data too short. |
| return null; |
| } |
| atomData.setPosition(0); |
| int atomSize = atomData.readInt(); |
| if (atomSize != atomData.bytesLeft() + 4) { |
| // Not an atom, or incorrect atom size. |
| return null; |
| } |
| int atomType = atomData.readInt(); |
| if (atomType != Atom.TYPE_pssh) { |
| // Not an atom, or incorrect atom type. |
| return null; |
| } |
| int atomVersion = Atom.parseFullAtomVersion(atomData.readInt()); |
| if (atomVersion > 1) { |
| Log.w(TAG, "Unsupported pssh version: " + atomVersion); |
| return null; |
| } |
| 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 null; |
| } |
| byte[] data = new byte[dataSize]; |
| atomData.readBytes(data, 0, dataSize); |
| return new PsshAtom(uuid, atomVersion, data); |
| } |
| |
| // TODO: Consider exposing this and making parsePsshAtom public. |
| private static class PsshAtom { |
| |
| private final UUID uuid; |
| private final int version; |
| private final byte[] schemeData; |
| |
| public PsshAtom(UUID uuid, int version, byte[] schemeData) { |
| this.uuid = uuid; |
| this.version = version; |
| this.schemeData = schemeData; |
| } |
| } |
| } |