Parse stz2 Atoms during mp4 extraction.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=129892143
This commit is contained in:
olly 2016-08-10 11:48:43 -07:00 committed by Oliver Woodman
parent e883b7c270
commit f6fdcee9b3
4 changed files with 201 additions and 20 deletions

View File

@ -0,0 +1,58 @@
/*
* 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 com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import junit.framework.TestCase;
/**
* Tests for {@link AtomParsers}.
*/
public final class AtomParsersTest extends TestCase {
private static final String ATOM_HEADER = "000000000000000000000000";
private static final String SAMPLE_COUNT = "00000004";
private static final byte[] FOUR_BIT_STZ2 = Util.getBytesFromHexString(ATOM_HEADER + "00000004"
+ SAMPLE_COUNT + "1234");
private static final byte[] EIGHT_BIT_STZ2 = Util.getBytesFromHexString(ATOM_HEADER + "00000008"
+ SAMPLE_COUNT + "01020304");
private static final byte[] SIXTEEN_BIT_STZ2 = Util.getBytesFromHexString(ATOM_HEADER + "00000010"
+ SAMPLE_COUNT + "0001000200030004");
public void testStz2Parsing4BitFieldSize() {
verifyParsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(FOUR_BIT_STZ2)));
}
public void testStz2Parsing8BitFieldSize() {
verifyParsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(EIGHT_BIT_STZ2)));
}
public void testStz2Parsing16BitFieldSize() {
verifyParsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(SIXTEEN_BIT_STZ2)));
}
private void verifyParsing(Atom.LeafAtom stz2Atom) {
AtomParsers.Stz2SampleSizeBox box = new AtomParsers.Stz2SampleSizeBox(stz2Atom);
assertEquals(4, box.getSampleCount());
assertFalse(box.isFixedSampleSize());
for (int i = 0; i < box.getSampleCount(); i++) {
assertEquals(i + 1, box.readNextSampleSize());
}
}
}

View File

@ -111,6 +111,7 @@ import java.util.List;
public static final int TYPE_ctts = Util.getIntegerCodeForString("ctts");
public static final int TYPE_stsc = Util.getIntegerCodeForString("stsc");
public static final int TYPE_stsz = Util.getIntegerCodeForString("stsz");
public static final int TYPE_stz2 = Util.getIntegerCodeForString("stz2");
public static final int TYPE_stco = Util.getIntegerCodeForString("stco");
public static final int TYPE_co64 = Util.getIntegerCodeForString("co64");
public static final int TYPE_tx3g = Util.getIntegerCodeForString("tx3g");

View File

