Move common MP4 parsing code to CommonMp4AtomParsers and Mp4Util.

Also add parseMp4vFromParent and return the track's duration in parseTrak.

This is in preparation for adding a non-fragmented MP4 extractor.
This commit is contained in:
Oliver Woodman 2015-02-06 11:43:37 +00:00
parent a968e5535b
commit bfa1de68d8
7 changed files with 632 additions and 480 deletions

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.chunk.parser;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder;
@ -78,6 +79,11 @@ public interface Extractor {
*/
public MediaFormat getFormat();
/**
* Returns the duration of the stream in microseconds, or {@link C#UNKNOWN_TIME_US} if unknown.
*/
public long getDurationUs();
/**
* Returns the pssh information parsed from the stream.
*

View File

@ -24,21 +24,18 @@ import com.google.android.exoplayer.chunk.parser.SegmentIndex;
import com.google.android.exoplayer.mp4.Atom;
import com.google.android.exoplayer.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer.mp4.Atom.LeafAtom;
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.upstream.NonBlockingInputStream;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.util.Pair;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@ -67,14 +64,8 @@ public final class FragmentedMp4Extractor implements Extractor {
private static final int READ_TERMINATING_RESULTS = RESULT_NEED_MORE_DATA | RESULT_END_OF_STREAM
| RESULT_READ_SAMPLE | RESULT_NEED_SAMPLE_HOLDER;
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE =
new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12};
/** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
private static final int[] AC3_CHANNEL_COUNTS = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
/** Nominal bit-rates for AC-3 audio in kbps, indexed by bit_rate_code. (See ETSI TS 102 366.) */
private static final int[] AC3_BIT_RATES = new int[] {32, 40, 48, 56, 64, 80, 96, 112, 128, 160,
192, 224, 256, 320, 384, 448, 512, 576, 640};
// Parser states
private static final int STATE_READING_ATOM_HEADER = 0;
@ -82,10 +73,6 @@ public final class FragmentedMp4Extractor implements Extractor {
private static final int STATE_READING_ENCRYPTION_DATA = 2;
private static final int STATE_READING_SAMPLE = 3;
// Atom data offsets
private static final int ATOM_HEADER_SIZE = 8;
private static final int FULL_ATOM_HEADER_SIZE = 12;
// Atoms that the parser cares about
private static final Set<Integer> PARSED_ATOMS;
static {
@ -173,7 +160,7 @@ public final class FragmentedMp4Extractor implements Extractor {
public FragmentedMp4Extractor(int workaroundFlags) {
this.workaroundFlags = workaroundFlags;
parserState = STATE_READING_ATOM_HEADER;
atomHeader = new ParsableByteArray(ATOM_HEADER_SIZE);
atomHeader = new ParsableByteArray(Mp4Util.ATOM_HEADER_SIZE);
extendedTypeScratch = new byte[16];
containerAtoms = new Stack<ContainerAtom>();
fragmentRun = new TrackFragment();
@ -210,6 +197,11 @@ public final class FragmentedMp4Extractor implements Extractor {
return track == null ? null : track.mediaFormat;
}
@Override
public long getDurationUs() {
return track == null ? C.UNKNOWN_TIME_US : track.durationUs;
}
@Override
public int read(NonBlockingInputStream inputStream, SampleHolder out)
throws ParserException {
@ -276,14 +268,14 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private int readAtomHeader(NonBlockingInputStream inputStream) {
int remainingBytes = ATOM_HEADER_SIZE - atomBytesRead;
int remainingBytes = Mp4Util.ATOM_HEADER_SIZE - atomBytesRead;
int bytesRead = inputStream.read(atomHeader.data, atomBytesRead, remainingBytes);
if (bytesRead == -1) {
return RESULT_END_OF_STREAM;
}
rootAtomBytesRead += bytesRead;
atomBytesRead += bytesRead;
if (atomBytesRead != ATOM_HEADER_SIZE) {
if (atomBytesRead != Mp4Util.ATOM_HEADER_SIZE) {
return RESULT_NEED_MORE_DATA;
}
@ -305,10 +297,10 @@ public final class FragmentedMp4Extractor implements Extractor {
if (CONTAINER_TYPES.contains(atomTypeInteger)) {
enterState(STATE_READING_ATOM_HEADER);
containerAtoms.add(new ContainerAtom(atomType,
rootAtomBytesRead + atomSize - ATOM_HEADER_SIZE));
rootAtomBytesRead + atomSize - Mp4Util.ATOM_HEADER_SIZE));
} else {
atomData = new ParsableByteArray(atomSize);
System.arraycopy(atomHeader.data, 0, atomData.data, 0, ATOM_HEADER_SIZE);
System.arraycopy(atomHeader.data, 0, atomData.data, 0, Mp4Util.ATOM_HEADER_SIZE);
enterState(STATE_READING_ATOM_PAYLOAD);
}
} else {
@ -377,7 +369,7 @@ public final class FragmentedMp4Extractor implements Extractor {
LeafAtom child = moovChildren.get(i);
if (child.type == Atom.TYPE_pssh) {
ParsableByteArray psshAtom = child.data;
psshAtom.setPosition(FULL_ATOM_HEADER_SIZE);
psshAtom.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
UUID uuid = new UUID(psshAtom.readLong(), psshAtom.readLong());
int dataSize = psshAtom.readInt();
byte[] data = new byte[dataSize];
@ -387,7 +379,7 @@ public final class FragmentedMp4Extractor implements Extractor {
}
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
extendsDefaults = parseTrex(mvex.getLeafAtomOfType(Atom.TYPE_trex).data);
track = parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak));
track = CommonMp4AtomParsers.parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak));
}
private void onMoofContainerAtomRead(ContainerAtom moof) {
@ -412,7 +404,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a trex atom (defined in 14496-12).
*/
private static DefaultSampleValues parseTrex(ParsableByteArray trex) {
trex.setPosition(FULL_ATOM_HEADER_SIZE + 4);
trex.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE + 4);
int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1;
int defaultSampleDuration = trex.readUnsignedIntToInt();
int defaultSampleSize = trex.readUnsignedIntToInt();
@ -421,388 +413,6 @@ public final class FragmentedMp4Extractor implements Extractor {
defaultSampleSize, defaultSampleFlags);
}
/**
* Parses a trak atom (defined in 14496-12).
*/
private static Track parseTrak(ContainerAtom trak) {
ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
Assertions.checkState(trackType == Track.TYPE_AUDIO || trackType == Track.TYPE_VIDEO
|| trackType == Track.TYPE_TEXT);
Pair<Integer, Long> header = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data);
int id = header.first;
// TODO: This value should be used to set a duration field on the Track object
// instantiated below, however we've found examples where the value is 0. Revisit whether we
// should set it anyway (and just have it be wrong for bad media streams).
// long duration = header.second;
long timescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf)
.getContainerAtomOfType(Atom.TYPE_stbl);
Pair<MediaFormat, TrackEncryptionBox[]> sampleDescriptions =
parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data);
return new Track(id, trackType, timescale, sampleDescriptions.first, sampleDescriptions.second);
}
/**
* Parses a tkhd atom (defined in 14496-12).
*
* @return A {@link Pair} consisting of the track id and duration (in the timescale indicated in
* the movie header box). The duration is set to -1 if the duration is unspecified.
*/
private static Pair<Integer, Long> parseTkhd(ParsableByteArray tkhd) {
tkhd.setPosition(ATOM_HEADER_SIZE);
int fullAtom = tkhd.readInt();
int version = parseFullAtomVersion(fullAtom);
tkhd.skip(version == 0 ? 8 : 16);
int trackId = tkhd.readInt();
tkhd.skip(4);
boolean durationUnknown = true;
int durationPosition = tkhd.getPosition();
int durationByteCount = version == 0 ? 4 : 8;
for (int i = 0; i < durationByteCount; i++) {
if (tkhd.data[durationPosition + i] != -1) {
durationUnknown = false;
break;
}
}
long duration;
if (durationUnknown) {
tkhd.skip(durationByteCount);
duration = -1;
} else {
duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong();
}
return Pair.create(trackId, duration);
}
/**
* Parses an hdlr atom (defined in 14496-12).
*
* @param hdlr The hdlr atom to parse.
* @return The track type.
*/
private static int parseHdlr(ParsableByteArray hdlr) {
hdlr.setPosition(FULL_ATOM_HEADER_SIZE + 4);
return hdlr.readInt();
}
/**
* Parses an mdhd atom (defined in 14496-12).
*
* @param mdhd The mdhd atom to parse.
* @return The media timescale, defined as the number of time units that pass in one second.
*/
private static long parseMdhd(ParsableByteArray mdhd) {
mdhd.setPosition(ATOM_HEADER_SIZE);
int fullAtom = mdhd.readInt();
int version = parseFullAtomVersion(fullAtom);
mdhd.skip(version == 0 ? 8 : 16);
return mdhd.readUnsignedInt();
}
private static Pair<MediaFormat, TrackEncryptionBox[]> parseStsd(ParsableByteArray stsd) {
stsd.setPosition(FULL_ATOM_HEADER_SIZE);
int numberOfEntries = stsd.readInt();
MediaFormat mediaFormat = null;
TrackEncryptionBox[] trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
for (int i = 0; i < numberOfEntries; i++) {
int childStartPosition = stsd.getPosition();
int childAtomSize = stsd.readInt();
int childAtomType = stsd.readInt();
if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3
|| childAtomType == Atom.TYPE_encv) {
Pair<MediaFormat, TrackEncryptionBox> avc =
parseAvcFromParent(stsd, childStartPosition, childAtomSize);
mediaFormat = avc.first;
trackEncryptionBoxes[i] = avc.second;
} else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca
|| childAtomType == Atom.TYPE_ac_3) {
Pair<MediaFormat, TrackEncryptionBox> audioSampleEntry =
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize);
mediaFormat = audioSampleEntry.first;
trackEncryptionBoxes[i] = audioSampleEntry.second;
} else if (childAtomType == Atom.TYPE_TTML) {
mediaFormat = MediaFormat.createTtmlFormat();
}
stsd.setPosition(childStartPosition + childAtomSize);
}
return Pair.create(mediaFormat, trackEncryptionBoxes);
}
private static Pair<MediaFormat, TrackEncryptionBox> parseAvcFromParent(ParsableByteArray parent,
int position, int size) {
parent.setPosition(position + ATOM_HEADER_SIZE);
parent.skip(24);
int width = parent.readUnsignedShort();
int height = parent.readUnsignedShort();
float pixelWidthHeightRatio = 1;
parent.skip(50);
List<byte[]> initializationData = null;
TrackEncryptionBox trackEncryptionBox = null;
int childPosition = parent.getPosition();
while (childPosition - position < size) {
parent.setPosition(childPosition);
int childStartPosition = parent.getPosition();
int childAtomSize = parent.readInt();
int childAtomType = parent.readInt();
if (childAtomType == Atom.TYPE_avcC) {
initializationData = parseAvcCFromParent(parent, childStartPosition);
} else if (childAtomType == Atom.TYPE_sinf) {
trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
} else if (childAtomType == Atom.TYPE_pasp) {
pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition);
}
childPosition += childAtomSize;
}
MediaFormat format = MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
width, height, pixelWidthHeightRatio, initializationData);
return Pair.create(format, trackEncryptionBox);
}
private static Pair<MediaFormat, TrackEncryptionBox> parseAudioSampleEntry(
ParsableByteArray parent, int atomType, int position, int size) {
parent.setPosition(position + ATOM_HEADER_SIZE);
parent.skip(16);
int channelCount = parent.readUnsignedShort();
int sampleSize = parent.readUnsignedShort();
parent.skip(4);
int sampleRate = parent.readUnsignedFixedPoint1616();
int bitrate = MediaFormat.NO_VALUE;
byte[] initializationData = null;
TrackEncryptionBox trackEncryptionBox = null;
int childPosition = parent.getPosition();
while (childPosition - position < size) {
parent.setPosition(childPosition);
int childStartPosition = parent.getPosition();
int childAtomSize = parent.readInt();
int childAtomType = parent.readInt();
if (atomType == Atom.TYPE_mp4a || atomType == Atom.TYPE_enca) {
if (childAtomType == Atom.TYPE_esds) {
initializationData = parseEsdsFromParent(parent, childStartPosition);
// TODO: Do we really need to do this? See [Internal: b/10903778]
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data.
Pair<Integer, Integer> audioSpecificConfig =
CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData);
sampleRate = audioSpecificConfig.first;
channelCount = audioSpecificConfig.second;
} else if (childAtomType == Atom.TYPE_sinf) {
trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
}
} else if (atomType == Atom.TYPE_ac_3 && childAtomType == Atom.TYPE_dac3) {
// TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
Ac3Format ac3Format =
parseAc3SpecificBoxFromParent(parent, childStartPosition);
if (ac3Format != null) {
sampleRate = ac3Format.sampleRate;
channelCount = ac3Format.channelCount;
bitrate = ac3Format.bitrate;
}
// TODO: Add support for encrypted AC-3.
trackEncryptionBox = null;
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
sampleRate = parseEc3SpecificBoxFromParent(parent, childStartPosition);
trackEncryptionBox = null;
}
childPosition += childAtomSize;
}
String mimeType;
if (atomType == Atom.TYPE_ac_3) {
mimeType = MimeTypes.AUDIO_AC3;
} else if (atomType == Atom.TYPE_ec_3) {
mimeType = MimeTypes.AUDIO_EC3;
} else {
mimeType = MimeTypes.AUDIO_AAC;
}
MediaFormat format = MediaFormat.createAudioFormat(
mimeType, sampleSize, channelCount, sampleRate, bitrate,
initializationData == null ? null : Collections.singletonList(initializationData));
return Pair.create(format, trackEncryptionBox);
}
private static Ac3Format parseAc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
// Start of the dac3 atom (defined in ETSI TS 102 366)
parent.setPosition(position + ATOM_HEADER_SIZE);
// fscod (sample rate code)
int fscod = (parent.readUnsignedByte() & 0xC0) >> 6;
int sampleRate;
switch (fscod) {
case 0:
sampleRate = 48000;
break;
case 1:
sampleRate = 44100;
break;
case 2:
sampleRate = 32000;
break;
default:
// TODO: The decoder should not use this stream.
return null;
}
int nextByte = parent.readUnsignedByte();
// Map acmod (audio coding mode) onto a channel count.
int channelCount = AC3_CHANNEL_COUNTS[(nextByte & 0x38) >> 3];
// lfeon (low frequency effects on)
if ((nextByte & 0x04) != 0) {
channelCount++;
}
// Map bit_rate_code onto a bit-rate in kbit/s.
int bitrate = AC3_BIT_RATES[((nextByte & 0x03) << 3) + (parent.readUnsignedByte() >> 5)];
return new Ac3Format(channelCount, sampleRate, bitrate);
}
private static int parseEc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
// Start of the dec3 atom (defined in ETSI TS 102 366)
parent.setPosition(position + ATOM_HEADER_SIZE);
// TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
return 0;
}
private static List<byte[]> parseAvcCFromParent(ParsableByteArray parent, int position) {
parent.setPosition(position + ATOM_HEADER_SIZE + 4);
// Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
int nalUnitLength = (parent.readUnsignedByte() & 0x3) + 1;
if (nalUnitLength != 4) {
// readSample currently relies on a nalUnitLength of 4.
// TODO: Consider handling the case where it isn't.
throw new IllegalStateException();
}
List<byte[]> initializationData = new ArrayList<byte[]>();
// TODO: We should try and parse these using CodecSpecificDataUtil.parseSpsNalUnit, and
// expose the AVC profile and level somewhere useful; Most likely in MediaFormat.
int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F;
for (int j = 0; j < numSequenceParameterSets; j++) {
initializationData.add(parseChildNalUnit(parent));
}
int numPictureParamterSets = parent.readUnsignedByte();
for (int j = 0; j < numPictureParamterSets; j++) {
initializationData.add(parseChildNalUnit(parent));
}
return initializationData;
}
private static byte[] parseChildNalUnit(ParsableByteArray atom) {
int length = atom.readUnsignedShort();
int offset = atom.getPosition();
atom.skip(length);
return CodecSpecificDataUtil.buildNalUnit(atom.data, offset, length);
}
private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position,
int size) {
int childPosition = position + ATOM_HEADER_SIZE;
TrackEncryptionBox trackEncryptionBox = null;
while (childPosition - position < size) {
parent.setPosition(childPosition);
int childAtomSize = parent.readInt();
int childAtomType = parent.readInt();
if (childAtomType == Atom.TYPE_frma) {
parent.readInt(); // dataFormat.
} else if (childAtomType == Atom.TYPE_schm) {
parent.skip(4);
parent.readInt(); // schemeType. Expect cenc
parent.readInt(); // schemeVersion. Expect 0x00010000
} else if (childAtomType == Atom.TYPE_schi) {
trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize);
}
childPosition += childAtomSize;
}
return trackEncryptionBox;
}
private static float parsePaspFromParent(ParsableByteArray parent, int position) {
parent.setPosition(position + ATOM_HEADER_SIZE);
int hSpacing = parent.readUnsignedIntToInt();
int vSpacing = parent.readUnsignedIntToInt();
return (float) hSpacing / vSpacing;
}
private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position,
int size) {
int childPosition = position + ATOM_HEADER_SIZE;
while (childPosition - position < size) {
parent.setPosition(childPosition);
int childAtomSize = parent.readInt();
int childAtomType = parent.readInt();
if (childAtomType == Atom.TYPE_tenc) {
parent.skip(4);
int firstInt = parent.readInt();
boolean defaultIsEncrypted = (firstInt >> 8) == 1;
int defaultInitVectorSize = firstInt & 0xFF;
byte[] defaultKeyId = new byte[16];
parent.readBytes(defaultKeyId, 0, defaultKeyId.length);
return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId);
}
childPosition += childAtomSize;
}
return null;
}
private static byte[] parseEsdsFromParent(ParsableByteArray parent, int position) {
parent.setPosition(position + ATOM_HEADER_SIZE + 4);
// Start of the ES_Descriptor (defined in 14496-1)
parent.skip(1); // ES_Descriptor tag
int varIntByte = parent.readUnsignedByte();
while (varIntByte > 127) {
varIntByte = parent.readUnsignedByte();
}
parent.skip(2); // ES_ID
int flags = parent.readUnsignedByte();
if ((flags & 0x80 /* streamDependenceFlag */) != 0) {
parent.skip(2);
}
if ((flags & 0x40 /* URL_Flag */) != 0) {
parent.skip(parent.readUnsignedShort());
}
if ((flags & 0x20 /* OCRstreamFlag */) != 0) {
parent.skip(2);
}
// Start of the DecoderConfigDescriptor (defined in 14496-1)
parent.skip(1); // DecoderConfigDescriptor tag
varIntByte = parent.readUnsignedByte();
while (varIntByte > 127) {
varIntByte = parent.readUnsignedByte();
}
parent.skip(13);
// Start of AudioSpecificConfig (defined in 14496-3)
parent.skip(1); // AudioSpecificConfig tag
varIntByte = parent.readUnsignedByte();
int varInt = varIntByte & 0x7F;
while (varIntByte > 127) {
varIntByte = parent.readUnsignedByte();
varInt = varInt << 8;
varInt |= varIntByte & 0x7F;
}
byte[] initializationData = new byte[varInt];
parent.readBytes(initializationData, 0, varInt);
return initializationData;
}
private static void parseMoof(Track track, DefaultSampleValues extendsDefaults,
ContainerAtom moof, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) {
parseTraf(track, extendsDefaults, moof.getContainerAtomOfType(Atom.TYPE_traf),
@ -848,9 +458,9 @@ public final class FragmentedMp4Extractor implements Extractor {
private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz,
TrackFragment out) {
int vectorSize = encryptionBox.initializationVectorSize;
saiz.setPosition(ATOM_HEADER_SIZE);
saiz.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = saiz.readInt();
int flags = parseFullAtomFlags(fullAtom);
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
if ((flags & 0x01) == 1) {
saiz.skip(8);
}
@ -885,9 +495,9 @@ public final class FragmentedMp4Extractor implements Extractor {
*/
private static DefaultSampleValues parseTfhd(DefaultSampleValues extendsDefaults,
ParsableByteArray tfhd) {
tfhd.setPosition(ATOM_HEADER_SIZE);
tfhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = tfhd.readInt();
int flags = parseFullAtomFlags(fullAtom);
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
tfhd.skip(4); // trackId
if ((flags & 0x01 /* base_data_offset_present */) != 0) {
@ -914,9 +524,9 @@ public final class FragmentedMp4Extractor implements Extractor {
* media, expressed in the media's timescale.
*/
private static long parseTfdt(ParsableByteArray tfdt) {
tfdt.setPosition(ATOM_HEADER_SIZE);
tfdt.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = tfdt.readInt();
int version = parseFullAtomVersion(fullAtom);
int version = Mp4Util.parseFullAtomVersion(fullAtom);
return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
}
@ -931,9 +541,9 @@ public final class FragmentedMp4Extractor implements Extractor {
*/
private static void parseTrun(Track track, DefaultSampleValues defaultSampleValues,
long decodeTime, int workaroundFlags, ParsableByteArray trun, TrackFragment out) {
trun.setPosition(ATOM_HEADER_SIZE);
trun.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = trun.readInt();
int flags = parseFullAtomFlags(fullAtom);
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
int sampleCount = trun.readUnsignedIntToInt();
if ((flags & 0x01 /* data_offset_present */) != 0) {
@ -991,7 +601,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
byte[] extendedTypeScratch) {
uuid.setPosition(ATOM_HEADER_SIZE);
uuid.setPosition(Mp4Util.ATOM_HEADER_SIZE);
uuid.readBytes(extendedTypeScratch, 0, 16);
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
@ -1010,9 +620,9 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) {
senc.setPosition(ATOM_HEADER_SIZE + offset);
senc.setPosition(Mp4Util.ATOM_HEADER_SIZE + offset);
int fullAtom = senc.readInt();
int flags = parseFullAtomFlags(fullAtom);
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) {
// TODO: Implement this.
@ -1034,9 +644,9 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a sidx atom (defined in 14496-12).
*/
private static SegmentIndex parseSidx(ParsableByteArray atom) {
atom.setPosition(ATOM_HEADER_SIZE);
atom.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = atom.readInt();
int version = parseFullAtomVersion(fullAtom);
int version = Mp4Util.parseFullAtomVersion(fullAtom);
atom.skip(4);
long timescale = atom.readUnsignedInt();
@ -1176,17 +786,8 @@ public final class FragmentedMp4Extractor implements Extractor {
inputStream.read(outputData, sampleSize);
if (track.type == Track.TYPE_VIDEO) {
// The mp4 file contains length-prefixed NAL units, but the decoder wants start code
// delimited content. Replace length prefixes with start codes.
int sampleOffset = outputData.position() - sampleSize;
int position = sampleOffset;
while (position < sampleOffset + sampleSize) {
outputData.position(position);
int length = readUnsignedIntToInt(outputData);
outputData.position(position);
outputData.put(NAL_START_CODE);
position += length + 4;
}
outputData.position(sampleOffset + sampleSize);
// delimited content.
Mp4Util.replaceLengthPrefixesWithAvcStartCodes(outputData, sampleSize);
}
out.size = sampleSize;
}
@ -1236,51 +837,4 @@ public final class FragmentedMp4Extractor implements Extractor {
}
}
/**
* Parses the version number out of the additional integer component of a full atom.
*/
private static int parseFullAtomVersion(int fullAtomInt) {
return 0x000000FF & (fullAtomInt >> 24);
}
/**
* Parses the atom flags out of the additional integer component of a full atom.
*/
private 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.
*/
private 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;
}
/** Represents the format for AC-3 audio. */
private static final class Ac3Format {
public final int channelCount;
public final int sampleRate;
public final int bitrate;
public Ac3Format(int channelCount, int sampleRate, int bitrate) {
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.bitrate = bitrate;
}
}
}

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.chunk.parser.webm;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder;
@ -184,6 +185,11 @@ public final class WebmExtractor implements Extractor {
return format;
}
@Override
public long getDurationUs() {
return durationUs == UNKNOWN ? C.UNKNOWN_TIME_US : durationUs;
}
@Override
public Map<UUID, byte[]> getPsshInfo() {
// TODO: Parse pssh data from Webm streams.

View File

@ -0,0 +1,481 @@
/*
* Copyright (C) 2014 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.exoplayer.mp4;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.Util;
import android.util.Pair;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */
public final class CommonMp4AtomParsers {
/** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
private static final int[] AC3_CHANNEL_COUNTS = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
/** Nominal bit-rates for AC-3 audio in kbps, indexed by bit_rate_code. (See ETSI TS 102 366.) */
private static final int[] AC3_BIT_RATES = new int[] {32, 40, 48, 56, 64, 80, 96, 112, 128, 160,
192, 224, 256, 320, 384, 448, 512, 576, 640};
/**
* Parses a trak atom (defined in 14496-12).
*
* @return A {@link Track} instance.
*/
public static Track parseTrak(Atom.ContainerAtom trak) {
Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
Assertions.checkState(trackType == Track.TYPE_AUDIO || trackType == Track.TYPE_VIDEO
|| trackType == Track.TYPE_TEXT || trackType == Track.TYPE_TIME_CODE);
Pair<Integer, Long> header = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data);
int id = header.first;
long duration = header.second;
long timescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
long durationUs;
if (duration == -1) {
durationUs = C.UNKNOWN_TIME_US;
} else {
durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, timescale);
}
Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf)
.getContainerAtomOfType(Atom.TYPE_stbl);
Pair<MediaFormat, TrackEncryptionBox[]> sampleDescriptions =
parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data);
return new Track(id, trackType, timescale, durationUs, sampleDescriptions.first,
sampleDescriptions.second);
}
/**
* Parses a tkhd atom (defined in 14496-12).
*
* @return A {@link Pair} consisting of the track id and duration (in the timescale indicated in
* the movie header box). The duration is set to -1 if the duration is unspecified.
*/
private static Pair<Integer, Long> parseTkhd(ParsableByteArray tkhd) {
tkhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = tkhd.readInt();
int version = Mp4Util.parseFullAtomVersion(fullAtom);
tkhd.skip(version == 0 ? 8 : 16);
int trackId = tkhd.readInt();
tkhd.skip(4);
boolean durationUnknown = true;
int durationPosition = tkhd.getPosition();
int durationByteCount = version == 0 ? 4 : 8;
for (int i = 0; i < durationByteCount; i++) {
if (tkhd.data[durationPosition + i] != -1) {
durationUnknown = false;
break;
}
}
long duration;
if (durationUnknown) {
tkhd.skip(durationByteCount);
duration = -1;
} else {
duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong();
}
return Pair.create(trackId, duration);
}
/**
* Parses an hdlr atom.
*
* @param hdlr The hdlr atom to parse.
* @return The track type.
*/
private static int parseHdlr(ParsableByteArray hdlr) {
hdlr.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE + 4);
return hdlr.readInt();
}
/**
* Parses an mdhd atom (defined in 14496-12).
*
* @param mdhd The mdhd atom to parse.
* @return The media timescale, defined as the number of time units that pass in one second.
*/
private static long parseMdhd(ParsableByteArray mdhd) {
mdhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = mdhd.readInt();
int version = Mp4Util.parseFullAtomVersion(fullAtom);
mdhd.skip(version == 0 ? 8 : 16);
return mdhd.readUnsignedInt();
}
private static Pair<MediaFormat, TrackEncryptionBox[]> parseStsd(ParsableByteArray stsd) {
stsd.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
int numberOfEntries = stsd.readInt();
MediaFormat mediaFormat = null;
TrackEncryptionBox[] trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
for (int i = 0; i < numberOfEntries; i++) {
int childStartPosition = stsd.getPosition();
int childAtomSize = stsd.readInt();
Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
int childAtomType = stsd.readInt();
if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3
|| childAtomType == Atom.TYPE_encv) {
Pair<MediaFormat, TrackEncryptionBox> avc =
parseAvcFromParent(stsd, childStartPosition, childAtomSize);
mediaFormat = avc.first;
trackEncryptionBoxes[i] = avc.second;
} else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca
|| childAtomType == Atom.TYPE_ac_3) {
Pair<MediaFormat, TrackEncryptionBox> audioSampleEntry =
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize);
mediaFormat = audioSampleEntry.first;
trackEncryptionBoxes[i] = audioSampleEntry.second;
} else if (childAtomType == Atom.TYPE_TTML) {
mediaFormat = MediaFormat.createTtmlFormat();
} else if (childAtomType == Atom.TYPE_mp4v) {
mediaFormat = parseMp4vFromParent(stsd, childStartPosition, childAtomSize);
}
stsd.setPosition(childStartPosition + childAtomSize);
}
return Pair.create(mediaFormat, trackEncryptionBoxes);
}
/** Returns the media format for an avc1 box. */
private static Pair<MediaFormat, TrackEncryptionBox> parseAvcFromParent(ParsableByteArray parent,
int position, int size) {
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
parent.skip(24);
int width = parent.readUnsignedShort();
int height = parent.readUnsignedShort();
float pixelWidthHeightRatio = 1;
parent.skip(50);
List<byte[]> initializationData = null;
TrackEncryptionBox trackEncryptionBox = null;
int childPosition = parent.getPosition();
while (childPosition - position < size) {
parent.setPosition(childPosition);
int childStartPosition = parent.getPosition();
int childAtomSize = parent.readInt();
if (childAtomSize == 0 && parent.getPosition() - position == size) {
// Handle optional terminating four zero bytes in MOV files.
break;
}
Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
int childAtomType = parent.readInt();
if (childAtomType == Atom.TYPE_avcC) {
initializationData = parseAvcCFromParent(parent, childStartPosition);
} else if (childAtomType == Atom.TYPE_sinf) {
trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
} else if (childAtomType == Atom.TYPE_pasp) {
pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition);
}
childPosition += childAtomSize;
}
MediaFormat format = MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
width, height, pixelWidthHeightRatio, initializationData);
return Pair.create(format, trackEncryptionBox);
}
private static List<byte[]> parseAvcCFromParent(ParsableByteArray parent, int position) {
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE + 4);
// Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
int nalUnitLength = (parent.readUnsignedByte() & 0x3) + 1;
if (nalUnitLength != 4) {
// readSample currently relies on a nalUnitLength of 4.
// TODO: Consider handling the case where it isn't.
throw new IllegalStateException();
}
List<byte[]> initializationData = new ArrayList<byte[]>();
// TODO: We should try and parse these using CodecSpecificDataUtil.parseSpsNalUnit, and
// expose the AVC profile and level somewhere useful; Most likely in MediaFormat.
int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F;
for (int j = 0; j < numSequenceParameterSets; j++) {
initializationData.add(Mp4Util.parseChildNalUnit(parent));
}
int numPictureParameterSets = parent.readUnsignedByte();
for (int j = 0; j < numPictureParameterSets; j++) {
initializationData.add(Mp4Util.parseChildNalUnit(parent));
}
return initializationData;
}
private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position,
int size) {
int childPosition = position + Mp4Util.ATOM_HEADER_SIZE;
TrackEncryptionBox trackEncryptionBox = null;
while (childPosition - position < size) {
parent.setPosition(childPosition);
int childAtomSize = parent.readInt();
int childAtomType = parent.readInt();
if (childAtomType == Atom.TYPE_frma) {
parent.readInt(); // dataFormat.
} else if (childAtomType == Atom.TYPE_schm) {
parent.skip(4);
parent.readInt(); // schemeType. Expect cenc
parent.readInt(); // schemeVersion. Expect 0x00010000
} else if (childAtomType == Atom.TYPE_schi) {
trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize);
}
childPosition += childAtomSize;
}
return trackEncryptionBox;
}
private static float parsePaspFromParent(ParsableByteArray parent, int position) {
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
int hSpacing = parent.readUnsignedIntToInt();
int vSpacing = parent.readUnsignedIntToInt();
return (float) hSpacing / vSpacing;
}
private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position,
int size) {
int childPosition = position + Mp4Util.ATOM_HEADER_SIZE;
while (childPosition - position < size) {
parent.setPosition(childPosition);
int childAtomSize = parent.readInt();
int childAtomType = parent.readInt();
if (childAtomType == Atom.TYPE_tenc) {
parent.skip(4);
int firstInt = parent.readInt();
boolean defaultIsEncrypted = (firstInt >> 8) == 1;
int defaultInitVectorSize = firstInt & 0xFF;
byte[] defaultKeyId = new byte[16];
parent.readBytes(defaultKeyId, 0, defaultKeyId.length);
return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId);
}
childPosition += childAtomSize;
}
return null;
}
/** Returns the media format for an mp4v box. */
private static MediaFormat parseMp4vFromParent(ParsableByteArray parent,
int position, int size) {
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
parent.skip(24);
int width = parent.readUnsignedShort();
int height = parent.readUnsignedShort();
parent.skip(50);
List<byte[]> initializationData = new ArrayList<byte[]>(1);
int childPosition = parent.getPosition();
while (childPosition - position < size) {
parent.setPosition(childPosition);
int childStartPosition = parent.getPosition();
int childAtomSize = parent.readInt();
Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
int childAtomType = parent.readInt();
if (childAtomType == Atom.TYPE_esds) {
initializationData.add(parseEsdsFromParent(parent, childStartPosition));
}
childPosition += childAtomSize;
}
return MediaFormat.createVideoFormat(
MimeTypes.VIDEO_MP4V, MediaFormat.NO_VALUE, width, height, initializationData);
}
private static Pair<MediaFormat, TrackEncryptionBox> parseAudioSampleEntry(
ParsableByteArray parent, int atomType, int position, int size) {
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
parent.skip(16);
int channelCount = parent.readUnsignedShort();
int sampleSize = parent.readUnsignedShort();
parent.skip(4);
int sampleRate = parent.readUnsignedFixedPoint1616();
int bitrate = MediaFormat.NO_VALUE;
byte[] initializationData = null;
TrackEncryptionBox trackEncryptionBox = null;
int childPosition = parent.getPosition();
while (childPosition - position < size) {
parent.setPosition(childPosition);
int childStartPosition = parent.getPosition();
int childAtomSize = parent.readInt();
Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
int childAtomType = parent.readInt();
if (atomType == Atom.TYPE_mp4a || atomType == Atom.TYPE_enca) {
if (childAtomType == Atom.TYPE_esds) {
initializationData = parseEsdsFromParent(parent, childStartPosition);
// TODO: Do we really need to do this? See [Internal: b/10903778]
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data.
Pair<Integer, Integer> audioSpecificConfig =
CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData);
sampleRate = audioSpecificConfig.first;
channelCount = audioSpecificConfig.second;
} else if (childAtomType == Atom.TYPE_sinf) {
trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
}
} else if (atomType == Atom.TYPE_ac_3 && childAtomType == Atom.TYPE_dac3) {
// TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
Ac3Format ac3Format =
parseAc3SpecificBoxFromParent(parent, childStartPosition);
if (ac3Format != null) {
sampleRate = ac3Format.sampleRate;
channelCount = ac3Format.channelCount;
bitrate = ac3Format.bitrate;
}
// TODO: Add support for encrypted AC-3.
trackEncryptionBox = null;
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
sampleRate = parseEc3SpecificBoxFromParent(parent, childStartPosition);
trackEncryptionBox = null;
}
childPosition += childAtomSize;
}
String mimeType;
if (atomType == Atom.TYPE_ac_3) {
mimeType = MimeTypes.AUDIO_AC3;
} else if (atomType == Atom.TYPE_ec_3) {
mimeType = MimeTypes.AUDIO_EC3;
} else {
mimeType = MimeTypes.AUDIO_AAC;
}
MediaFormat format = MediaFormat.createAudioFormat(
mimeType, sampleSize, channelCount, sampleRate, bitrate,
initializationData == null ? null : Collections.singletonList(initializationData));
return Pair.create(format, trackEncryptionBox);
}
/** Returns codec-specific initialization data contained in an esds box. */
private static byte[] parseEsdsFromParent(ParsableByteArray parent, int position) {
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE + 4);
// Start of the ES_Descriptor (defined in 14496-1)
parent.skip(1); // ES_Descriptor tag
int varIntByte = parent.readUnsignedByte();
while (varIntByte > 127) {
varIntByte = parent.readUnsignedByte();
}
parent.skip(2); // ES_ID
int flags = parent.readUnsignedByte();
if ((flags & 0x80 /* streamDependenceFlag */) != 0) {
parent.skip(2);
}
if ((flags & 0x40 /* URL_Flag */) != 0) {
parent.skip(parent.readUnsignedShort());
}
if ((flags & 0x20 /* OCRstreamFlag */) != 0) {
parent.skip(2);
}
// Start of the DecoderConfigDescriptor (defined in 14496-1)
parent.skip(1); // DecoderConfigDescriptor tag
varIntByte = parent.readUnsignedByte();
while (varIntByte > 127) {
varIntByte = parent.readUnsignedByte();
}
parent.skip(13);
// Start of AudioSpecificConfig (defined in 14496-3)
parent.skip(1); // AudioSpecificConfig tag
varIntByte = parent.readUnsignedByte();
int varInt = varIntByte & 0x7F;
while (varIntByte > 127) {
varIntByte = parent.readUnsignedByte();
varInt = varInt << 8;
varInt |= varIntByte & 0x7F;
}
byte[] initializationData = new byte[varInt];
parent.readBytes(initializationData, 0, varInt);
return initializationData;
}
private static Ac3Format parseAc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
// Start of the dac3 atom (defined in ETSI TS 102 366)
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
// fscod (sample rate code)
int fscod = (parent.readUnsignedByte() & 0xC0) >> 6;
int sampleRate;
switch (fscod) {
case 0:
sampleRate = 48000;
break;
case 1:
sampleRate = 44100;
break;
case 2:
sampleRate = 32000;
break;
default:
// TODO: The decoder should not use this stream.
return null;
}
int nextByte = parent.readUnsignedByte();
// Map acmod (audio coding mode) onto a channel count.
int channelCount = AC3_CHANNEL_COUNTS[(nextByte & 0x38) >> 3];
// lfeon (low frequency effects on)
if ((nextByte & 0x04) != 0) {
channelCount++;
}
// Map bit_rate_code onto a bit-rate in kbit/s.
int bitrate = AC3_BIT_RATES[((nextByte & 0x03) << 3) + (parent.readUnsignedByte() >> 5)];
return new Ac3Format(channelCount, sampleRate, bitrate);
}
private static int parseEc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
// Start of the dec3 atom (defined in ETSI TS 102 366)
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
// TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
return 0;
}
private CommonMp4AtomParsers() {
// Prevent instantiation.
}
/** Represents the format for AC-3 audio. */
private static final class Ac3Format {
public final int channelCount;
public final int sampleRate;
public final int bitrate;
public Ac3Format(int channelCount, int sampleRate, int bitrate) {
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.bitrate = bitrate;
}
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 2014 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.exoplayer.mp4;
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.ParsableByteArray;
import java.nio.ByteBuffer;
/**
* Utility methods and constants for parsing fragmented and unfragmented MP4 files.
*/
public final class Mp4Util {
/** 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;
/** 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};
/** 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
* {@code size} bytes preceding the buffer's position.
*/
public static void replaceLengthPrefixesWithAvcStartCodes(ByteBuffer buffer, int size) {
int sampleOffset = buffer.position() - size;
int position = sampleOffset;
while (position < sampleOffset + size) {
buffer.position(position);
int length = readUnsignedIntToInt(buffer);
buffer.position(position);
buffer.put(NAL_START_CODE);
position += length + 4;
}
buffer.position(sampleOffset + size);
}
/** Constructs and returns a NAL unit with a start code followed by the data in {@code atom}. */
public static byte[] parseChildNalUnit(ParsableByteArray atom) {
int length = atom.readUnsignedShort();
int offset = atom.getPosition();
atom.skip(length);
return CodecSpecificDataUtil.buildNalUnit(atom.data, offset, length);
}
}

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.mp4;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox;
@ -43,6 +44,10 @@ public final class Track {
* Type of a meta track.
*/
public static final int TYPE_META = 0x6D657461;
/**
* Type of a time-code track.
*/
public static final int TYPE_TIME_CODE = 0x746D6364;
/**
* The track identifier.
@ -50,7 +55,8 @@ public final class Track {
public final int id;
/**
* One of {@link #TYPE_VIDEO}, {@link #TYPE_AUDIO}, {@link #TYPE_HINT} and {@link #TYPE_META}.
* One of {@link #TYPE_VIDEO}, {@link #TYPE_AUDIO}, {@link #TYPE_HINT}, {@link #TYPE_META} and
* {@link #TYPE_TIME_CODE}.
*/
public final int type;
@ -59,6 +65,11 @@ public final class Track {
*/
public final long timescale;
/**
* The duration of the track in microseconds, or {@link C#UNKNOWN_TIME_US} if unknown.
*/
public final long durationUs;
/**
* The format if {@link #type} is {@link #TYPE_VIDEO} or {@link #TYPE_AUDIO}. Null otherwise.
*/
@ -69,11 +80,12 @@ public final class Track {
*/
public final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes;
public Track(int id, int type, long timescale, MediaFormat mediaFormat,
public Track(int id, int type, long timescale, long durationUs, MediaFormat mediaFormat,
TrackEncryptionBox[] sampleDescriptionEncryptionBoxes) {
this.id = id;
this.type = type;
this.timescale = timescale;
this.durationUs = durationUs;
this.mediaFormat = mediaFormat;
this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes;
}

View File

@ -167,8 +167,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
: Track.TYPE_AUDIO;
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME);
extractor.setTrack(new Track(trackIndex, trackType, streamElement.timescale, mediaFormat,
trackEncryptionBoxes));
extractor.setTrack(new Track(trackIndex, trackType, streamElement.timescale,
initialManifest.durationUs, mediaFormat, trackEncryptionBoxes));
extractors.put(trackIndex, extractor);
}
this.maxHeight = maxHeight;