mirror of
https://github.com/androidx/media.git
synced 2025-05-10 09:12:16 +08:00
Relocate content of Mp4Util to more sensible locations.
- Atom related stuff in Mp4Util is moved to Atom. - Remainder of Mp4Util is renamed to H264Util.
This commit is contained in:
parent
fea2140d57
commit
61443b2f12
@ -26,9 +26,9 @@ import com.google.android.exoplayer.mp4.Atom;
|
|||||||
import com.google.android.exoplayer.mp4.Atom.ContainerAtom;
|
import com.google.android.exoplayer.mp4.Atom.ContainerAtom;
|
||||||
import com.google.android.exoplayer.mp4.Atom.LeafAtom;
|
import com.google.android.exoplayer.mp4.Atom.LeafAtom;
|
||||||
import com.google.android.exoplayer.mp4.CommonMp4AtomParsers;
|
import com.google.android.exoplayer.mp4.CommonMp4AtomParsers;
|
||||||
import com.google.android.exoplayer.mp4.Mp4Util;
|
|
||||||
import com.google.android.exoplayer.mp4.Track;
|
import com.google.android.exoplayer.mp4.Track;
|
||||||
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
||||||
|
import com.google.android.exoplayer.util.H264Util;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
@ -157,7 +157,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
public FragmentedMp4Extractor(int workaroundFlags) {
|
public FragmentedMp4Extractor(int workaroundFlags) {
|
||||||
this.workaroundFlags = workaroundFlags;
|
this.workaroundFlags = workaroundFlags;
|
||||||
parserState = STATE_READING_ATOM_HEADER;
|
parserState = STATE_READING_ATOM_HEADER;
|
||||||
atomHeader = new ParsableByteArray(Mp4Util.ATOM_HEADER_SIZE);
|
atomHeader = new ParsableByteArray(Atom.ATOM_HEADER_SIZE);
|
||||||
extendedTypeScratch = new byte[16];
|
extendedTypeScratch = new byte[16];
|
||||||
containerAtoms = new Stack<ContainerAtom>();
|
containerAtoms = new Stack<ContainerAtom>();
|
||||||
fragmentRun = new TrackFragment();
|
fragmentRun = new TrackFragment();
|
||||||
@ -259,14 +259,14 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int readAtomHeader(NonBlockingInputStream inputStream) {
|
private int readAtomHeader(NonBlockingInputStream inputStream) {
|
||||||
int remainingBytes = Mp4Util.ATOM_HEADER_SIZE - atomBytesRead;
|
int remainingBytes = Atom.ATOM_HEADER_SIZE - atomBytesRead;
|
||||||
int bytesRead = inputStream.read(atomHeader.data, atomBytesRead, remainingBytes);
|
int bytesRead = inputStream.read(atomHeader.data, atomBytesRead, remainingBytes);
|
||||||
if (bytesRead == -1) {
|
if (bytesRead == -1) {
|
||||||
return RESULT_END_OF_STREAM;
|
return RESULT_END_OF_STREAM;
|
||||||
}
|
}
|
||||||
rootAtomBytesRead += bytesRead;
|
rootAtomBytesRead += bytesRead;
|
||||||
atomBytesRead += bytesRead;
|
atomBytesRead += bytesRead;
|
||||||
if (atomBytesRead != Mp4Util.ATOM_HEADER_SIZE) {
|
if (atomBytesRead != Atom.ATOM_HEADER_SIZE) {
|
||||||
return RESULT_NEED_MORE_DATA;
|
return RESULT_NEED_MORE_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,10 +288,10 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
if (CONTAINER_TYPES.contains(atomTypeInteger)) {
|
if (CONTAINER_TYPES.contains(atomTypeInteger)) {
|
||||||
enterState(STATE_READING_ATOM_HEADER);
|
enterState(STATE_READING_ATOM_HEADER);
|
||||||
containerAtoms.add(new ContainerAtom(atomType,
|
containerAtoms.add(new ContainerAtom(atomType,
|
||||||
rootAtomBytesRead + atomSize - Mp4Util.ATOM_HEADER_SIZE));
|
rootAtomBytesRead + atomSize - Atom.ATOM_HEADER_SIZE));
|
||||||
} else {
|
} else {
|
||||||
atomData = new ParsableByteArray(atomSize);
|
atomData = new ParsableByteArray(atomSize);
|
||||||
System.arraycopy(atomHeader.data, 0, atomData.data, 0, Mp4Util.ATOM_HEADER_SIZE);
|
System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.ATOM_HEADER_SIZE);
|
||||||
enterState(STATE_READING_ATOM_PAYLOAD);
|
enterState(STATE_READING_ATOM_PAYLOAD);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -360,7 +360,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
LeafAtom child = moovChildren.get(i);
|
LeafAtom child = moovChildren.get(i);
|
||||||
if (child.type == Atom.TYPE_pssh) {
|
if (child.type == Atom.TYPE_pssh) {
|
||||||
ParsableByteArray psshAtom = child.data;
|
ParsableByteArray psshAtom = child.data;
|
||||||
psshAtom.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
|
psshAtom.setPosition(Atom.FULL_ATOM_HEADER_SIZE);
|
||||||
UUID uuid = new UUID(psshAtom.readLong(), psshAtom.readLong());
|
UUID uuid = new UUID(psshAtom.readLong(), psshAtom.readLong());
|
||||||
int dataSize = psshAtom.readInt();
|
int dataSize = psshAtom.readInt();
|
||||||
byte[] data = new byte[dataSize];
|
byte[] data = new byte[dataSize];
|
||||||
@ -399,7 +399,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
* Parses a trex atom (defined in 14496-12).
|
* Parses a trex atom (defined in 14496-12).
|
||||||
*/
|
*/
|
||||||
private static DefaultSampleValues parseTrex(ParsableByteArray trex) {
|
private static DefaultSampleValues parseTrex(ParsableByteArray trex) {
|
||||||
trex.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE + 4);
|
trex.setPosition(Atom.FULL_ATOM_HEADER_SIZE + 4);
|
||||||
int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1;
|
int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1;
|
||||||
int defaultSampleDuration = trex.readUnsignedIntToInt();
|
int defaultSampleDuration = trex.readUnsignedIntToInt();
|
||||||
int defaultSampleSize = trex.readUnsignedIntToInt();
|
int defaultSampleSize = trex.readUnsignedIntToInt();
|
||||||
@ -453,9 +453,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz,
|
private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz,
|
||||||
TrackFragment out) {
|
TrackFragment out) {
|
||||||
int vectorSize = encryptionBox.initializationVectorSize;
|
int vectorSize = encryptionBox.initializationVectorSize;
|
||||||
saiz.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
saiz.setPosition(Atom.ATOM_HEADER_SIZE);
|
||||||
int fullAtom = saiz.readInt();
|
int fullAtom = saiz.readInt();
|
||||||
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
|
int flags = Atom.parseFullAtomFlags(fullAtom);
|
||||||
if ((flags & 0x01) == 1) {
|
if ((flags & 0x01) == 1) {
|
||||||
saiz.skip(8);
|
saiz.skip(8);
|
||||||
}
|
}
|
||||||
@ -490,9 +490,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
*/
|
*/
|
||||||
private static DefaultSampleValues parseTfhd(DefaultSampleValues extendsDefaults,
|
private static DefaultSampleValues parseTfhd(DefaultSampleValues extendsDefaults,
|
||||||
ParsableByteArray tfhd) {
|
ParsableByteArray tfhd) {
|
||||||
tfhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
tfhd.setPosition(Atom.ATOM_HEADER_SIZE);
|
||||||
int fullAtom = tfhd.readInt();
|
int fullAtom = tfhd.readInt();
|
||||||
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
|
int flags = Atom.parseFullAtomFlags(fullAtom);
|
||||||
|
|
||||||
tfhd.skip(4); // trackId
|
tfhd.skip(4); // trackId
|
||||||
if ((flags & 0x01 /* base_data_offset_present */) != 0) {
|
if ((flags & 0x01 /* base_data_offset_present */) != 0) {
|
||||||
@ -519,9 +519,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
* media, expressed in the media's timescale.
|
* media, expressed in the media's timescale.
|
||||||
*/
|
*/
|
||||||
private static long parseTfdt(ParsableByteArray tfdt) {
|
private static long parseTfdt(ParsableByteArray tfdt) {
|
||||||
tfdt.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
tfdt.setPosition(Atom.ATOM_HEADER_SIZE);
|
||||||
int fullAtom = tfdt.readInt();
|
int fullAtom = tfdt.readInt();
|
||||||
int version = Mp4Util.parseFullAtomVersion(fullAtom);
|
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||||
return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
|
return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -536,9 +536,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
*/
|
*/
|
||||||
private static void parseTrun(Track track, DefaultSampleValues defaultSampleValues,
|
private static void parseTrun(Track track, DefaultSampleValues defaultSampleValues,
|
||||||
long decodeTime, int workaroundFlags, ParsableByteArray trun, TrackFragment out) {
|
long decodeTime, int workaroundFlags, ParsableByteArray trun, TrackFragment out) {
|
||||||
trun.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
trun.setPosition(Atom.ATOM_HEADER_SIZE);
|
||||||
int fullAtom = trun.readInt();
|
int fullAtom = trun.readInt();
|
||||||
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
|
int flags = Atom.parseFullAtomFlags(fullAtom);
|
||||||
|
|
||||||
int sampleCount = trun.readUnsignedIntToInt();
|
int sampleCount = trun.readUnsignedIntToInt();
|
||||||
if ((flags & 0x01 /* data_offset_present */) != 0) {
|
if ((flags & 0x01 /* data_offset_present */) != 0) {
|
||||||
@ -596,7 +596,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
|
|
||||||
private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
|
private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
|
||||||
byte[] extendedTypeScratch) {
|
byte[] extendedTypeScratch) {
|
||||||
uuid.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
uuid.setPosition(Atom.ATOM_HEADER_SIZE);
|
||||||
uuid.readBytes(extendedTypeScratch, 0, 16);
|
uuid.readBytes(extendedTypeScratch, 0, 16);
|
||||||
|
|
||||||
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
|
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
|
||||||
@ -615,9 +615,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) {
|
private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) {
|
||||||
senc.setPosition(Mp4Util.ATOM_HEADER_SIZE + offset);
|
senc.setPosition(Atom.ATOM_HEADER_SIZE + offset);
|
||||||
int fullAtom = senc.readInt();
|
int fullAtom = senc.readInt();
|
||||||
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
|
int flags = Atom.parseFullAtomFlags(fullAtom);
|
||||||
|
|
||||||
if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) {
|
if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) {
|
||||||
// TODO: Implement this.
|
// TODO: Implement this.
|
||||||
@ -639,9 +639,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
* Parses a sidx atom (defined in 14496-12).
|
* Parses a sidx atom (defined in 14496-12).
|
||||||
*/
|
*/
|
||||||
private static SegmentIndex parseSidx(ParsableByteArray atom) {
|
private static SegmentIndex parseSidx(ParsableByteArray atom) {
|
||||||
atom.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
atom.setPosition(Atom.ATOM_HEADER_SIZE);
|
||||||
int fullAtom = atom.readInt();
|
int fullAtom = atom.readInt();
|
||||||
int version = Mp4Util.parseFullAtomVersion(fullAtom);
|
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||||
|
|
||||||
atom.skip(4);
|
atom.skip(4);
|
||||||
long timescale = atom.readUnsignedInt();
|
long timescale = atom.readUnsignedInt();
|
||||||
@ -781,7 +781,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
if (track.type == Track.TYPE_VIDEO) {
|
if (track.type == Track.TYPE_VIDEO) {
|
||||||
// The mp4 file contains length-prefixed NAL units, but the decoder wants start code
|
// The mp4 file contains length-prefixed NAL units, but the decoder wants start code
|
||||||
// delimited content.
|
// delimited content.
|
||||||
Mp4Util.replaceLengthPrefixesWithAvcStartCodes(outputData, sampleSize);
|
H264Util.replaceLengthPrefixesWithAvcStartCodes(outputData, sampleSize);
|
||||||
}
|
}
|
||||||
out.size = sampleSize;
|
out.size = sampleSize;
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,8 @@ package com.google.android.exoplayer.hls.parser;
|
|||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
import com.google.android.exoplayer.hls.parser.HlsExtractor.TrackOutput;
|
import com.google.android.exoplayer.hls.parser.HlsExtractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.mp4.Mp4Util;
|
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
import com.google.android.exoplayer.util.H264Util;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
@ -73,7 +73,7 @@ import java.util.List;
|
|||||||
|
|
||||||
// Scan the appended data, processing NAL units as they are encountered
|
// Scan the appended data, processing NAL units as they are encountered
|
||||||
while (offset < limit) {
|
while (offset < limit) {
|
||||||
int nextNalUnitOffset = Mp4Util.findNalUnit(dataArray, offset, limit, prefixFlags);
|
int nextNalUnitOffset = H264Util.findNalUnit(dataArray, offset, limit, prefixFlags);
|
||||||
if (nextNalUnitOffset < limit) {
|
if (nextNalUnitOffset < limit) {
|
||||||
// We've seen the start of a NAL unit.
|
// We've seen the start of a NAL unit.
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ import java.util.List;
|
|||||||
feedNalUnitTargetBuffersData(dataArray, offset, nextNalUnitOffset);
|
feedNalUnitTargetBuffersData(dataArray, offset, nextNalUnitOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
int nalUnitType = Mp4Util.getNalUnitType(dataArray, nextNalUnitOffset);
|
int nalUnitType = H264Util.getNalUnitType(dataArray, nextNalUnitOffset);
|
||||||
int nalUnitOffsetInData = nextNalUnitOffset - limit;
|
int nalUnitOffsetInData = nextNalUnitOffset - limit;
|
||||||
if (nalUnitType == NAL_UNIT_TYPE_AUD) {
|
if (nalUnitType == NAL_UNIT_TYPE_AUD) {
|
||||||
if (output.isWritingSample()) {
|
if (output.isWritingSample()) {
|
||||||
|
@ -24,6 +24,18 @@ import java.util.List;
|
|||||||
|
|
||||||
public abstract class Atom {
|
public abstract class Atom {
|
||||||
|
|
||||||
|
/** Size of an atom header, in bytes. */
|
||||||
|
public static final int ATOM_HEADER_SIZE = 8;
|
||||||
|
|
||||||
|
/** Size of a long atom header, in bytes. */
|
||||||
|
public static final int LONG_ATOM_HEADER_SIZE = 16;
|
||||||
|
|
||||||
|
/** Size of a full atom header, in bytes. */
|
||||||
|
public static final int FULL_ATOM_HEADER_SIZE = 12;
|
||||||
|
|
||||||
|
/** Value for the first 32 bits of atomSize when the atom size is actually a long value. */
|
||||||
|
public static final int LONG_SIZE_PREFIX = 1;
|
||||||
|
|
||||||
public static final int TYPE_ftyp = getAtomTypeInteger("ftyp");
|
public static final int TYPE_ftyp = getAtomTypeInteger("ftyp");
|
||||||
public static final int TYPE_avc1 = getAtomTypeInteger("avc1");
|
public static final int TYPE_avc1 = getAtomTypeInteger("avc1");
|
||||||
public static final int TYPE_avc3 = getAtomTypeInteger("avc3");
|
public static final int TYPE_avc3 = getAtomTypeInteger("avc3");
|
||||||
@ -154,6 +166,20 @@ public abstract class Atom {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the version number out of the additional integer component of a full atom.
|
||||||
|
*/
|
||||||
|
public static int parseFullAtomVersion(int fullAtomInt) {
|
||||||
|
return 0x000000FF & (fullAtomInt >> 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the atom flags out of the additional integer component of a full atom.
|
||||||
|
*/
|
||||||
|
public static int parseFullAtomFlags(int fullAtomInt) {
|
||||||
|
return 0x00FFFFFF & fullAtomInt;
|
||||||
|
}
|
||||||
|
|
||||||
private static String getAtomTypeString(int type) {
|
private static String getAtomTypeString(int type) {
|
||||||
return "" + (char) (type >> 24)
|
return "" + (char) (type >> 24)
|
||||||
+ (char) ((type >> 16) & 0xFF)
|
+ (char) ((type >> 16) & 0xFF)
|
||||||
|
@ -20,6 +20,7 @@ import com.google.android.exoplayer.MediaFormat;
|
|||||||
import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox;
|
import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
||||||
|
import com.google.android.exoplayer.util.H264Util;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
@ -102,7 +103,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null;
|
ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null;
|
||||||
|
|
||||||
// Skip full atom.
|
// Skip full atom.
|
||||||
stsz.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
|
stsz.setPosition(Atom.FULL_ATOM_HEADER_SIZE);
|
||||||
int fixedSampleSize = stsz.readUnsignedIntToInt();
|
int fixedSampleSize = stsz.readUnsignedIntToInt();
|
||||||
int sampleCount = stsz.readUnsignedIntToInt();
|
int sampleCount = stsz.readUnsignedIntToInt();
|
||||||
|
|
||||||
@ -112,10 +113,10 @@ public final class CommonMp4AtomParsers {
|
|||||||
int[] flags = new int[sampleCount];
|
int[] flags = new int[sampleCount];
|
||||||
|
|
||||||
// Prepare to read chunk offsets.
|
// Prepare to read chunk offsets.
|
||||||
chunkOffsets.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
|
chunkOffsets.setPosition(Atom.FULL_ATOM_HEADER_SIZE);
|
||||||
int chunkCount = chunkOffsets.readUnsignedIntToInt();
|
int chunkCount = chunkOffsets.readUnsignedIntToInt();
|
||||||
|
|
||||||
stsc.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
|
stsc.setPosition(Atom.FULL_ATOM_HEADER_SIZE);
|
||||||
int remainingSamplesPerChunkChanges = stsc.readUnsignedIntToInt() - 1;
|
int remainingSamplesPerChunkChanges = stsc.readUnsignedIntToInt() - 1;
|
||||||
Assertions.checkState(stsc.readInt() == 1, "stsc first chunk must be 1");
|
Assertions.checkState(stsc.readInt() == 1, "stsc first chunk must be 1");
|
||||||
int samplesPerChunk = stsc.readUnsignedIntToInt();
|
int samplesPerChunk = stsc.readUnsignedIntToInt();
|
||||||
@ -130,7 +131,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
int remainingSamplesInChunk = samplesPerChunk;
|
int remainingSamplesInChunk = samplesPerChunk;
|
||||||
|
|
||||||
// Prepare to read sample timestamps.
|
// Prepare to read sample timestamps.
|
||||||
stts.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
|
stts.setPosition(Atom.FULL_ATOM_HEADER_SIZE);
|
||||||
int remainingTimestampDeltaChanges = stts.readUnsignedIntToInt() - 1;
|
int remainingTimestampDeltaChanges = stts.readUnsignedIntToInt() - 1;
|
||||||
int remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt();
|
int remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt();
|
||||||
int timestampDeltaInTimeUnits = stts.readUnsignedIntToInt();
|
int timestampDeltaInTimeUnits = stts.readUnsignedIntToInt();
|
||||||
@ -141,8 +142,8 @@ public final class CommonMp4AtomParsers {
|
|||||||
int remainingTimestampOffsetChanges = 0;
|
int remainingTimestampOffsetChanges = 0;
|
||||||
int timestampOffset = 0;
|
int timestampOffset = 0;
|
||||||
if (ctts != null) {
|
if (ctts != null) {
|
||||||
ctts.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
ctts.setPosition(Atom.ATOM_HEADER_SIZE);
|
||||||
cttsHasSignedOffsets = Mp4Util.parseFullAtomVersion(ctts.readInt()) == 1;
|
cttsHasSignedOffsets = Atom.parseFullAtomVersion(ctts.readInt()) == 1;
|
||||||
remainingTimestampOffsetChanges = ctts.readUnsignedIntToInt() - 1;
|
remainingTimestampOffsetChanges = ctts.readUnsignedIntToInt() - 1;
|
||||||
remainingSamplesAtTimestampOffset = ctts.readUnsignedIntToInt();
|
remainingSamplesAtTimestampOffset = ctts.readUnsignedIntToInt();
|
||||||
timestampOffset = cttsHasSignedOffsets ? ctts.readInt() : ctts.readUnsignedIntToInt();
|
timestampOffset = cttsHasSignedOffsets ? ctts.readInt() : ctts.readUnsignedIntToInt();
|
||||||
@ -151,7 +152,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
int nextSynchronizationSampleIndex = -1;
|
int nextSynchronizationSampleIndex = -1;
|
||||||
int remainingSynchronizationSamples = 0;
|
int remainingSynchronizationSamples = 0;
|
||||||
if (stss != null) {
|
if (stss != null) {
|
||||||
stss.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
|
stss.setPosition(Atom.FULL_ATOM_HEADER_SIZE);
|
||||||
remainingSynchronizationSamples = stss.readUnsignedIntToInt();
|
remainingSynchronizationSamples = stss.readUnsignedIntToInt();
|
||||||
nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1;
|
nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1;
|
||||||
}
|
}
|
||||||
@ -249,10 +250,10 @@ public final class CommonMp4AtomParsers {
|
|||||||
* @return Timescale for the movie.
|
* @return Timescale for the movie.
|
||||||
*/
|
*/
|
||||||
private static long parseMvhd(ParsableByteArray mvhd) {
|
private static long parseMvhd(ParsableByteArray mvhd) {
|
||||||
mvhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
mvhd.setPosition(Atom.ATOM_HEADER_SIZE);
|
||||||
|
|
||||||
int fullAtom = mvhd.readInt();
|
int fullAtom = mvhd.readInt();
|
||||||
int version = Mp4Util.parseFullAtomVersion(fullAtom);
|
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||||
|
|
||||||
mvhd.skip(version == 0 ? 8 : 16);
|
mvhd.skip(version == 0 ? 8 : 16);
|
||||||
|
|
||||||
@ -266,9 +267,9 @@ public final class CommonMp4AtomParsers {
|
|||||||
* the movie header box). The duration is set to -1 if the duration is unspecified.
|
* the movie header box). The duration is set to -1 if the duration is unspecified.
|
||||||
*/
|
*/
|
||||||
private static Pair<Integer, Long> parseTkhd(ParsableByteArray tkhd) {
|
private static Pair<Integer, Long> parseTkhd(ParsableByteArray tkhd) {
|
||||||
tkhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
tkhd.setPosition(Atom.ATOM_HEADER_SIZE);
|
||||||
int fullAtom = tkhd.readInt();
|
int fullAtom = tkhd.readInt();
|
||||||
int version = Mp4Util.parseFullAtomVersion(fullAtom);
|
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||||
|
|
||||||
tkhd.skip(version == 0 ? 8 : 16);
|
tkhd.skip(version == 0 ? 8 : 16);
|
||||||
|
|
||||||
@ -302,7 +303,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
* @return The track type.
|
* @return The track type.
|
||||||
*/
|
*/
|
||||||
private static int parseHdlr(ParsableByteArray hdlr) {
|
private static int parseHdlr(ParsableByteArray hdlr) {
|
||||||
hdlr.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE + 4);
|
hdlr.setPosition(Atom.FULL_ATOM_HEADER_SIZE + 4);
|
||||||
return hdlr.readInt();
|
return hdlr.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,9 +314,9 @@ public final class CommonMp4AtomParsers {
|
|||||||
* @return The media timescale, defined as the number of time units that pass in one second.
|
* @return The media timescale, defined as the number of time units that pass in one second.
|
||||||
*/
|
*/
|
||||||
private static long parseMdhd(ParsableByteArray mdhd) {
|
private static long parseMdhd(ParsableByteArray mdhd) {
|
||||||
mdhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
mdhd.setPosition(Atom.ATOM_HEADER_SIZE);
|
||||||
int fullAtom = mdhd.readInt();
|
int fullAtom = mdhd.readInt();
|
||||||
int version = Mp4Util.parseFullAtomVersion(fullAtom);
|
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||||
|
|
||||||
mdhd.skip(version == 0 ? 8 : 16);
|
mdhd.skip(version == 0 ? 8 : 16);
|
||||||
return mdhd.readUnsignedInt();
|
return mdhd.readUnsignedInt();
|
||||||
@ -323,7 +324,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
|
|
||||||
private static Pair<MediaFormat, TrackEncryptionBox[]> parseStsd(
|
private static Pair<MediaFormat, TrackEncryptionBox[]> parseStsd(
|
||||||
ParsableByteArray stsd, long durationUs) {
|
ParsableByteArray stsd, long durationUs) {
|
||||||
stsd.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
|
stsd.setPosition(Atom.FULL_ATOM_HEADER_SIZE);
|
||||||
int numberOfEntries = stsd.readInt();
|
int numberOfEntries = stsd.readInt();
|
||||||
MediaFormat mediaFormat = null;
|
MediaFormat mediaFormat = null;
|
||||||
TrackEncryptionBox[] trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
|
TrackEncryptionBox[] trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
|
||||||
@ -357,7 +358,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
/** Returns the media format for an avc1 box. */
|
/** Returns the media format for an avc1 box. */
|
||||||
private static Pair<MediaFormat, TrackEncryptionBox> parseAvcFromParent(ParsableByteArray parent,
|
private static Pair<MediaFormat, TrackEncryptionBox> parseAvcFromParent(ParsableByteArray parent,
|
||||||
int position, int size, long durationUs) {
|
int position, int size, long durationUs) {
|
||||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
parent.setPosition(position + Atom.ATOM_HEADER_SIZE);
|
||||||
|
|
||||||
parent.skip(24);
|
parent.skip(24);
|
||||||
int width = parent.readUnsignedShort();
|
int width = parent.readUnsignedShort();
|
||||||
@ -394,7 +395,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static List<byte[]> parseAvcCFromParent(ParsableByteArray parent, int position) {
|
private static List<byte[]> parseAvcCFromParent(ParsableByteArray parent, int position) {
|
||||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE + 4);
|
parent.setPosition(position + Atom.ATOM_HEADER_SIZE + 4);
|
||||||
// Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
|
// Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
|
||||||
int nalUnitLength = (parent.readUnsignedByte() & 0x3) + 1;
|
int nalUnitLength = (parent.readUnsignedByte() & 0x3) + 1;
|
||||||
if (nalUnitLength != 4) {
|
if (nalUnitLength != 4) {
|
||||||
@ -407,18 +408,18 @@ public final class CommonMp4AtomParsers {
|
|||||||
// expose the AVC profile and level somewhere useful; Most likely in MediaFormat.
|
// expose the AVC profile and level somewhere useful; Most likely in MediaFormat.
|
||||||
int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F;
|
int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F;
|
||||||
for (int j = 0; j < numSequenceParameterSets; j++) {
|
for (int j = 0; j < numSequenceParameterSets; j++) {
|
||||||
initializationData.add(Mp4Util.parseChildNalUnit(parent));
|
initializationData.add(H264Util.parseChildNalUnit(parent));
|
||||||
}
|
}
|
||||||
int numPictureParameterSets = parent.readUnsignedByte();
|
int numPictureParameterSets = parent.readUnsignedByte();
|
||||||
for (int j = 0; j < numPictureParameterSets; j++) {
|
for (int j = 0; j < numPictureParameterSets; j++) {
|
||||||
initializationData.add(Mp4Util.parseChildNalUnit(parent));
|
initializationData.add(H264Util.parseChildNalUnit(parent));
|
||||||
}
|
}
|
||||||
return initializationData;
|
return initializationData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position,
|
private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position,
|
||||||
int size) {
|
int size) {
|
||||||
int childPosition = position + Mp4Util.ATOM_HEADER_SIZE;
|
int childPosition = position + Atom.ATOM_HEADER_SIZE;
|
||||||
|
|
||||||
TrackEncryptionBox trackEncryptionBox = null;
|
TrackEncryptionBox trackEncryptionBox = null;
|
||||||
while (childPosition - position < size) {
|
while (childPosition - position < size) {
|
||||||
@ -441,7 +442,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static float parsePaspFromParent(ParsableByteArray parent, int position) {
|
private static float parsePaspFromParent(ParsableByteArray parent, int position) {
|
||||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
parent.setPosition(position + Atom.ATOM_HEADER_SIZE);
|
||||||
int hSpacing = parent.readUnsignedIntToInt();
|
int hSpacing = parent.readUnsignedIntToInt();
|
||||||
int vSpacing = parent.readUnsignedIntToInt();
|
int vSpacing = parent.readUnsignedIntToInt();
|
||||||
return (float) hSpacing / vSpacing;
|
return (float) hSpacing / vSpacing;
|
||||||
@ -449,7 +450,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
|
|
||||||
private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position,
|
private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position,
|
||||||
int size) {
|
int size) {
|
||||||
int childPosition = position + Mp4Util.ATOM_HEADER_SIZE;
|
int childPosition = position + Atom.ATOM_HEADER_SIZE;
|
||||||
while (childPosition - position < size) {
|
while (childPosition - position < size) {
|
||||||
parent.setPosition(childPosition);
|
parent.setPosition(childPosition);
|
||||||
int childAtomSize = parent.readInt();
|
int childAtomSize = parent.readInt();
|
||||||
@ -471,7 +472,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
/** Returns the media format for an mp4v box. */
|
/** Returns the media format for an mp4v box. */
|
||||||
private static MediaFormat parseMp4vFromParent(ParsableByteArray parent, int position, int size,
|
private static MediaFormat parseMp4vFromParent(ParsableByteArray parent, int position, int size,
|
||||||
long durationUs) {
|
long durationUs) {
|
||||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
parent.setPosition(position + Atom.ATOM_HEADER_SIZE);
|
||||||
|
|
||||||
parent.skip(24);
|
parent.skip(24);
|
||||||
int width = parent.readUnsignedShort();
|
int width = parent.readUnsignedShort();
|
||||||
@ -498,7 +499,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
|
|
||||||
private static Pair<MediaFormat, TrackEncryptionBox> parseAudioSampleEntry(
|
private static Pair<MediaFormat, TrackEncryptionBox> parseAudioSampleEntry(
|
||||||
ParsableByteArray parent, int atomType, int position, int size, long durationUs) {
|
ParsableByteArray parent, int atomType, int position, int size, long durationUs) {
|
||||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
parent.setPosition(position + Atom.ATOM_HEADER_SIZE);
|
||||||
parent.skip(16);
|
parent.skip(16);
|
||||||
int channelCount = parent.readUnsignedShort();
|
int channelCount = parent.readUnsignedShort();
|
||||||
int sampleSize = parent.readUnsignedShort();
|
int sampleSize = parent.readUnsignedShort();
|
||||||
@ -563,7 +564,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
|
|
||||||
/** Returns codec-specific initialization data contained in an esds box. */
|
/** Returns codec-specific initialization data contained in an esds box. */
|
||||||
private static byte[] parseEsdsFromParent(ParsableByteArray parent, int position) {
|
private static byte[] parseEsdsFromParent(ParsableByteArray parent, int position) {
|
||||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE + 4);
|
parent.setPosition(position + Atom.ATOM_HEADER_SIZE + 4);
|
||||||
// Start of the ES_Descriptor (defined in 14496-1)
|
// Start of the ES_Descriptor (defined in 14496-1)
|
||||||
parent.skip(1); // ES_Descriptor tag
|
parent.skip(1); // ES_Descriptor tag
|
||||||
int varIntByte = parent.readUnsignedByte();
|
int varIntByte = parent.readUnsignedByte();
|
||||||
@ -607,7 +608,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
|
|
||||||
private static Ac3Format parseAc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
|
private static Ac3Format parseAc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
|
||||||
// Start of the dac3 atom (defined in ETSI TS 102 366)
|
// Start of the dac3 atom (defined in ETSI TS 102 366)
|
||||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
parent.setPosition(position + Atom.ATOM_HEADER_SIZE);
|
||||||
|
|
||||||
// fscod (sample rate code)
|
// fscod (sample rate code)
|
||||||
int fscod = (parent.readUnsignedByte() & 0xC0) >> 6;
|
int fscod = (parent.readUnsignedByte() & 0xC0) >> 6;
|
||||||
@ -645,7 +646,7 @@ public final class CommonMp4AtomParsers {
|
|||||||
|
|
||||||
private static int parseEc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
|
private static int parseEc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
|
||||||
// Start of the dec3 atom (defined in ETSI TS 102 366)
|
// Start of the dec3 atom (defined in ETSI TS 102 366)
|
||||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
parent.setPosition(position + Atom.ATOM_HEADER_SIZE);
|
||||||
// TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
|
// TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,9 @@ import com.google.android.exoplayer.util.Util;
|
|||||||
/** Sample table for a track in an MP4 file. */
|
/** Sample table for a track in an MP4 file. */
|
||||||
public final class Mp4TrackSampleTable {
|
public final class Mp4TrackSampleTable {
|
||||||
|
|
||||||
|
/** Sample index when no sample is available. */
|
||||||
|
public static final int NO_SAMPLE = -1;
|
||||||
|
|
||||||
/** Sample offsets in bytes. */
|
/** Sample offsets in bytes. */
|
||||||
public final long[] offsets;
|
public final long[] offsets;
|
||||||
/** Sample sizes in bytes. */
|
/** Sample sizes in bytes. */
|
||||||
@ -53,7 +56,7 @@ public final class Mp4TrackSampleTable {
|
|||||||
* timestamp, if one is available.
|
* timestamp, if one is available.
|
||||||
*
|
*
|
||||||
* @param timeUs Timestamp adjacent to which to find a synchronization sample.
|
* @param timeUs Timestamp adjacent to which to find a synchronization sample.
|
||||||
* @return Index of the synchronization sample, or {@link Mp4Util#NO_SAMPLE} if none.
|
* @return Index of the synchronization sample, or {@link #NO_SAMPLE} if none.
|
||||||
*/
|
*/
|
||||||
public int getIndexOfEarlierOrEqualSynchronizationSample(long timeUs) {
|
public int getIndexOfEarlierOrEqualSynchronizationSample(long timeUs) {
|
||||||
int startIndex = Util.binarySearchFloor(timestampsUs, timeUs, true, false);
|
int startIndex = Util.binarySearchFloor(timestampsUs, timeUs, true, false);
|
||||||
@ -63,7 +66,7 @@ public final class Mp4TrackSampleTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Mp4Util.NO_SAMPLE;
|
return NO_SAMPLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,7 +74,7 @@ public final class Mp4TrackSampleTable {
|
|||||||
* if one is available.
|
* if one is available.
|
||||||
*
|
*
|
||||||
* @param timeUs Timestamp adjacent to which to find a synchronization sample.
|
* @param timeUs Timestamp adjacent to which to find a synchronization sample.
|
||||||
* @return index Index of the synchronization sample, or {@link Mp4Util#NO_SAMPLE} if none.
|
* @return index Index of the synchronization sample, or {@link #NO_SAMPLE} if none.
|
||||||
*/
|
*/
|
||||||
public int getIndexOfLaterOrEqualSynchronizationSample(long timeUs) {
|
public int getIndexOfLaterOrEqualSynchronizationSample(long timeUs) {
|
||||||
int startIndex = Util.binarySearchCeil(timestampsUs, timeUs, true, false);
|
int startIndex = Util.binarySearchCeil(timestampsUs, timeUs, true, false);
|
||||||
@ -81,7 +84,7 @@ public final class Mp4TrackSampleTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Mp4Util.NO_SAMPLE;
|
return NO_SAMPLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ import com.google.android.exoplayer.mp4.Atom;
|
|||||||
import com.google.android.exoplayer.mp4.Atom.ContainerAtom;
|
import com.google.android.exoplayer.mp4.Atom.ContainerAtom;
|
||||||
import com.google.android.exoplayer.mp4.CommonMp4AtomParsers;
|
import com.google.android.exoplayer.mp4.CommonMp4AtomParsers;
|
||||||
import com.google.android.exoplayer.mp4.Mp4TrackSampleTable;
|
import com.google.android.exoplayer.mp4.Mp4TrackSampleTable;
|
||||||
import com.google.android.exoplayer.mp4.Mp4Util;
|
|
||||||
import com.google.android.exoplayer.mp4.Track;
|
import com.google.android.exoplayer.mp4.Track;
|
||||||
import com.google.android.exoplayer.upstream.BufferPool;
|
import com.google.android.exoplayer.upstream.BufferPool;
|
||||||
import com.google.android.exoplayer.upstream.BufferedNonBlockingInputStream;
|
import com.google.android.exoplayer.upstream.BufferedNonBlockingInputStream;
|
||||||
@ -35,6 +34,7 @@ import com.google.android.exoplayer.upstream.DataSpec;
|
|||||||
import com.google.android.exoplayer.upstream.Loader;
|
import com.google.android.exoplayer.upstream.Loader;
|
||||||
import com.google.android.exoplayer.upstream.Loader.Loadable;
|
import com.google.android.exoplayer.upstream.Loader.Loadable;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
import com.google.android.exoplayer.util.H264Util;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
@ -58,6 +58,8 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
private static final String TAG = "Mp4SampleExtractor";
|
private static final String TAG = "Mp4SampleExtractor";
|
||||||
private static final String LOADER_THREAD_NAME = "Mp4SampleExtractor";
|
private static final String LOADER_THREAD_NAME = "Mp4SampleExtractor";
|
||||||
|
|
||||||
|
private static final int NO_TRACK = -1;
|
||||||
|
|
||||||
// Reading results
|
// Reading results
|
||||||
private static final int RESULT_NEED_MORE_DATA = 1;
|
private static final int RESULT_NEED_MORE_DATA = 1;
|
||||||
private static final int RESULT_END_OF_STREAM = 2;
|
private static final int RESULT_END_OF_STREAM = 2;
|
||||||
@ -167,7 +169,7 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
// TODO: Implement Allocator here so it is possible to check there is only one buffer at a time.
|
// TODO: Implement Allocator here so it is possible to check there is only one buffer at a time.
|
||||||
bufferPool = new BufferPool(readAheadAllocationSize);
|
bufferPool = new BufferPool(readAheadAllocationSize);
|
||||||
loader = new Loader(LOADER_THREAD_NAME);
|
loader = new Loader(LOADER_THREAD_NAME);
|
||||||
atomHeader = new ParsableByteArray(Mp4Util.LONG_ATOM_HEADER_SIZE);
|
atomHeader = new ParsableByteArray(Atom.LONG_ATOM_HEADER_SIZE);
|
||||||
containerAtoms = new Stack<Atom.ContainerAtom>();
|
containerAtoms = new Stack<Atom.ContainerAtom>();
|
||||||
|
|
||||||
parserState = STATE_READING_ATOM_HEADER;
|
parserState = STATE_READING_ATOM_HEADER;
|
||||||
@ -206,12 +208,12 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
|
|
||||||
// Get the timestamp of the earliest currently-selected sample.
|
// Get the timestamp of the earliest currently-selected sample.
|
||||||
int earliestSampleTrackIndex = getTrackIndexOfEarliestCurrentSample();
|
int earliestSampleTrackIndex = getTrackIndexOfEarliestCurrentSample();
|
||||||
if (earliestSampleTrackIndex == Mp4Util.NO_TRACK) {
|
if (earliestSampleTrackIndex == NO_TRACK) {
|
||||||
tracks[trackIndex].sampleIndex = 0;
|
tracks[trackIndex].sampleIndex = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (earliestSampleTrackIndex == Mp4Util.NO_SAMPLE) {
|
if (earliestSampleTrackIndex == Mp4TrackSampleTable.NO_SAMPLE) {
|
||||||
tracks[trackIndex].sampleIndex = Mp4Util.NO_SAMPLE;
|
tracks[trackIndex].sampleIndex = Mp4TrackSampleTable.NO_SAMPLE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long timestampUs =
|
long timestampUs =
|
||||||
@ -281,7 +283,7 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
|
|
||||||
Mp4TrackSampleTable sampleTable = tracks[trackIndex].sampleTable;
|
Mp4TrackSampleTable sampleTable = tracks[trackIndex].sampleTable;
|
||||||
int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(positionUs);
|
int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(positionUs);
|
||||||
if (sampleIndex == Mp4Util.NO_SAMPLE) {
|
if (sampleIndex == Mp4TrackSampleTable.NO_SAMPLE) {
|
||||||
sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(positionUs);
|
sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(positionUs);
|
||||||
}
|
}
|
||||||
tracks[trackIndex].sampleIndex = sampleIndex;
|
tracks[trackIndex].sampleIndex = sampleIndex;
|
||||||
@ -333,7 +335,7 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
int sampleIndex = track.sampleIndex;
|
int sampleIndex = track.sampleIndex;
|
||||||
|
|
||||||
// Check for the end of the stream.
|
// Check for the end of the stream.
|
||||||
if (sampleIndex == Mp4Util.NO_SAMPLE) {
|
if (sampleIndex == Mp4TrackSampleTable.NO_SAMPLE) {
|
||||||
// TODO: Should END_OF_STREAM be returned as soon as this track has no more samples, or as
|
// TODO: Should END_OF_STREAM be returned as soon as this track has no more samples, or as
|
||||||
// soon as no tracks have a sample (as implemented here)?
|
// soon as no tracks have a sample (as implemented here)?
|
||||||
return hasSampleInAnySelectedTrack() ? SampleSource.NOTHING_READ : SampleSource.END_OF_STREAM;
|
return hasSampleInAnySelectedTrack() ? SampleSource.NOTHING_READ : SampleSource.END_OF_STREAM;
|
||||||
@ -395,7 +397,7 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
if (MimeTypes.VIDEO_H264.equals(tracks[trackIndex].track.mediaFormat.mimeType)) {
|
if (MimeTypes.VIDEO_H264.equals(tracks[trackIndex].track.mediaFormat.mimeType)) {
|
||||||
// The mp4 file contains length-prefixed access units, but the decoder wants start code
|
// The mp4 file contains length-prefixed access units, but the decoder wants start code
|
||||||
// delimited content.
|
// delimited content.
|
||||||
Mp4Util.replaceLengthPrefixesWithAvcStartCodes(sampleHolder.data, sampleSize);
|
H264Util.replaceLengthPrefixesWithAvcStartCodes(sampleHolder.data, sampleSize);
|
||||||
}
|
}
|
||||||
sampleHolder.size = sampleSize;
|
sampleHolder.size = sampleSize;
|
||||||
}
|
}
|
||||||
@ -411,7 +413,7 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
|
|
||||||
// Advance to the next sample, checking if this was the last sample.
|
// Advance to the next sample, checking if this was the last sample.
|
||||||
track.sampleIndex =
|
track.sampleIndex =
|
||||||
sampleIndex + 1 == track.sampleTable.getSampleCount() ? Mp4Util.NO_SAMPLE : sampleIndex + 1;
|
sampleIndex + 1 == track.sampleTable.getSampleCount() ? Mp4TrackSampleTable.NO_SAMPLE : sampleIndex + 1;
|
||||||
|
|
||||||
// Reset the loading error counter if we read past the offset at which the error was thrown.
|
// Reset the loading error counter if we read past the offset at which the error was thrown.
|
||||||
if (dataSourceStream.getReadPosition() > loadErrorPosition) {
|
if (dataSourceStream.getReadPosition() > loadErrorPosition) {
|
||||||
@ -489,12 +491,12 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the index of the track that contains the earliest current sample, or
|
* Returns the index of the track that contains the earliest current sample, or {@link #NO_TRACK}
|
||||||
* {@link Mp4Util#NO_TRACK} if no track is selected, or {@link Mp4Util#NO_SAMPLE} if no samples
|
* if no track is selected, or {@link Mp4TrackSampleTable#NO_SAMPLE} if no samples remain in
|
||||||
* remain in selected tracks.
|
* selected tracks.
|
||||||
*/
|
*/
|
||||||
private int getTrackIndexOfEarliestCurrentSample() {
|
private int getTrackIndexOfEarliestCurrentSample() {
|
||||||
int earliestSampleTrackIndex = Mp4Util.NO_TRACK;
|
int earliestSampleTrackIndex = NO_TRACK;
|
||||||
long earliestSampleOffset = Long.MAX_VALUE;
|
long earliestSampleOffset = Long.MAX_VALUE;
|
||||||
for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
|
for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
|
||||||
Mp4Track track = tracks[trackIndex];
|
Mp4Track track = tracks[trackIndex];
|
||||||
@ -503,10 +505,10 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
}
|
}
|
||||||
|
|
||||||
int sampleIndex = track.sampleIndex;
|
int sampleIndex = track.sampleIndex;
|
||||||
if (sampleIndex == Mp4Util.NO_SAMPLE) {
|
if (sampleIndex == Mp4TrackSampleTable.NO_SAMPLE) {
|
||||||
if (earliestSampleTrackIndex == Mp4Util.NO_TRACK) {
|
if (earliestSampleTrackIndex == NO_TRACK) {
|
||||||
// A track is selected, but it has no more samples.
|
// A track is selected, but it has no more samples.
|
||||||
earliestSampleTrackIndex = Mp4Util.NO_SAMPLE;
|
earliestSampleTrackIndex = Mp4TrackSampleTable.NO_SAMPLE;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -524,7 +526,8 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
private boolean hasSampleInAnySelectedTrack() {
|
private boolean hasSampleInAnySelectedTrack() {
|
||||||
boolean hasSample = false;
|
boolean hasSample = false;
|
||||||
for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
|
for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
|
||||||
if (tracks[trackIndex].selected && tracks[trackIndex].sampleIndex != Mp4Util.NO_SAMPLE) {
|
if (tracks[trackIndex].selected && tracks[trackIndex].sampleIndex
|
||||||
|
!= Mp4TrackSampleTable.NO_SAMPLE) {
|
||||||
hasSample = true;
|
hasSample = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -556,10 +559,10 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
|
|
||||||
// The size value is either 4 or 8 bytes long (in which case atomSize = Mp4Util.LONG_ATOM_SIZE).
|
// The size value is either 4 or 8 bytes long (in which case atomSize = Mp4Util.LONG_ATOM_SIZE).
|
||||||
int remainingBytes;
|
int remainingBytes;
|
||||||
if (atomSize != Mp4Util.LONG_ATOM_SIZE) {
|
if (atomSize != Atom.LONG_SIZE_PREFIX) {
|
||||||
remainingBytes = Mp4Util.ATOM_HEADER_SIZE - atomBytesRead;
|
remainingBytes = Atom.ATOM_HEADER_SIZE - atomBytesRead;
|
||||||
} else {
|
} else {
|
||||||
remainingBytes = Mp4Util.LONG_ATOM_HEADER_SIZE - atomBytesRead;
|
remainingBytes = Atom.LONG_ATOM_HEADER_SIZE - atomBytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
int bytesRead = inputStream.read(atomHeader.data, atomBytesRead, remainingBytes);
|
int bytesRead = inputStream.read(atomHeader.data, atomBytesRead, remainingBytes);
|
||||||
@ -568,17 +571,17 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
}
|
}
|
||||||
rootAtomBytesRead += bytesRead;
|
rootAtomBytesRead += bytesRead;
|
||||||
atomBytesRead += bytesRead;
|
atomBytesRead += bytesRead;
|
||||||
if (atomBytesRead < Mp4Util.ATOM_HEADER_SIZE
|
if (atomBytesRead < Atom.ATOM_HEADER_SIZE
|
||||||
|| (atomSize == Mp4Util.LONG_ATOM_SIZE && atomBytesRead < Mp4Util.LONG_ATOM_HEADER_SIZE)) {
|
|| (atomSize == Atom.LONG_SIZE_PREFIX && atomBytesRead < Atom.LONG_ATOM_HEADER_SIZE)) {
|
||||||
return RESULT_NEED_MORE_DATA;
|
return RESULT_NEED_MORE_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
atomHeader.setPosition(0);
|
atomHeader.setPosition(0);
|
||||||
atomSize = atomHeader.readUnsignedInt();
|
atomSize = atomHeader.readUnsignedInt();
|
||||||
atomType = atomHeader.readInt();
|
atomType = atomHeader.readInt();
|
||||||
if (atomSize == Mp4Util.LONG_ATOM_SIZE) {
|
if (atomSize == Atom.LONG_SIZE_PREFIX) {
|
||||||
// The extended atom size is contained in the next 8 bytes, so try to read it now.
|
// The extended atom size is contained in the next 8 bytes, so try to read it now.
|
||||||
if (atomBytesRead < Mp4Util.LONG_ATOM_HEADER_SIZE) {
|
if (atomBytesRead < Atom.LONG_ATOM_HEADER_SIZE) {
|
||||||
return readAtomHeader();
|
return readAtomHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,18 +590,18 @@ public final class Mp4SampleExtractor implements SampleExtractor, Loader.Callbac
|
|||||||
|
|
||||||
Integer atomTypeInteger = atomType; // Avoids boxing atomType twice.
|
Integer atomTypeInteger = atomType; // Avoids boxing atomType twice.
|
||||||
if (CONTAINER_TYPES.contains(atomTypeInteger)) {
|
if (CONTAINER_TYPES.contains(atomTypeInteger)) {
|
||||||
if (atomSize == Mp4Util.LONG_ATOM_SIZE) {
|
if (atomSize == Atom.LONG_SIZE_PREFIX) {
|
||||||
containerAtoms.add(new ContainerAtom(
|
containerAtoms.add(new ContainerAtom(
|
||||||
atomType, rootAtomBytesRead + atomSize - Mp4Util.LONG_ATOM_HEADER_SIZE));
|
atomType, rootAtomBytesRead + atomSize - Atom.LONG_ATOM_HEADER_SIZE));
|
||||||
} else {
|
} else {
|
||||||
containerAtoms.add(new ContainerAtom(
|
containerAtoms.add(new ContainerAtom(
|
||||||
atomType, rootAtomBytesRead + atomSize - Mp4Util.ATOM_HEADER_SIZE));
|
atomType, rootAtomBytesRead + atomSize - Atom.ATOM_HEADER_SIZE));
|
||||||
}
|
}
|
||||||
enterState(STATE_READING_ATOM_HEADER);
|
enterState(STATE_READING_ATOM_HEADER);
|
||||||
} else if (LEAF_ATOM_TYPES.contains(atomTypeInteger)) {
|
} else if (LEAF_ATOM_TYPES.contains(atomTypeInteger)) {
|
||||||
Assertions.checkState(atomSize <= Integer.MAX_VALUE);
|
Assertions.checkState(atomSize <= Integer.MAX_VALUE);
|
||||||
atomData = new ParsableByteArray((int) atomSize);
|
atomData = new ParsableByteArray((int) atomSize);
|
||||||
System.arraycopy(atomHeader.data, 0, atomData.data, 0, Mp4Util.ATOM_HEADER_SIZE);
|
System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.ATOM_HEADER_SIZE);
|
||||||
enterState(STATE_READING_ATOM_PAYLOAD);
|
enterState(STATE_READING_ATOM_PAYLOAD);
|
||||||
} else {
|
} else {
|
||||||
atomData = null;
|
atomData = null;
|
||||||
|
@ -13,67 +13,17 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.mp4;
|
package com.google.android.exoplayer.util;
|
||||||
|
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility methods and constants for parsing fragmented and unfragmented MP4 files.
|
* Utility methods for handling H264 data.
|
||||||
*/
|
*/
|
||||||
public final class Mp4Util {
|
public final class H264Util {
|
||||||
|
|
||||||
/** Size of an atom header, in bytes. */
|
|
||||||
public static final int ATOM_HEADER_SIZE = 8;
|
|
||||||
|
|
||||||
/** Size of a long atom header, in bytes. */
|
|
||||||
public static final int LONG_ATOM_HEADER_SIZE = 16;
|
|
||||||
|
|
||||||
/** Size of a full atom header, in bytes. */
|
|
||||||
public static final int FULL_ATOM_HEADER_SIZE = 12;
|
|
||||||
|
|
||||||
/** Value for the first 32 bits of atomSize when the atom size is actually a long value. */
|
|
||||||
public static final int LONG_ATOM_SIZE = 1;
|
|
||||||
|
|
||||||
/** Sample index when no sample is available. */
|
|
||||||
public static final int NO_SAMPLE = -1;
|
|
||||||
|
|
||||||
/** Track index when no track is selected. */
|
|
||||||
public static final int NO_TRACK = -1;
|
|
||||||
|
|
||||||
/** Four initial bytes that must prefix H.264/AVC NAL units for decoding. */
|
/** Four initial bytes that must prefix H.264/AVC NAL units for decoding. */
|
||||||
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
|
public static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
|
||||||
|
|
||||||
/** Parses the version number out of the additional integer component of a full atom. */
|
|
||||||
public static int parseFullAtomVersion(int fullAtomInt) {
|
|
||||||
return 0x000000FF & (fullAtomInt >> 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Parses the atom flags out of the additional integer component of a full atom. */
|
|
||||||
public static int parseFullAtomFlags(int fullAtomInt) {
|
|
||||||
return 0x00FFFFFF & fullAtomInt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
|
|
||||||
* assumed that the top bit will always be set to zero.
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException If the top bit of the input data is set.
|
|
||||||
*/
|
|
||||||
public static int readUnsignedIntToInt(ByteBuffer data) {
|
|
||||||
int result = 0xFF & data.get();
|
|
||||||
for (int i = 1; i < 4; i++) {
|
|
||||||
result <<= 8;
|
|
||||||
result |= 0xFF & data.get();
|
|
||||||
}
|
|
||||||
if (result < 0) {
|
|
||||||
throw new IllegalArgumentException("Top bit not zero: " + result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces length prefixes of NAL units in {@code buffer} with start code prefixes, within the
|
* Replaces length prefixes of NAL units in {@code buffer} with start code prefixes, within the
|
||||||
@ -92,7 +42,9 @@ public final class Mp4Util {
|
|||||||
buffer.position(sampleOffset + size);
|
buffer.position(sampleOffset + size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs and returns a NAL unit with a start code followed by the data in {@code atom}. */
|
/**
|
||||||
|
* Constructs and returns a NAL unit with a start code followed by the data in {@code atom}.
|
||||||
|
*/
|
||||||
public static byte[] parseChildNalUnit(ParsableByteArray atom) {
|
public static byte[] parseChildNalUnit(ParsableByteArray atom) {
|
||||||
int length = atom.readUnsignedShort();
|
int length = atom.readUnsignedShort();
|
||||||
int offset = atom.getPosition();
|
int offset = atom.getPosition();
|
||||||
@ -101,43 +53,39 @@ public final class Mp4Util {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the first NAL unit in {@code data}.
|
* Gets the type of the NAL unit in {@code data} that starts at {@code offset}.
|
||||||
* <p>
|
|
||||||
* For a NAL unit to be found, its first four bytes must be contained within the part of the
|
|
||||||
* array being searched.
|
|
||||||
*
|
*
|
||||||
* @param data The data to search.
|
* @param data The data to search.
|
||||||
* @param startOffset The offset (inclusive) in the data to start the search.
|
* @param offset The start offset of a NAL unit. Must lie between {@code -3} (inclusive) and
|
||||||
* @param endOffset The offset (exclusive) in the data to end the search.
|
* {@code data.length - 3} (exclusive).
|
||||||
* @param type The type of the NAL unit to search for, or -1 for any NAL unit.
|
* @return The type of the unit.
|
||||||
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
|
|
||||||
*/
|
*/
|
||||||
public static int findNalUnit(byte[] data, int startOffset, int endOffset, int type) {
|
public static int getNalUnitType(byte[] data, int offset) {
|
||||||
return findNalUnit(data, startOffset, endOffset, type, null);
|
return data[offset + 3] & 0x1F;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like {@link #findNalUnit(byte[], int, int, int)}, but supports finding of NAL units across
|
* Finds the first NAL unit in {@code data}.
|
||||||
* array boundaries.
|
|
||||||
* <p>
|
* <p>
|
||||||
* To use this method, pass the same {@code prefixFlags} parameter to successive calls where the
|
* If {@code prefixFlags} is null then the first four bytes of a NAL unit must be entirely
|
||||||
* data passed represents a contiguous stream. The state maintained in this parameter allows the
|
* contained within the part of the array being searched in order for it to be found.
|
||||||
* detection of NAL units where the NAL unit prefix spans array boundaries.
|
|
||||||
* <p>
|
* <p>
|
||||||
* Note that when using {@code prefixFlags} the return value may be 3, 2 or 1 less than
|
* When {@code prefixFlags} is non-null, this method supports finding NAL units whose first four
|
||||||
* {@code startOffset}, to indicate a NAL unit starting 3, 2 or 1 bytes before the first byte in
|
* bytes span {@code data} arrays passed to successive calls. To use this feature, pass the same
|
||||||
* the current array.
|
* {@code prefixFlags} parameter to successive calls. State maintained in this parameter enables
|
||||||
|
* the detection of such NAL units. Note that when using this feature, the return value may be 3,
|
||||||
|
* 2 or 1 less than {@code startOffset}, to indicate a NAL unit starting 3, 2 or 1 bytes before
|
||||||
|
* the first byte in the current array.
|
||||||
*
|
*
|
||||||
* @param data The data to search.
|
* @param data The data to search.
|
||||||
* @param startOffset The offset (inclusive) in the data to start the search.
|
* @param startOffset The offset (inclusive) in the data to start the search.
|
||||||
* @param endOffset The offset (exclusive) in the data to end the search.
|
* @param endOffset The offset (exclusive) in the data to end the search.
|
||||||
* @param type The type of the NAL unit to search for, or -1 for any NAL unit.
|
|
||||||
* @param prefixFlags A boolean array whose first three elements are used to store the state
|
* @param prefixFlags A boolean array whose first three elements are used to store the state
|
||||||
* required to detect NAL units where the NAL unit prefix spans array boundaries. The array
|
* required to detect NAL units where the NAL unit prefix spans array boundaries. The array
|
||||||
* must be at least 3 elements long.
|
* must be at least 3 elements long.
|
||||||
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
|
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
|
||||||
*/
|
*/
|
||||||
public static int findNalUnit(byte[] data, int startOffset, int endOffset, int type,
|
public static int findNalUnit(byte[] data, int startOffset, int endOffset,
|
||||||
boolean[] prefixFlags) {
|
boolean[] prefixFlags) {
|
||||||
int length = endOffset - startOffset;
|
int length = endOffset - startOffset;
|
||||||
|
|
||||||
@ -147,15 +95,14 @@ public final class Mp4Util {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (prefixFlags != null) {
|
if (prefixFlags != null) {
|
||||||
if (prefixFlags[0] && matchesType(data, startOffset, type)) {
|
if (prefixFlags[0]) {
|
||||||
clearPrefixFlags(prefixFlags);
|
clearPrefixFlags(prefixFlags);
|
||||||
return startOffset - 3;
|
return startOffset - 3;
|
||||||
} else if (length > 1 && prefixFlags[1] && data[startOffset] == 1
|
} else if (length > 1 && prefixFlags[1] && data[startOffset] == 1) {
|
||||||
&& matchesType(data, startOffset + 1, type)) {
|
|
||||||
clearPrefixFlags(prefixFlags);
|
clearPrefixFlags(prefixFlags);
|
||||||
return startOffset - 2;
|
return startOffset - 2;
|
||||||
} else if (length > 2 && prefixFlags[2] && data[startOffset] == 0
|
} else if (length > 2 && prefixFlags[2] && data[startOffset] == 0
|
||||||
&& data[startOffset + 1] == 1 && matchesType(data, startOffset + 2, type)) {
|
&& data[startOffset + 1] == 1) {
|
||||||
clearPrefixFlags(prefixFlags);
|
clearPrefixFlags(prefixFlags);
|
||||||
return startOffset - 1;
|
return startOffset - 1;
|
||||||
}
|
}
|
||||||
@ -169,8 +116,7 @@ public final class Mp4Util {
|
|||||||
if ((data[i] & 0xFE) != 0) {
|
if ((data[i] & 0xFE) != 0) {
|
||||||
// There isn't a NAL prefix here, or at the next two positions. Do nothing and let the
|
// There isn't a NAL prefix here, or at the next two positions. Do nothing and let the
|
||||||
// loop advance the index by three.
|
// loop advance the index by three.
|
||||||
} else if (data[i - 2] == 0 && data[i - 1] == 0 && data[i] == 1
|
} else if (data[i - 2] == 0 && data[i - 1] == 0 && data[i] == 1) {
|
||||||
&& matchesType(data, i + 1, type)) {
|
|
||||||
if (prefixFlags != null) {
|
if (prefixFlags != null) {
|
||||||
clearPrefixFlags(prefixFlags);
|
clearPrefixFlags(prefixFlags);
|
||||||
}
|
}
|
||||||
@ -199,45 +145,25 @@ public final class Mp4Util {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like {@link #findNalUnit(byte[], int, int, int)} with {@code type == -1}.
|
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
|
||||||
|
* assumed that the top bit will always be set to zero.
|
||||||
*
|
*
|
||||||
* @param data The data to search.
|
* @throws IllegalArgumentException If the top bit of the input data is set.
|
||||||
* @param startOffset The offset (inclusive) in the data to start the search.
|
|
||||||
* @param endOffset The offset (exclusive) in the data to end the search.
|
|
||||||
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
|
|
||||||
*/
|
*/
|
||||||
public static int findNalUnit(byte[] data, int startOffset, int endOffset) {
|
private static int readUnsignedIntToInt(ByteBuffer data) {
|
||||||
return findNalUnit(data, startOffset, endOffset, null);
|
int result = 0xFF & data.get();
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
result <<= 8;
|
||||||
|
result |= 0xFF & data.get();
|
||||||
|
}
|
||||||
|
if (result < 0) {
|
||||||
|
throw new IllegalArgumentException("Top bit not zero: " + result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like {@link #findNalUnit(byte[], int, int, int, boolean[])} with {@code type == -1}.
|
* Clears prefix flags, as used by {@link #findNalUnit(byte[], int, int, boolean[])}.
|
||||||
*
|
|
||||||
* @param data The data to search.
|
|
||||||
* @param startOffset The offset (inclusive) in the data to start the search.
|
|
||||||
* @param endOffset The offset (exclusive) in the data to end the search.
|
|
||||||
* @param prefixFlags A boolean array of length at least 3.
|
|
||||||
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
|
|
||||||
*/
|
|
||||||
public static int findNalUnit(byte[] data, int startOffset, int endOffset,
|
|
||||||
boolean[] prefixFlags) {
|
|
||||||
return findNalUnit(data, startOffset, endOffset, -1, prefixFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the type of the NAL unit in {@code data} that starts at {@code offset}.
|
|
||||||
*
|
|
||||||
* @param data The data to search.
|
|
||||||
* @param offset The start offset of a NAL unit. Must lie between {@code -3} (inclusive) and
|
|
||||||
* {@code data.length - 3} (exclusive).
|
|
||||||
* @return The type of the unit.
|
|
||||||
*/
|
|
||||||
public static int getNalUnitType(byte[] data, int offset) {
|
|
||||||
return data[offset + 3] & 0x1F;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears prefix flags, as used by {@link #findNalUnit(byte[], int, int, int, boolean[])}.
|
|
||||||
*
|
*
|
||||||
* @param prefixFlags The flags to clear.
|
* @param prefixFlags The flags to clear.
|
||||||
*/
|
*/
|
||||||
@ -247,11 +173,4 @@ public final class Mp4Util {
|
|||||||
prefixFlags[2] = false;
|
prefixFlags[2] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the type at {@code offset} is equal to {@code type}, or if {@code type == -1}.
|
|
||||||
*/
|
|
||||||
private static boolean matchesType(byte[] data, int offset, int type) {
|
|
||||||
return type == -1 || (data[offset] & 0x1F) == type;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -13,16 +13,16 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.mp4;
|
package com.google.android.exoplayer.util;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link Mp4Util}.
|
* Tests for {@link H264Util}.
|
||||||
*/
|
*/
|
||||||
public class Mp4UtilTest extends TestCase {
|
public class H264UtilTest extends TestCase {
|
||||||
|
|
||||||
private static final int TEST_PARTIAL_NAL_POSITION = 4;
|
private static final int TEST_PARTIAL_NAL_POSITION = 4;
|
||||||
private static final int TEST_NAL_POSITION = 10;
|
private static final int TEST_NAL_POSITION = 10;
|
||||||
@ -31,19 +31,19 @@ public class Mp4UtilTest extends TestCase {
|
|||||||
byte[] data = buildTestData();
|
byte[] data = buildTestData();
|
||||||
|
|
||||||
// Should find NAL unit.
|
// Should find NAL unit.
|
||||||
int result = Mp4Util.findNalUnit(data, 0, data.length);
|
int result = H264Util.findNalUnit(data, 0, data.length, null);
|
||||||
assertEquals(TEST_NAL_POSITION, result);
|
assertEquals(TEST_NAL_POSITION, result);
|
||||||
// Should find NAL unit whose prefix ends one byte before the limit.
|
// Should find NAL unit whose prefix ends one byte before the limit.
|
||||||
result = Mp4Util.findNalUnit(data, 0, TEST_NAL_POSITION + 4);
|
result = H264Util.findNalUnit(data, 0, TEST_NAL_POSITION + 4, null);
|
||||||
assertEquals(TEST_NAL_POSITION, result);
|
assertEquals(TEST_NAL_POSITION, result);
|
||||||
// Shouldn't find NAL unit whose prefix ends at the limit (since the limit is exclusive).
|
// Shouldn't find NAL unit whose prefix ends at the limit (since the limit is exclusive).
|
||||||
result = Mp4Util.findNalUnit(data, 0, TEST_NAL_POSITION + 3);
|
result = H264Util.findNalUnit(data, 0, TEST_NAL_POSITION + 3, null);
|
||||||
assertEquals(TEST_NAL_POSITION + 3, result);
|
assertEquals(TEST_NAL_POSITION + 3, result);
|
||||||
// Should find NAL unit whose prefix starts at the offset.
|
// Should find NAL unit whose prefix starts at the offset.
|
||||||
result = Mp4Util.findNalUnit(data, TEST_NAL_POSITION, data.length);
|
result = H264Util.findNalUnit(data, TEST_NAL_POSITION, data.length, null);
|
||||||
assertEquals(TEST_NAL_POSITION, result);
|
assertEquals(TEST_NAL_POSITION, result);
|
||||||
// Shouldn't find NAL unit whose prefix starts one byte past the offset.
|
// Shouldn't find NAL unit whose prefix starts one byte past the offset.
|
||||||
result = Mp4Util.findNalUnit(data, TEST_NAL_POSITION + 1, data.length);
|
result = H264Util.findNalUnit(data, TEST_NAL_POSITION + 1, data.length, null);
|
||||||
assertEquals(data.length, result);
|
assertEquals(data.length, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,9 +54,9 @@ public class Mp4UtilTest extends TestCase {
|
|||||||
boolean[] prefixFlags = new boolean[3];
|
boolean[] prefixFlags = new boolean[3];
|
||||||
byte[] data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
|
byte[] data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
|
||||||
byte[] data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, data.length);
|
byte[] data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, data.length);
|
||||||
int result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
int result = H264Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
||||||
assertEquals(data1.length, result);
|
assertEquals(data1.length, result);
|
||||||
result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
result = H264Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
||||||
assertEquals(-1, result);
|
assertEquals(-1, result);
|
||||||
assertPrefixFlagsCleared(prefixFlags);
|
assertPrefixFlagsCleared(prefixFlags);
|
||||||
|
|
||||||
@ -64,9 +64,9 @@ public class Mp4UtilTest extends TestCase {
|
|||||||
prefixFlags = new boolean[3];
|
prefixFlags = new boolean[3];
|
||||||
data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 3);
|
data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 3);
|
||||||
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 3, data.length);
|
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 3, data.length);
|
||||||
result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
result = H264Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
||||||
assertEquals(data1.length, result);
|
assertEquals(data1.length, result);
|
||||||
result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
result = H264Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
||||||
assertEquals(-3, result);
|
assertEquals(-3, result);
|
||||||
assertPrefixFlagsCleared(prefixFlags);
|
assertPrefixFlagsCleared(prefixFlags);
|
||||||
|
|
||||||
@ -75,11 +75,11 @@ public class Mp4UtilTest extends TestCase {
|
|||||||
data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
|
data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
|
||||||
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2);
|
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2);
|
||||||
byte[] data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length);
|
byte[] data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length);
|
||||||
result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
result = H264Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
||||||
assertEquals(data1.length, result);
|
assertEquals(data1.length, result);
|
||||||
result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
result = H264Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
||||||
assertEquals(data2.length, result);
|
assertEquals(data2.length, result);
|
||||||
result = Mp4Util.findNalUnit(data3, 0, data3.length, prefixFlags);
|
result = H264Util.findNalUnit(data3, 0, data3.length, prefixFlags);
|
||||||
assertEquals(-2, result);
|
assertEquals(-2, result);
|
||||||
assertPrefixFlagsCleared(prefixFlags);
|
assertPrefixFlagsCleared(prefixFlags);
|
||||||
|
|
||||||
@ -89,13 +89,13 @@ public class Mp4UtilTest extends TestCase {
|
|||||||
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2);
|
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2);
|
||||||
data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, TEST_NAL_POSITION + 3);
|
data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, TEST_NAL_POSITION + 3);
|
||||||
byte[] data4 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length);
|
byte[] data4 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length);
|
||||||
result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
result = H264Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
||||||
assertEquals(data1.length, result);
|
assertEquals(data1.length, result);
|
||||||
result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
result = H264Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
||||||
assertEquals(data2.length, result);
|
assertEquals(data2.length, result);
|
||||||
result = Mp4Util.findNalUnit(data3, 0, data3.length, prefixFlags);
|
result = H264Util.findNalUnit(data3, 0, data3.length, prefixFlags);
|
||||||
assertEquals(data3.length, result);
|
assertEquals(data3.length, result);
|
||||||
result = Mp4Util.findNalUnit(data4, 0, data4.length, prefixFlags);
|
result = H264Util.findNalUnit(data4, 0, data4.length, prefixFlags);
|
||||||
assertEquals(-3, result);
|
assertEquals(-3, result);
|
||||||
assertPrefixFlagsCleared(prefixFlags);
|
assertPrefixFlagsCleared(prefixFlags);
|
||||||
|
|
||||||
@ -103,9 +103,9 @@ public class Mp4UtilTest extends TestCase {
|
|||||||
prefixFlags = new boolean[3];
|
prefixFlags = new boolean[3];
|
||||||
data1 = Arrays.copyOfRange(data, 0, TEST_PARTIAL_NAL_POSITION + 2);
|
data1 = Arrays.copyOfRange(data, 0, TEST_PARTIAL_NAL_POSITION + 2);
|
||||||
data2 = Arrays.copyOfRange(data, TEST_PARTIAL_NAL_POSITION + 2, data.length);
|
data2 = Arrays.copyOfRange(data, TEST_PARTIAL_NAL_POSITION + 2, data.length);
|
||||||
result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
result = H264Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
||||||
assertEquals(data1.length, result);
|
assertEquals(data1.length, result);
|
||||||
result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
result = H264Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
||||||
assertEquals(4, result);
|
assertEquals(4, result);
|
||||||
assertPrefixFlagsCleared(prefixFlags);
|
assertPrefixFlagsCleared(prefixFlags);
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user