@ -50,7 +50,7 @@ import java.util.List;
* @param trak Atom to decode.
* @param mvhd Movie header atom, used to get the timescale.
* @param duration The duration in units of the timescale declared in the mvhd atom, or -1 if the
* duration should be parsed from the tkhd atom.
* duration should be parsed from the tkhd atom.
* @param drmInitData {@link DrmInitData} to be included in the format.
* @param isQuickTime True for QuickTime media. False otherwise.
* @return A {@link Track} instance, or {@code null} if the track's type isn't supported.
@ -98,8 +98,22 @@ import java.util.List;
*/
public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAtom,
GaplessInfoHolder gaplessInfoHolder) throws ParserException {
// Array of sample sizes.
ParsableByteArray stsz = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz).data;
SampleSizeBox sampleSizeBox;
Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz);
if (stszAtom != null) {
sampleSizeBox = new StszSampleSizeBox(stszAtom);
} else {
Atom.LeafAtom stz2Atom = stblAtom.getLeafAtomOfType(Atom.TYPE_stz2);
if (stz2Atom == null) {
throw new ParserException("Track has no sample table size information");
}
sampleSizeBox = new Stz2SampleSizeBox(stz2Atom);
}
int sampleCount = sampleSizeBox.getSampleCount();
if (sampleCount == 0) {
return new TrackSampleTable(new long[0], new int[0], 0, new long[0], new int[0]);
}
// Entries are byte offsets of chunks.
boolean chunkOffsetsAreLongs = false;
@ -120,14 +134,6 @@ import java.util.List;
Atom.LeafAtom cttsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_ctts);
ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null;
// Skip full atom.
stsz.setPosition(Atom.FULL_HEADER_SIZE);
int fixedSampleSize = stsz.readUnsignedIntToInt();
int sampleCount = stsz.readUnsignedIntToInt();
if (sampleCount == 0) {
return new TrackSampleTable(new long[0], new int[0], 0, new long[0], new int[0]);
}
// Prepare to read chunk information.
ChunkIterator chunkIterator = new ChunkIterator(stsc, chunkOffsets, chunkOffsetsAreLongs);
@ -160,7 +166,7 @@ import java.util.List;
}
// True if we can rechunk fixed-sample-size data. Note that we only rechunk raw audio.
boolean isRechunkable = fixedSampleSize != 0
boolean isRechunkable = sampleSizeBox.isFixedSampleSize()
&& MimeTypes.AUDIO_RAW.equals(track.format.sampleMimeType)
&& remainingTimestampDeltaChanges == 0 && remainingTimestampOffsetChanges == 0
&& remainingSynchronizationSamples == 0;
@ -204,7 +210,7 @@ import java.util.List;
}
offsets[i] = offset;
sizes[i] = fixedSampleSize == 0 ? stsz.readUnsignedIntToInt() : fixedSampleSize;
sizes[i] = sampleSizeBox.readNextSampleSize();
if (sizes[i] > maximumSize) {
maximumSize = sizes[i];
}
@ -253,6 +259,7 @@ import java.util.List;
chunkOffsetsBytes[chunkIterator.index] = chunkIterator.offset;
chunkSampleCounts[chunkIterator.index] = chunkIterator.numSamples;
}
int fixedSampleSize = sampleSizeBox.readNextSampleSize();
FixedSampleSizeRechunker.Results rechunkedResults = FixedSampleSizeRechunker.rechunk(
fixedSampleSize, chunkOffsetsBytes, chunkSampleCounts, timestampDeltaInTimeUnits);
offsets = rechunkedResults.offsets;
@ -561,7 +568,7 @@ import java.util.List;
*
* @param mdhd The mdhd atom to decode.
* @return A pair consisting of the media timescale defined as the number of time units that pass
* in one second, and the language code.
* in one second, and the language code.
*/
private static Pair<Long, String> parseMdhd(ParsableByteArray mdhd) {
mdhd.setPosition(Atom.HEADER_SIZE);
@ -717,7 +724,7 @@ import java.util.List;
*
* @param edtsAtom edts (edit box) atom to decode.
* @return Pair of edit list durations and edit list media times, or a pair of nulls if they are
* not present.
* not present.
*/
private static Pair<long[], long[]> parseEdts(Atom.ContainerAtom edtsAtom) {
Atom.LeafAtom elst;
@ -867,7 +874,9 @@ import java.util.List;
}
}
/** Returns the position of the esds box within a parent, or -1 if no esds box is found */
/**
* Returns the position of the esds box within a parent, or -1 if no esds box is found
*/
private static int findEsdsPosition(ParsableByteArray parent, int position, int size) {
int childAtomPosition = parent.getPosition();
while (childAtomPosition - position < size) {
@ -883,7 +892,9 @@ import java.util.List;
return -1;
}
/** Returns codec-specific initialization data contained in an esds box. */
/**
* Returns codec-specific initialization data contained in an esds box.
*/
private static Pair<String, byte[]> parseEsdsFromParent(ParsableByteArray parent, int position) {
parent.setPosition(position + Atom.HEADER_SIZE + 4);
// Start of the ES_Descriptor (defined in 14496-1)
@ -1028,7 +1039,9 @@ import java.util.List;
return null;
}
/** Parses the size of an expandable class, as specified by ISO 14496-1 subsection 8.3.3. */
/**
* Parses the size of an expandable class, as specified by ISO 14496-1 subsection 8.3.3.
*/
private static int parseExpandableClassSize(ParsableByteArray data) {
int currentByte = data.readUnsignedByte();
int size = currentByte & 0x7F;
@ -1122,6 +1135,114 @@ import java.util.List;
requiredSampleTransformation = Track.TRANSFORMATION_NONE;
}
}
/**
* A box containing sample sizes (e.g. stsz, stz2).
*/
private interface SampleSizeBox {
/**
* Returns the number of samples.
*/
int getSampleCount();
/**
* Returns the size for the next sample.
*/
int readNextSampleSize();
/**
* Returns whether samples have a fixed size.
*/
boolean isFixedSampleSize();
}
/**
* An stsz sample size box.
*/
/* package */ static final class StszSampleSizeBox implements SampleSizeBox {
private final int fixedSampleSize;
private final int sampleCount;
private final ParsableByteArray data;
public StszSampleSizeBox(Atom.LeafAtom stszAtom) {
data = stszAtom.data;
data.setPosition(Atom.FULL_HEADER_SIZE);
fixedSampleSize = data.readUnsignedIntToInt();
sampleCount = data.readUnsignedIntToInt();
}
@Override
public int getSampleCount() {
return sampleCount;
}
@Override
public int readNextSampleSize() {
return fixedSampleSize == 0 ? data.readUnsignedIntToInt() : fixedSampleSize;
}
@Override
public boolean isFixedSampleSize() {
return fixedSampleSize != 0;
}
}
/**
* An stz2 sample size box.
*/
/* package */ static final class Stz2SampleSizeBox implements SampleSizeBox {
private final ParsableByteArray data;
private final int sampleCount;
private final int fieldSize; // Can be 4, 8, or 16.
// Used only if fieldSize == 4.
private int sampleIndex;
private int currentByte;
public Stz2SampleSizeBox(Atom.LeafAtom stz2Atom) {
data = stz2Atom.data;
data.setPosition(Atom.FULL_HEADER_SIZE);
fieldSize = data.readUnsignedIntToInt() & 0x000000FF;
sampleCount = data.readUnsignedIntToInt();
}
@Override
public int getSampleCount() {
return sampleCount;
}
@Override
public int readNextSampleSize() {
if (fieldSize == 8) {
return data.readUnsignedByte();
} else if (fieldSize == 16) {
return data.readUnsignedShort();
} else {
// fieldSize == 4.
if ((sampleIndex++ % 2) == 0) {
// Read the next byte into our cached byte when we are reading the upper bits.
currentByte = data.readUnsignedByte();
// Read the upper bits from the byte and shift them to the lower 4 bits.
return (currentByte & 0xF0) >> 4;
} else {
// Mask out the upper 4 bits of the last byte we read.
return currentByte & 0x0F;
}
}
}
@Override
public boolean isFixedSampleSize() {
return false;
}
}
}

View File

@ -475,8 +475,9 @@ public final class Mp4Extractor implements Extractor, SeekMap {
return atom == Atom.TYPE_mdhd || atom == Atom.TYPE_mvhd || atom == Atom.TYPE_hdlr
|| atom == Atom.TYPE_stsd || atom == Atom.TYPE_stts || atom == Atom.TYPE_stss
|| atom == Atom.TYPE_ctts || atom == Atom.TYPE_elst || atom == Atom.TYPE_stsc
|| atom == Atom.TYPE_stsz || atom == Atom.TYPE_stco || atom == Atom.TYPE_co64
|| atom == Atom.TYPE_tkhd || atom == Atom.TYPE_ftyp || atom == Atom.TYPE_udta;
|| atom == Atom.TYPE_stsz || atom == Atom.TYPE_stz2 || atom == Atom.TYPE_stco
|| atom == Atom.TYPE_co64 || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_ftyp
|| atom == Atom.TYPE_udta;
}
/**