package com.googlecode.mp4parser.boxes; import com.coremedia.iso.Hex; import com.coremedia.iso.IsoTypeReader; import com.coremedia.iso.IsoTypeWriter; import com.googlecode.mp4parser.AbstractFullBox; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; public abstract class AbstractSampleEncryptionBox extends AbstractFullBox { int algorithmId = -1; int ivSize = -1; byte[] kid = new byte[16]; List<Entry> entries = new LinkedList<Entry>(); protected AbstractSampleEncryptionBox(String type) { super(type); } public int getOffsetToFirstIV() { int offset = (getSize() > (1l << 32) ? 16 : 8); offset += isOverrideTrackEncryptionBoxParameters() ? 20 : 0; offset += 4; //num entries return offset; } @Override public void _parseDetails(ByteBuffer content) { parseVersionAndFlags(content); if ((getFlags() & 0x1) > 0) { algorithmId = IsoTypeReader.readUInt24(content); ivSize = IsoTypeReader.readUInt8(content); kid = new byte[16]; content.get(kid); } long numOfEntries = IsoTypeReader.readUInt32(content); while (numOfEntries-- > 0) { Entry e = new Entry(); e.iv = new byte[((getFlags() & 0x1) > 0) ? ivSize : 8]; content.get(e.iv); if ((getFlags() & 0x2) > 0) { int numOfPairs = IsoTypeReader.readUInt16(content); e.pairs = new LinkedList<Entry.Pair>(); while (numOfPairs-- > 0) { e.pairs.add(e.createPair(IsoTypeReader.readUInt16(content), IsoTypeReader.readUInt32(content))); } } entries.add(e); } } public int getSampleCount() { return entries.size(); } public List<Entry> getEntries() { return entries; } public void setEntries(List<Entry> entries) { this.entries = entries; } public int getAlgorithmId() { return algorithmId; } public void setAlgorithmId(int algorithmId) { this.algorithmId = algorithmId; } public int getIvSize() { return ivSize; } public void setIvSize(int ivSize) { this.ivSize = ivSize; } public byte[] getKid() { return kid; } public void setKid(byte[] kid) { this.kid = kid; } public boolean isSubSampleEncryption() { return (getFlags() & 0x2) > 0; } public boolean isOverrideTrackEncryptionBoxParameters() { return (getFlags() & 0x1) > 0; } public void setSubSampleEncryption(boolean b) { if (b) { setFlags(getFlags() | 0x2); } else { setFlags(getFlags() & (0xffffff ^ 0x2)); } } public void setOverrideTrackEncryptionBoxParameters(boolean b) { if (b) { setFlags(getFlags() | 0x1); } else { setFlags(getFlags() & (0xffffff ^ 0x1)); } } @Override protected void getContent(ByteBuffer byteBuffer) { writeVersionAndFlags(byteBuffer); if (isOverrideTrackEncryptionBoxParameters()) { IsoTypeWriter.writeUInt24(byteBuffer, algorithmId); IsoTypeWriter.writeUInt8(byteBuffer, ivSize); byteBuffer.put(kid); } IsoTypeWriter.writeUInt32(byteBuffer, entries.size()); for (Entry entry : entries) { if (isOverrideTrackEncryptionBoxParameters()) { byte[] ivFull = new byte[ivSize]; System.arraycopy(entry.iv, 0, ivFull, ivSize - entry.iv.length, entry.iv.length); byteBuffer.put(ivFull); } else { // just put the iv - i don't know any better byteBuffer.put(entry.iv); } if (isSubSampleEncryption()) { IsoTypeWriter.writeUInt16(byteBuffer, entry.pairs.size()); for (Entry.Pair pair : entry.pairs) { IsoTypeWriter.writeUInt16(byteBuffer, pair.clear); IsoTypeWriter.writeUInt32(byteBuffer, pair.encrypted); } } } } @Override protected long getContentSize() { long contentSize = 4; if (isOverrideTrackEncryptionBoxParameters()) { contentSize += 4; contentSize += kid.length; } contentSize += 4; for (Entry entry : entries) { contentSize += entry.getSize(); } return contentSize; } @Override public void getBox(WritableByteChannel os) throws IOException { super.getBox(os); } public Entry createEntry() { return new Entry(); } public class Entry { public byte[] iv; public List<Pair> pairs = new LinkedList<Pair>(); public int getSize() { int size = 0; if (isOverrideTrackEncryptionBoxParameters()) { size = ivSize; } else { size = iv.length; } if (isSubSampleEncryption()) { size += 2; for (Entry.Pair pair : pairs) { size += 6; } } return size; } public Pair createPair(int clear, long encrypted) { return new Pair(clear, encrypted); } public class Pair { public int clear; public long encrypted; public Pair(int clear, long encrypted) { this.clear = clear; this.encrypted = encrypted; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Pair pair = (Pair) o; if (clear != pair.clear) return false; if (encrypted != pair.encrypted) return false; return true; } @Override public int hashCode() { int result = clear; result = 31 * result + (int) (encrypted ^ (encrypted >>> 32)); return result; } @Override public String toString() { return "clr:" + clear + " enc:" + encrypted; } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Entry entry = (Entry) o; if (!new BigInteger(iv).equals(new BigInteger(entry.iv))) return false; if (pairs != null ? !pairs.equals(entry.pairs) : entry.pairs != null) return false; return true; } @Override public int hashCode() { int result = iv != null ? Arrays.hashCode(iv) : 0; result = 31 * result + (pairs != null ? pairs.hashCode() : 0); return result; } @Override public String toString() { return "Entry{" + "iv=" + Hex.encodeHex(iv) + ", pairs=" + pairs + '}'; } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AbstractSampleEncryptionBox that = (AbstractSampleEncryptionBox) o; if (algorithmId != that.algorithmId) return false; if (ivSize != that.ivSize) return false; if (entries != null ? !entries.equals(that.entries) : that.entries != null) return false; if (!Arrays.equals(kid, that.kid)) return false; return true; } @Override public int hashCode() { int result = algorithmId; result = 31 * result + ivSize; result = 31 * result + (kid != null ? Arrays.hashCode(kid) : 0); result = 31 * result + (entries != null ? entries.hashCode() : 0); return result; } public List<Short> getEntrySizes() { List<Short> entrySizes = new ArrayList<Short>(entries.size()); for (Entry entry : entries) { short size = (short) entry.iv.length; if (isSubSampleEncryption()) { size += 2; //numPairs size += entry.pairs.size() * 6; } entrySizes.add(size); } return entrySizes; } }