Update Pssh Atom Util to expose internal data class, parse v1 PSSH atoms

This commit is contained in:
kamaroyl 2024-01-27 10:13:52 -07:00 committed by tonihei
parent dcae49a561
commit b898dbacad
2 changed files with 92 additions and 20 deletions

View File

@ -72,10 +72,11 @@ public final class PsshAtomUtil {
if (data != null && data.length != 0) { if (data != null && data.length != 0) {
psshBox.putInt(data.length); psshBox.putInt(data.length);
psshBox.put(data); psshBox.put(data);
} // Else the last 4 bytes are a 0 DataSize. } else {
psshBox.putInt(0);
}
return psshBox.array(); return psshBox.array();
} }
/** /**
* Returns whether the data is a valid PSSH atom. * Returns whether the data is a valid PSSH atom.
* *
@ -152,23 +153,29 @@ public final class PsshAtomUtil {
* @return The parsed PSSH atom. Null if the input is not a valid PSSH atom, or if the PSSH atom * @return The parsed PSSH atom. Null if the input is not a valid PSSH atom, or if the PSSH atom
* has an unsupported version. * has an unsupported version.
*/ */
// TODO: Support parsing of the key ids for version 1 PSSH atoms.
@Nullable @Nullable
private static PsshAtom parsePsshAtom(byte[] atom) { public static PsshAtom parsePsshAtom(byte[] atom) {
ParsableByteArray atomData = new ParsableByteArray(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 null; return null;
} }
atomData.setPosition(0); atomData.setPosition(0);
int bufferLength = atomData.bytesLeft();
int atomSize = atomData.readInt(); int atomSize = atomData.readInt();
if (atomSize != atomData.bytesLeft() + 4) {
// Not an atom, or incorrect atom size. if (atomSize != bufferLength) {
Log.w(
TAG,
"Advertised atom size ("
+ atomSize
+ ") does not match current buffer size: "
+ bufferLength);
return null; 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. Log.w(TAG, "Atom Type is not pssh: " + atomType);
return null; return null;
} }
int atomVersion = Atom.parseFullAtomVersion(atomData.readInt()); int atomVersion = Atom.parseFullAtomVersion(atomData.readInt());
@ -177,31 +184,47 @@ public final class PsshAtomUtil {
return null; return null;
} }
UUID uuid = new UUID(atomData.readLong(), atomData.readLong()); UUID uuid = new UUID(atomData.readLong(), atomData.readLong());
UUID[] keyIds = null;
int dataSize = 0;
if (atomVersion == 1) { if (atomVersion == 1) {
int keyIdCount = atomData.readUnsignedIntToInt(); int keyIdCount = atomData.readUnsignedIntToInt();
atomData.skipBytes(16 * keyIdCount); keyIds = new UUID[keyIdCount];
} for (int i = 0; i < keyIdCount; ++i) {
int dataSize = atomData.readUnsignedIntToInt(); keyIds[i] = new UUID(atomData.readLong(), atomData.readLong());
if (dataSize != atomData.bytesLeft()) { }
// Incorrect dataSize. } else if (atomVersion == 0) {
return null; dataSize = atomData.readUnsignedIntToInt();
bufferLength = atomData.bytesLeft();
if (dataSize != bufferLength) {
Log.w(
TAG,
"Atom data size (" + dataSize + ") does not match the bytes left: " + bufferLength);
return null;
}
} }
byte[] data = new byte[dataSize]; byte[] data = new byte[dataSize];
atomData.readBytes(data, 0, dataSize); atomData.readBytes(data, 0, dataSize);
return new PsshAtom(uuid, atomVersion, data); return new PsshAtom(uuid, atomVersion, data, keyIds);
} }
// TODO: Consider exposing this and making parsePsshAtom public. /**
private static class PsshAtom { * A class representing the mp4 PSSH Atom as specified in the CENC standard - systemId the UUID of
* the encryption system as specified in ISO/IEC 23009-1 section 5.8.4.1 - version the version of
* the PSSH atom, should be 0 or 1 - schemaData the binary data in the atom - keyIds the optional
* set of keyIds associated with the
*/
public static class PsshAtom {
private final UUID uuid; public final UUID uuid;
private final int version; public final int version;
private final byte[] schemeData; public final byte[] schemeData;
@Nullable public final UUID[] keyIds;
public PsshAtom(UUID uuid, int version, byte[] schemeData) { public PsshAtom(UUID uuid, int version, byte[] schemeData, @Nullable UUID[] keyIds) {
this.uuid = uuid; this.uuid = uuid;
this.version = version; this.version = version;
this.schemeData = schemeData; this.schemeData = schemeData;
this.keyIds = keyIds;
} }
} }
} }

