/* * 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 android.util.Log; import android.util.Pair; 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 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. 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. */ public static UUID parseUuid(byte[] atom) { Pair<UUID, byte[]> parsedAtom = parsePsshAtom(atom); if (parsedAtom == null) { return null; } return parsedAtom.first; } /** * 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. */ public static byte[] parseSchemeSpecificData(byte[] atom, UUID uuid) { Pair<UUID, byte[]> parsedAtom = parsePsshAtom(atom); if (parsedAtom == null) { return null; } if (uuid != null && !uuid.equals(parsedAtom.first)) { Log.w(TAG, "UUID mismatch. Expected: " + uuid + ", got: " + parsedAtom.first + "."); return null; } return parsedAtom.second; } /** * 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 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 Pair.create(uuid, data); } }