Simplify parsing of encryption data + support SENC boxes.

Issue: #4
This commit is contained in:
Oliver Woodman 2014-08-11 19:42:04 +01:00
parent 8ec8840261
commit 005e98fc34
3 changed files with 79 additions and 80 deletions

View File

@ -54,6 +54,7 @@ import java.util.List;
public static final int TYPE_frma = 0x66726D61; public static final int TYPE_frma = 0x66726D61;
public static final int TYPE_saiz = 0x7361697A; public static final int TYPE_saiz = 0x7361697A;
public static final int TYPE_uuid = 0x75756964; public static final int TYPE_uuid = 0x75756964;
public static final int TYPE_senc = 0x73656E63;
public final int type; public final int type;

View File

@ -93,7 +93,7 @@ public final class FragmentedMp4Extractor {
// Parser states // Parser states
private static final int STATE_READING_ATOM_HEADER = 0; private static final int STATE_READING_ATOM_HEADER = 0;
private static final int STATE_READING_ATOM_PAYLOAD = 1; private static final int STATE_READING_ATOM_PAYLOAD = 1;
private static final int STATE_READING_CENC_AUXILIARY_DATA = 2; private static final int STATE_READING_ENCRYPTION_DATA = 2;
private static final int STATE_READING_SAMPLE = 3; private static final int STATE_READING_SAMPLE = 3;
// Atom data offsets // Atom data offsets
@ -130,6 +130,7 @@ public final class FragmentedMp4Extractor {
parsedAtoms.add(Atom.TYPE_pssh); parsedAtoms.add(Atom.TYPE_pssh);
parsedAtoms.add(Atom.TYPE_saiz); parsedAtoms.add(Atom.TYPE_saiz);
parsedAtoms.add(Atom.TYPE_uuid); parsedAtoms.add(Atom.TYPE_uuid);
parsedAtoms.add(Atom.TYPE_senc);
PARSED_ATOMS = Collections.unmodifiableSet(parsedAtoms); PARSED_ATOMS = Collections.unmodifiableSet(parsedAtoms);
} }
@ -162,8 +163,6 @@ public final class FragmentedMp4Extractor {
private int atomType; private int atomType;
private int atomSize; private int atomSize;
private ParsableByteArray atomData; private ParsableByteArray atomData;
private ParsableByteArray cencAuxiliaryData;
private int cencAuxiliaryBytesRead;
private int pendingSeekTimeMs; private int pendingSeekTimeMs;
private int sampleIndex; private int sampleIndex;
@ -273,8 +272,8 @@ public final class FragmentedMp4Extractor {
case STATE_READING_ATOM_PAYLOAD: case STATE_READING_ATOM_PAYLOAD:
results |= readAtomPayload(inputStream); results |= readAtomPayload(inputStream);
break; break;
case STATE_READING_CENC_AUXILIARY_DATA: case STATE_READING_ENCRYPTION_DATA:
results |= readCencAuxiliaryData(inputStream); results |= readEncryptionData(inputStream);
break; break;
default: default:
results |= readOrSkipSample(inputStream, out); results |= readOrSkipSample(inputStream, out);
@ -330,9 +329,6 @@ public final class FragmentedMp4Extractor {
rootAtomBytesRead = 0; rootAtomBytesRead = 0;
} }
break; break;
case STATE_READING_CENC_AUXILIARY_DATA:
cencAuxiliaryBytesRead = 0;
break;
} }
parserState = state; parserState = state;
} }
@ -354,12 +350,9 @@ public final class FragmentedMp4Extractor {
atomType = atomHeader.readInt(); atomType = atomHeader.readInt();
if (atomType == Atom.TYPE_mdat) { if (atomType == Atom.TYPE_mdat) {
int cencAuxSize = fragmentRun.auxiliarySampleInfoTotalSize; if (fragmentRun.sampleEncryptionDataNeedsFill) {
if (cencAuxSize > 0) { enterState(STATE_READING_ENCRYPTION_DATA);
cencAuxiliaryData = new ParsableByteArray(cencAuxSize);
enterState(STATE_READING_CENC_AUXILIARY_DATA);
} else { } else {
cencAuxiliaryData = null;
enterState(STATE_READING_SAMPLE); enterState(STATE_READING_SAMPLE);
} }
return 0; return 0;
@ -631,7 +624,7 @@ public final class FragmentedMp4Extractor {
if (childAtomType == Atom.TYPE_esds) { if (childAtomType == Atom.TYPE_esds) {
initializationData = parseEsdsFromParent(parent, childStartPosition); initializationData = parseEsdsFromParent(parent, childStartPosition);
// TODO: Do we really need to do this? See [redacted] // TODO: Do we really need to do this? See [redacted]
// Update sampleRate and sampleRate from the AudioSpecificConfig initialization data. // Update sampleRate and channelCount from the AudioSpecificConfig initialization data.
Pair<Integer, Integer> audioSpecificConfig = Pair<Integer, Integer> audioSpecificConfig =
CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData); CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData);
sampleRate = audioSpecificConfig.first; sampleRate = audioSpecificConfig.first;
@ -752,7 +745,7 @@ public final class FragmentedMp4Extractor {
parent.skip(13); parent.skip(13);
// Start of AudioSpecificConfig (defined in 14496-3) // Start of AudioSpecificConfig (defined in 14496-3)
parent.skip(1); // AudioSpecificConfig tag parent.skip(1); // AudioSpecificConfig tag
varIntByte = parent.readUnsignedByte(); varIntByte = parent.readUnsignedByte();
int varInt = varIntByte & 0x7F; int varInt = varIntByte & 0x7F;
while (varIntByte > 127) { while (varIntByte > 127) {
@ -789,26 +782,38 @@ public final class FragmentedMp4Extractor {
*/ */
private static void parseTraf(Track track, DefaultSampleValues extendsDefaults, private static void parseTraf(Track track, DefaultSampleValues extendsDefaults,
ContainerAtom traf, TrackFragment out, int workaroundFlags) { ContainerAtom traf, TrackFragment out, int workaroundFlags) {
LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
if (saiz != null) {
parseSaiz(saiz.getData(), out);
}
LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
long decodeTime = tfdtAtom == null ? 0 long decodeTime = tfdtAtom == null ? 0
: parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).getData()); : parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).getData());
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd); LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
DefaultSampleValues fragmentHeader = parseTfhd(extendsDefaults, tfhd.getData()); DefaultSampleValues fragmentHeader = parseTfhd(extendsDefaults, tfhd.getData());
out.setSampleDescriptionIndex(fragmentHeader.sampleDescriptionIndex); out.setSampleDescriptionIndex(fragmentHeader.sampleDescriptionIndex);
LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun); LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun);
parseTrun(track, fragmentHeader, decodeTime, workaroundFlags, trun.getData(), out); parseTrun(track, fragmentHeader, decodeTime, workaroundFlags, trun.getData(), out);
TrackEncryptionBox trackEncryptionBox =
track.sampleDescriptionEncryptionBoxes[fragmentHeader.sampleDescriptionIndex];
LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
if (saiz != null) {
parseSaiz(trackEncryptionBox, saiz.getData(), out);
}
LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc);
if (senc != null) {
parseSenc(senc.getData(), out);
}
LeafAtom uuid = traf.getLeafAtomOfType(Atom.TYPE_uuid); LeafAtom uuid = traf.getLeafAtomOfType(Atom.TYPE_uuid);
if (uuid != null) { if (uuid != null) {
parseUuid(uuid.getData(), out); parseUuid(uuid.getData(), out);
} }
} }
private static void parseSaiz(ParsableByteArray saiz, TrackFragment out) { private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz,
TrackFragment out) {
int vectorSize = encryptionBox.initializationVectorSize;
saiz.setPosition(ATOM_HEADER_SIZE); saiz.setPosition(ATOM_HEADER_SIZE);
int fullAtom = saiz.readInt(); int fullAtom = saiz.readInt();
int flags = parseFullAtomFlags(fullAtom); int flags = parseFullAtomFlags(fullAtom);
@ -818,19 +823,20 @@ public final class FragmentedMp4Extractor {
int defaultSampleInfoSize = saiz.readUnsignedByte(); int defaultSampleInfoSize = saiz.readUnsignedByte();
int sampleCount = saiz.readUnsignedIntToInt(); int sampleCount = saiz.readUnsignedIntToInt();
int totalSize = 0; int totalSize = 0;
int[] sampleInfoSizes = new int[sampleCount]; boolean[] sampleHasSubsampleEncryptionTable = new boolean[sampleCount];
if (defaultSampleInfoSize == 0) { if (defaultSampleInfoSize == 0) {
for (int i = 0; i < sampleCount; i++) { for (int i = 0; i < sampleCount; i++) {
sampleInfoSizes[i] = saiz.readUnsignedByte(); int sampleInfoSize = saiz.readUnsignedByte();
totalSize += sampleInfoSizes[i]; totalSize += sampleInfoSize;
sampleHasSubsampleEncryptionTable[i] = sampleInfoSize > vectorSize;
} }
} else { } else {
for (int i = 0; i < sampleCount; i++) { boolean subsampleEncryption = defaultSampleInfoSize > vectorSize;
sampleInfoSizes[i] = defaultSampleInfoSize; totalSize += defaultSampleInfoSize * sampleCount;
totalSize += defaultSampleInfoSize; Arrays.fill(sampleHasSubsampleEncryptionTable, subsampleEncryption);
}
} }
out.setAuxiliarySampleInfoTables(totalSize, sampleInfoSizes); out.setSampleEncryptionData(sampleHasSubsampleEncryptionTable,
new ParsableByteArray(totalSize), true);
} }
/** /**
@ -942,13 +948,8 @@ public final class FragmentedMp4Extractor {
} }
sampleDecodingTimeTable[i] = (int) ((cumulativeTime * 1000) / timescale); sampleDecodingTimeTable[i] = (int) ((cumulativeTime * 1000) / timescale);
sampleSizeTable[i] = sampleSize; sampleSizeTable[i] = sampleSize;
boolean isSync = ((sampleFlags >> 16) & 0x1) == 0; sampleIsSyncFrameTable[i] = ((sampleFlags >> 16) & 0x1) == 0
if (workaroundEveryVideoFrameIsSyncFrame && i != 0) { && (!workaroundEveryVideoFrameIsSyncFrame || i == 0);
isSync = false;
}
if (isSync) {
sampleIsSyncFrameTable[i] = true;
}
cumulativeTime += sampleDuration; cumulativeTime += sampleDuration;
} }
@ -966,9 +967,19 @@ public final class FragmentedMp4Extractor {
return; return;
} }
// See "Portable encoding of audio-video objects: The Protected Interoperable File Format // Except for the extended type, this box is identical to a SENC box. See "Portable encoding of
// (PIFF), John A. Bocharov et al, Section 5.3.2.1." // audio-video objects: The Protected Interoperable File Format (PIFF), John A. Bocharov et al,
int fullAtom = uuid.readInt(); // Section 5.3.2.1."
parseSenc(uuid, 16, out);
}
private static void parseSenc(ParsableByteArray senc, TrackFragment out) {
parseSenc(senc, 0, out);
}
private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) {
senc.setPosition(ATOM_HEADER_SIZE + offset);
int fullAtom = senc.readInt();
int flags = parseFullAtomFlags(fullAtom); int flags = parseFullAtomFlags(fullAtom);
if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) { if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) {
@ -977,15 +988,19 @@ public final class FragmentedMp4Extractor {
} }
boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0; boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0;
int numberOfEntries = uuid.readUnsignedIntToInt(); int sampleCount = senc.readUnsignedIntToInt();
if (numberOfEntries != out.length) { if (sampleCount != out.length) {
throw new IllegalStateException("Length mismatch: " + numberOfEntries + ", " + out.length); throw new IllegalStateException("Length mismatch: " + sampleCount + ", " + out.length);
} }
int sampleEncryptionDataLength = uuid.length() - uuid.getPosition(); boolean[] sampleHasSubsampleEncryptionTable = new boolean[sampleCount];
Arrays.fill(sampleHasSubsampleEncryptionTable, subsampleEncryption);
int sampleEncryptionDataLength = senc.length() - senc.getPosition();
ParsableByteArray sampleEncryptionData = new ParsableByteArray(sampleEncryptionDataLength); ParsableByteArray sampleEncryptionData = new ParsableByteArray(sampleEncryptionDataLength);
uuid.readBytes(sampleEncryptionData.getData(), 0, sampleEncryptionData.length()); senc.readBytes(sampleEncryptionData.getData(), 0, sampleEncryptionDataLength);
out.setSmoothStreamingSampleEncryptionData(sampleEncryptionData, subsampleEncryption);
out.setSampleEncryptionData(sampleHasSubsampleEncryptionTable, sampleEncryptionData, false);
} }
/** /**
@ -1044,17 +1059,14 @@ public final class FragmentedMp4Extractor {
return new SegmentIndex(atom.length(), sizes, offsets, durationsUs, timesUs); return new SegmentIndex(atom.length(), sizes, offsets, durationsUs, timesUs);
} }
private int readCencAuxiliaryData(NonBlockingInputStream inputStream) { private int readEncryptionData(NonBlockingInputStream inputStream) {
int length = cencAuxiliaryData.length(); ParsableByteArray sampleEncryptionData = fragmentRun.sampleEncryptionData;
int bytesRead = inputStream.read(cencAuxiliaryData.getData(), cencAuxiliaryBytesRead, int sampleEncryptionDataLength = sampleEncryptionData.length();
length - cencAuxiliaryBytesRead); if (inputStream.getAvailableByteCount() < sampleEncryptionDataLength) {
if (bytesRead == -1) {
return RESULT_END_OF_STREAM;
}
cencAuxiliaryBytesRead += bytesRead;
if (cencAuxiliaryBytesRead < length) {
return RESULT_NEED_MORE_DATA; return RESULT_NEED_MORE_DATA;
} }
inputStream.read(sampleEncryptionData.getData(), 0, sampleEncryptionDataLength);
fragmentRun.sampleEncryptionDataNeedsFill = false;
enterState(STATE_READING_SAMPLE); enterState(STATE_READING_SAMPLE);
return 0; return 0;
} }
@ -1093,15 +1105,12 @@ public final class FragmentedMp4Extractor {
} }
private int skipSample(NonBlockingInputStream inputStream, int sampleSize) { private int skipSample(NonBlockingInputStream inputStream, int sampleSize) {
ParsableByteArray sampleEncryptionData = cencAuxiliaryData != null ? cencAuxiliaryData ParsableByteArray sampleEncryptionData = fragmentRun.sampleEncryptionData;
: fragmentRun.smoothStreamingSampleEncryptionData;
if (sampleEncryptionData != null) { if (sampleEncryptionData != null) {
TrackEncryptionBox encryptionBox = TrackEncryptionBox encryptionBox =
track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex]; track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex];
int vectorSize = encryptionBox.initializationVectorSize; int vectorSize = encryptionBox.initializationVectorSize;
boolean subsampleEncryption = cencAuxiliaryData != null boolean subsampleEncryption = fragmentRun.sampleHasSubsampleEncryptionTable[sampleIndex];
? fragmentRun.auxiliarySampleInfoSizeTable[sampleIndex] > vectorSize
: fragmentRun.smoothStreamingUsesSubsampleEncryption;
sampleEncryptionData.skip(vectorSize); sampleEncryptionData.skip(vectorSize);
int subsampleCount = subsampleEncryption ? sampleEncryptionData.readUnsignedShort() : 1; int subsampleCount = subsampleEncryption ? sampleEncryptionData.readUnsignedShort() : 1;
if (subsampleEncryption) { if (subsampleEncryption) {
@ -1128,13 +1137,11 @@ public final class FragmentedMp4Extractor {
out.flags |= MediaExtractor.SAMPLE_FLAG_SYNC; out.flags |= MediaExtractor.SAMPLE_FLAG_SYNC;
lastSyncSampleIndex = sampleIndex; lastSyncSampleIndex = sampleIndex;
} }
if (out.allowDataBufferReplacement if (out.allowDataBufferReplacement && (out.data == null || out.data.capacity() < sampleSize)) {
&& (out.data == null || out.data.capacity() < sampleSize)) {
outputData = ByteBuffer.allocate(sampleSize); outputData = ByteBuffer.allocate(sampleSize);
out.data = outputData; out.data = outputData;
} }
ParsableByteArray sampleEncryptionData = cencAuxiliaryData != null ? cencAuxiliaryData ParsableByteArray sampleEncryptionData = fragmentRun.sampleEncryptionData;
: fragmentRun.smoothStreamingSampleEncryptionData;
if (sampleEncryptionData != null) { if (sampleEncryptionData != null) {
readSampleEncryptionData(sampleEncryptionData, out); readSampleEncryptionData(sampleEncryptionData, out);
} }
@ -1173,9 +1180,7 @@ public final class FragmentedMp4Extractor {
byte[] keyId = encryptionBox.keyId; byte[] keyId = encryptionBox.keyId;
boolean isEncrypted = encryptionBox.isEncrypted; boolean isEncrypted = encryptionBox.isEncrypted;
int vectorSize = encryptionBox.initializationVectorSize; int vectorSize = encryptionBox.initializationVectorSize;
boolean subsampleEncryption = cencAuxiliaryData != null boolean subsampleEncryption = fragmentRun.sampleHasSubsampleEncryptionTable[sampleIndex];
? fragmentRun.auxiliarySampleInfoSizeTable[sampleIndex] > vectorSize
: fragmentRun.smoothStreamingUsesSubsampleEncryption;
byte[] vector = out.cryptoInfo.iv; byte[] vector = out.cryptoInfo.iv;
if (vector == null || vector.length != 16) { if (vector == null || vector.length != 16) {

View File

@ -27,12 +27,10 @@ package com.google.android.exoplayer.parser.mp4;
public int[] sampleDecodingTimeTable; public int[] sampleDecodingTimeTable;
public int[] sampleCompositionTimeOffsetTable; public int[] sampleCompositionTimeOffsetTable;
public boolean[] sampleIsSyncFrameTable; public boolean[] sampleIsSyncFrameTable;
public boolean[] sampleHasSubsampleEncryptionTable;
public int auxiliarySampleInfoTotalSize; public ParsableByteArray sampleEncryptionData;
public int[] auxiliarySampleInfoSizeTable; public boolean sampleEncryptionDataNeedsFill;
public boolean smoothStreamingUsesSubsampleEncryption;
public ParsableByteArray smoothStreamingSampleEncryptionData;
public void setSampleDescriptionIndex(int sampleDescriptionIndex) { public void setSampleDescriptionIndex(int sampleDescriptionIndex) {
this.sampleDescriptionIndex = sampleDescriptionIndex; this.sampleDescriptionIndex = sampleDescriptionIndex;
@ -47,16 +45,11 @@ package com.google.android.exoplayer.parser.mp4;
this.length = sampleSizeTable.length; this.length = sampleSizeTable.length;
} }
public void setAuxiliarySampleInfoTables(int totalAuxiliarySampleInfoSize, public void setSampleEncryptionData(boolean[] sampleHasSubsampleEncryptionTable,
int[] auxiliarySampleInfoSizeTable) { ParsableByteArray sampleEncryptionData, boolean sampleEncryptionDataNeedsFill) {
this.auxiliarySampleInfoTotalSize = totalAuxiliarySampleInfoSize; this.sampleHasSubsampleEncryptionTable = sampleHasSubsampleEncryptionTable;
this.auxiliarySampleInfoSizeTable = auxiliarySampleInfoSizeTable; this.sampleEncryptionData = sampleEncryptionData;
} this.sampleEncryptionDataNeedsFill = sampleEncryptionDataNeedsFill;
public void setSmoothStreamingSampleEncryptionData(ParsableByteArray data,
boolean usesSubsampleEncryption) {
this.smoothStreamingSampleEncryptionData = data;
this.smoothStreamingUsesSubsampleEncryption = usesSubsampleEncryption;
} }
public int getSamplePresentationTime(int index) { public int getSamplePresentationTime(int index) {