View File

@ -50,4 +50,53 @@ public final class PsshAtomUtilTest {
parsablePsshAtom.readBytes(psshSchemeData, 0, schemeData.length); parsablePsshAtom.readBytes(psshSchemeData, 0, schemeData.length);
assertThat(psshSchemeData).isEqualTo(schemeData); assertThat(psshSchemeData).isEqualTo(schemeData);
} }
@Test
public void buildVersion1Pssh() {
byte[] schemeData = new byte[] {0, 1, 2, 3, 4, 5};
UUID[] keyIds = new UUID[2];
keyIds[0] = UUID.fromString("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed");
keyIds[1] = UUID.fromString("dc03d7f3-334d-b858-f114-9ab759a925fb");
byte[] psshAtom = PsshAtomUtil.buildPsshAtom(C.WIDEVINE_UUID, keyIds, schemeData);
ParsableByteArray parsablePsshAtom = new ParsableByteArray(psshAtom);
assertThat(parsablePsshAtom.readUnsignedIntToInt()).isEqualTo(psshAtom.length);
assertThat(parsablePsshAtom.readInt()).isEqualTo(TYPE_pssh); // type
int fullAtomInt = parsablePsshAtom.readInt(); // version + flags
assertThat(parseFullAtomVersion(fullAtomInt)).isEqualTo(1);
assertThat(parseFullAtomFlags(fullAtomInt)).isEqualTo(0);
UUID systemId = new UUID(parsablePsshAtom.readLong(), parsablePsshAtom.readLong());
assertThat(systemId).isEqualTo(WIDEVINE_UUID);
assertThat(parsablePsshAtom.readUnsignedIntToInt()).isEqualTo(2);
UUID keyId0 = new UUID(parsablePsshAtom.readLong(), parsablePsshAtom.readLong());
assertThat(keyId0).isEqualTo(keyIds[0]);
UUID keyId1 = new UUID(parsablePsshAtom.readLong(), parsablePsshAtom.readLong());
assertThat(keyId1).isEqualTo(keyIds[1]);
assertThat(parsablePsshAtom.readUnsignedIntToInt()).isEqualTo(schemeData.length);
byte[] psshSchemeData = new byte[schemeData.length];
parsablePsshAtom.readBytes(psshSchemeData, 0, schemeData.length);
assertThat(psshSchemeData).isEqualTo(schemeData);
}
@Test
public void parseV1Atom() {
byte[] psshBuffer = {
0x00, 0x00, 0x00, 0x44, 0x70, 0x73, 0x73, 0x68, // BMFF box header (68 bytes, 'pssh')
0x01, 0x00, 0x00, 0x00, // Full box header (version = 1, flags = 0)
0x10, 0x77, -0x11, -0x14, -0x40, -0x4e, 0x4d, 0x02, // SystemID
-0x54, -0x1d, 0x3c, 0x1e, 0x52, -0x1e, -0x05, 0x4b, 0x00, 0x00, 0x00, 0x02, // KID_count (2)
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // First KID ("0123456789012345")
0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, // Second KID ("ABCDEFGHIJKLMNOP")
0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x00, 0x00, 0x00, 0x00, // Size of Data (0)
};
PsshAtomUtil.PsshAtom psshAtom = PsshAtomUtil.parsePsshAtom(psshBuffer);
assertThat(psshAtom).isNotNull();
System.out.println("psshAtom: " + psshAtom);
assertThat(psshAtom.version).isEqualTo(1);
assertThat(psshAtom.uuid)
.isEqualTo(UUID.fromString("1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"));
assertThat(psshAtom.keyIds).isNotNull();
assertThat(psshAtom.keyIds.length).isEqualTo(2);
assertThat(psshAtom.schemeData).isEmpty();
}
} }