package com.googlecode.mp4parser.authoring.tracks; import com.coremedia.iso.IsoFile; import com.coremedia.iso.IsoTypeReaderVariable; import com.coremedia.iso.boxes.*; import com.coremedia.iso.boxes.h264.AvcConfigurationBox; import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry; import com.googlecode.mp4parser.MemoryDataSourceImpl; import com.googlecode.mp4parser.authoring.Sample; import com.googlecode.mp4parser.authoring.Track; import com.googlecode.mp4parser.authoring.TrackMetaData; import com.googlecode.mp4parser.boxes.basemediaformat.TrackEncryptionBox; import com.googlecode.mp4parser.boxes.cenc.CencEncryptingSampleList; import com.googlecode.mp4parser.boxes.cenc.CencSampleAuxiliaryDataFormat; import javax.crypto.SecretKey; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.security.SecureRandom; import java.util.*; import static com.googlecode.mp4parser.util.CastUtils.l2i; /** * Encrypts a given track with common encryption. */ public class CencEncryptingTrackImpl implements CencEncyprtedTrack { Track source; SecretKey cek; UUID keyId; CencEncryptingSampleList samples; List<CencSampleAuxiliaryDataFormat> cencSampleAuxiliaryData; boolean dummyIvs = false; boolean subSampleEncryption = false; public CencEncryptingTrackImpl(Track source, UUID keyId, SecretKey cek) { this(source, keyId, cek, null); } public CencEncryptingTrackImpl(Track source, UUID keyId, SecretKey cek, List<CencSampleAuxiliaryDataFormat> cencSampleAuxiliaryData) { this.source = source; this.cek = cek; this.keyId = keyId; List<Sample> origSamples = source.getSamples(); if (cencSampleAuxiliaryData == null) { this.cencSampleAuxiliaryData = cencSampleAuxiliaryData = new LinkedList<CencSampleAuxiliaryDataFormat>(); BigInteger one = new BigInteger("1"); byte[] init = new byte[]{}; BigInteger ivInt = new BigInteger(1, new byte[]{0, 0, 0, 0, 0, 0, 0, 0}); if (!dummyIvs) { Random random = new SecureRandom(); random.nextBytes(init); } AvcConfigurationBox avcC = null; List<Box> boxes = source.getSampleDescriptionBox().getSampleEntry().getBoxes(); for (Box box : boxes) { if (box instanceof AvcConfigurationBox) { avcC = (AvcConfigurationBox) box; subSampleEncryption = true; } } for (Sample origSample : origSamples) { byte[] iv = ivInt.toByteArray(); byte[] eightByteIv = new byte[]{0, 0, 0, 0, 0, 0, 0, 0}; System.arraycopy( iv, iv.length - 8 > 0 ? iv.length - 8 : 0, eightByteIv, (8 - iv.length) < 0 ? 0 : (8 - iv.length), iv.length > 8 ? 8 : iv.length); CencSampleAuxiliaryDataFormat e = new CencSampleAuxiliaryDataFormat(); this.cencSampleAuxiliaryData.add(e); e.iv = eightByteIv; ByteBuffer sample = (ByteBuffer) origSample.asByteBuffer().rewind(); if (avcC != null) { int nalLengthSize = avcC.getLengthSizeMinusOne() + 1; List<CencSampleAuxiliaryDataFormat.Pair> pairs = new ArrayList<CencSampleAuxiliaryDataFormat.Pair>(5); while (sample.remaining() > 0) { int nalLength = l2i(IsoTypeReaderVariable.read(sample, nalLengthSize)); int clearBytes; int nalGrossSize = nalLength + nalLengthSize; if (nalGrossSize >= 112) { clearBytes = 96 + nalGrossSize % 16; } else { clearBytes = nalGrossSize; } pairs.add(e.createPair(clearBytes, nalGrossSize - clearBytes)); sample.position(sample.position() + nalLength); } e.pairs = pairs.toArray(new CencSampleAuxiliaryDataFormat.Pair[pairs.size()]); } ivInt = ivInt.add(one); } } samples = new CencEncryptingSampleList(cek, source.getSamples(), cencSampleAuxiliaryData); } public void setDummyIvs(boolean dummyIvs) { this.dummyIvs = dummyIvs; } public UUID getKeyId() { return keyId; } public boolean hasSubSampleEncryption() { return subSampleEncryption; } public List<CencSampleAuxiliaryDataFormat> getSampleEncryptionEntries() { return cencSampleAuxiliaryData; } public SampleDescriptionBox getSampleDescriptionBox() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); SampleDescriptionBox stsd; try { source.getSampleDescriptionBox().getBox(Channels.newChannel(baos)); stsd = (SampleDescriptionBox) new IsoFile(new MemoryDataSourceImpl(baos.toByteArray())).getBoxes().get(0); } catch (IOException e) { throw new RuntimeException("Dumping stsd to memory failed"); } // stsd is now a copy of the original stsd. Not very efficient but we don't have to do that a hundred times ... OriginalFormatBox originalFormatBox = new OriginalFormatBox(); originalFormatBox.setDataFormat(stsd.getSampleEntry().getType()); if (stsd.getSampleEntry() instanceof AudioSampleEntry) { ((AudioSampleEntry) stsd.getSampleEntry()).setType("enca"); } else if (stsd.getSampleEntry() instanceof VisualSampleEntry) { ((VisualSampleEntry) stsd.getSampleEntry()).setType("encv"); } else { throw new RuntimeException("I don't know how to cenc " + stsd.getSampleEntry().getType()); } ProtectionSchemeInformationBox sinf = new ProtectionSchemeInformationBox(); sinf.addBox(originalFormatBox); SchemeTypeBox schm = new SchemeTypeBox(); schm.setSchemeType("cenc"); schm.setSchemeVersion(0x00010000); sinf.addBox(schm); SchemeInformationBox schi = new SchemeInformationBox(); TrackEncryptionBox trackEncryptionBox = new TrackEncryptionBox(); trackEncryptionBox.setDefaultIvSize(8); trackEncryptionBox.setDefaultAlgorithmId(0x01); trackEncryptionBox.setDefault_KID(keyId); schi.addBox(trackEncryptionBox); sinf.addBox(schi); stsd.getSampleEntry().addBox(sinf); return stsd; } public long[] getSampleDurations() { return source.getSampleDurations(); } public long getDuration() { return source.getDuration(); } public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { return source.getCompositionTimeEntries(); } public long[] getSyncSamples() { return source.getSyncSamples(); } public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { return source.getSampleDependencies(); } public TrackMetaData getTrackMetaData() { return source.getTrackMetaData(); } public String getHandler() { return source.getHandler(); } public List<Sample> getSamples() { return samples; } public Box getMediaHeaderBox() { return source.getMediaHeaderBox(); } public SubSampleInformationBox getSubsampleInformationBox() { return source.getSubsampleInformationBox(); } public void close() throws IOException { source.close(); } }