Support multiplexed fMP4.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117228173
This commit is contained in:
parent
c08c5b6f4c
commit
37e00c8c6f
@ -50,20 +50,18 @@ public final class ChunkIndex implements SeekMap {
|
|||||||
private final long durationUs;
|
private final long durationUs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param durationUs The duration of the stream.
|
|
||||||
* @param sizes The chunk sizes, in bytes.
|
* @param sizes The chunk sizes, in bytes.
|
||||||
* @param offsets The chunk byte offsets.
|
* @param offsets The chunk byte offsets.
|
||||||
* @param durationsUs The chunk durations, in microseconds.
|
* @param durationsUs The chunk durations, in microseconds.
|
||||||
* @param timesUs The start time of each chunk, in microseconds.
|
* @param timesUs The start time of each chunk, in microseconds.
|
||||||
*/
|
*/
|
||||||
public ChunkIndex(long durationUs, int[] sizes, long[] offsets, long[] durationsUs,
|
public ChunkIndex(int[] sizes, long[] offsets, long[] durationsUs, long[] timesUs) {
|
||||||
long[] timesUs) {
|
|
||||||
this.durationUs = durationUs;
|
|
||||||
this.sizes = sizes;
|
this.sizes = sizes;
|
||||||
this.offsets = offsets;
|
this.offsets = offsets;
|
||||||
this.durationsUs = durationsUs;
|
this.durationsUs = durationsUs;
|
||||||
this.timesUs = timesUs;
|
this.timesUs = timesUs;
|
||||||
length = sizes.length;
|
length = sizes.length;
|
||||||
|
durationUs = durationsUs[length - 1] + timesUs[length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,12 +28,15 @@ import com.google.android.exoplayer.extractor.SeekMap;
|
|||||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.extractor.mp4.Atom.ContainerAtom;
|
import com.google.android.exoplayer.extractor.mp4.Atom.ContainerAtom;
|
||||||
import com.google.android.exoplayer.extractor.mp4.Atom.LeafAtom;
|
import com.google.android.exoplayer.extractor.mp4.Atom.LeafAtom;
|
||||||
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.NalUnitUtil;
|
import com.google.android.exoplayer.util.NalUnitUtil;
|
||||||
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;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -43,8 +46,6 @@ import java.util.UUID;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Facilitates the extraction of data from the fragmented mp4 container format.
|
* Facilitates the extraction of data from the fragmented mp4 container format.
|
||||||
* <p>
|
|
||||||
* This implementation only supports de-muxed (i.e. single track) streams.
|
|
||||||
*/
|
*/
|
||||||
public final class FragmentedMp4Extractor implements Extractor {
|
public final class FragmentedMp4Extractor implements Extractor {
|
||||||
|
|
||||||
@ -57,35 +58,45 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
* <p>
|
* <p>
|
||||||
* This flag does nothing if the stream is not a video stream.
|
* This flag does nothing if the stream is not a video stream.
|
||||||
*/
|
*/
|
||||||
public static final int WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
|
public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag to ignore any tfdt boxes in the stream.
|
* Flag to ignore any tfdt boxes in the stream.
|
||||||
*/
|
*/
|
||||||
public static final int WORKAROUND_IGNORE_TFDT_BOX = 2;
|
public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4
|
||||||
|
* container.
|
||||||
|
*/
|
||||||
|
private static final int FLAG_SIDELOADED = 4;
|
||||||
|
|
||||||
private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE =
|
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};
|
new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12};
|
||||||
|
|
||||||
// Parser states
|
// Parser states.
|
||||||
private static final int STATE_READING_ATOM_HEADER = 0;
|
private static final int STATE_READING_ATOM_HEADER = 0;
|
||||||
private static final int STATE_READING_ATOM_PAYLOAD = 1;
|
private static final int STATE_READING_ATOM_PAYLOAD = 1;
|
||||||
private static final int STATE_READING_ENCRYPTION_DATA = 2;
|
private static final int STATE_READING_ENCRYPTION_DATA = 2;
|
||||||
private static final int STATE_READING_SAMPLE_START = 3;
|
private static final int STATE_READING_SAMPLE_START = 3;
|
||||||
private static final int STATE_READING_SAMPLE_CONTINUE = 4;
|
private static final int STATE_READING_SAMPLE_CONTINUE = 4;
|
||||||
|
|
||||||
private final int workaroundFlags;
|
// Workarounds.
|
||||||
|
private final int flags;
|
||||||
|
private final Track sideloadedTrack;
|
||||||
|
|
||||||
|
// Track-linked data bundle, accessible as a whole through trackID.
|
||||||
|
private final SparseArray<TrackBundle> trackBundles;
|
||||||
|
|
||||||
// Temporary arrays.
|
// Temporary arrays.
|
||||||
private final ParsableByteArray nalStartCode;
|
private final ParsableByteArray nalStartCode;
|
||||||
private final ParsableByteArray nalLength;
|
private final ParsableByteArray nalLength;
|
||||||
private final ParsableByteArray encryptionSignalByte;
|
private final ParsableByteArray encryptionSignalByte;
|
||||||
|
|
||||||
// Parser state
|
// Parser state.
|
||||||
private final ParsableByteArray atomHeader;
|
private final ParsableByteArray atomHeader;
|
||||||
private final byte[] extendedTypeScratch;
|
private final byte[] extendedTypeScratch;
|
||||||
private final Stack<ContainerAtom> containerAtoms;
|
private final Stack<ContainerAtom> containerAtoms;
|
||||||
private final TrackFragment fragmentRun;
|
|
||||||
|
|
||||||
private int parserState;
|
private int parserState;
|
||||||
private int atomType;
|
private int atomType;
|
||||||
@ -94,18 +105,13 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
private ParsableByteArray atomData;
|
private ParsableByteArray atomData;
|
||||||
private long endOfMdatPosition;
|
private long endOfMdatPosition;
|
||||||
|
|
||||||
private int sampleIndex;
|
private TrackBundle currentTrackBundle;
|
||||||
private int sampleSize;
|
private int sampleSize;
|
||||||
private int sampleBytesWritten;
|
private int sampleBytesWritten;
|
||||||
private int sampleCurrentNalBytesRemaining;
|
private int sampleCurrentNalBytesRemaining;
|
||||||
|
|
||||||
// Data parsed from moov atom.
|
// Extractor output.
|
||||||
private Track track;
|
|
||||||
private DefaultSampleValues extendsDefaults;
|
|
||||||
|
|
||||||
// Extractor outputs.
|
|
||||||
private ExtractorOutput extractorOutput;
|
private ExtractorOutput extractorOutput;
|
||||||
private TrackOutput trackOutput;
|
|
||||||
|
|
||||||
// Whether extractorOutput.seekMap has been invoked.
|
// Whether extractorOutput.seekMap has been invoked.
|
||||||
private boolean haveOutputSeekMap;
|
private boolean haveOutputSeekMap;
|
||||||
@ -115,18 +121,27 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param workaroundFlags Flags to allow parsing of faulty streams.
|
* @param flags Flags to allow parsing of faulty streams.
|
||||||
* {@link #WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME} is currently the only flag defined.
|
|
||||||
*/
|
*/
|
||||||
public FragmentedMp4Extractor(int workaroundFlags) {
|
public FragmentedMp4Extractor(int flags) {
|
||||||
this.workaroundFlags = workaroundFlags;
|
this(flags, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param flags Flags to allow parsing of faulty streams.
|
||||||
|
* @param sideloadedTrack Sideloaded track information, in the case that the extractor
|
||||||
|
* will not receive a moov box in the input data.
|
||||||
|
*/
|
||||||
|
public FragmentedMp4Extractor(int flags, Track sideloadedTrack) {
|
||||||
|
this.sideloadedTrack = sideloadedTrack;
|
||||||
|
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
|
||||||
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
|
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
|
||||||
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
|
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
|
||||||
nalLength = new ParsableByteArray(4);
|
nalLength = new ParsableByteArray(4);
|
||||||
encryptionSignalByte = new ParsableByteArray(1);
|
encryptionSignalByte = new ParsableByteArray(1);
|
||||||
extendedTypeScratch = new byte[16];
|
extendedTypeScratch = new byte[16];
|
||||||
containerAtoms = new Stack<>();
|
containerAtoms = new Stack<>();
|
||||||
fragmentRun = new TrackFragment();
|
trackBundles = new SparseArray<>();
|
||||||
enterReadingAtomHeaderState();
|
enterReadingAtomHeaderState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,26 +150,15 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
return Sniffer.sniffFragmented(input);
|
return Sniffer.sniffFragmented(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sideloads track information into the extractor.
|
|
||||||
* <p>
|
|
||||||
* Should be called before {@link #read(ExtractorInput, PositionHolder)} in the case that the
|
|
||||||
* extractor will not receive a moov atom in the input data, from which track information would
|
|
||||||
* normally be parsed.
|
|
||||||
*
|
|
||||||
* @param track The track to sideload.
|
|
||||||
*/
|
|
||||||
public void setTrack(Track track) {
|
|
||||||
this.extendsDefaults = new DefaultSampleValues(0, 0, 0, 0);
|
|
||||||
this.track = track;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput output) {
|
public void init(ExtractorOutput output) {
|
||||||
extractorOutput = output;
|
extractorOutput = output;
|
||||||
trackOutput = output.track(0);
|
if (sideloadedTrack != null) {
|
||||||
|
trackBundles.put(0, new TrackBundle(sideloadedTrack, new TrackFragment(), output.track(0),
|
||||||
|
new DefaultSampleValues(0, 0, 0, 0)));
|
||||||
extractorOutput.endTracks();
|
extractorOutput.endTracks();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek() {
|
public void seek() {
|
||||||
@ -214,21 +218,22 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
long atomPosition = input.getPosition() - atomHeaderBytesRead;
|
long atomPosition = input.getPosition() - atomHeaderBytesRead;
|
||||||
if (atomType == Atom.TYPE_moof) {
|
if (atomType == Atom.TYPE_moof) {
|
||||||
// The data positions may be updated when parsing the tfhd/trun.
|
// The data positions may be updated when parsing the tfhd/trun.
|
||||||
fragmentRun.auxiliaryDataPosition = atomPosition;
|
int trackCount = trackBundles.size();
|
||||||
fragmentRun.dataPosition = atomPosition;
|
for (int i = 0; i < trackCount; i++) {
|
||||||
|
TrackFragment fragment = trackBundles.valueAt(i).fragment;
|
||||||
|
fragment.auxiliaryDataPosition = atomPosition;
|
||||||
|
fragment.dataPosition = atomPosition;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (atomType == Atom.TYPE_mdat) {
|
if (atomType == Atom.TYPE_mdat) {
|
||||||
|
currentTrackBundle = null;
|
||||||
endOfMdatPosition = atomPosition + atomSize;
|
endOfMdatPosition = atomPosition + atomSize;
|
||||||
if (!haveOutputSeekMap) {
|
if (!haveOutputSeekMap) {
|
||||||
extractorOutput.seekMap(new SeekMap.Unseekable(track.durationUs));
|
extractorOutput.seekMap(new SeekMap.Unseekable(C.UNKNOWN_TIME_US));
|
||||||
haveOutputSeekMap = true;
|
haveOutputSeekMap = true;
|
||||||
}
|
}
|
||||||
if (fragmentRun.sampleEncryptionDataNeedsFill) {
|
|
||||||
parserState = STATE_READING_ENCRYPTION_DATA;
|
parserState = STATE_READING_ENCRYPTION_DATA;
|
||||||
} else {
|
|
||||||
parserState = STATE_READING_SAMPLE_START;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +281,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
if (!containerAtoms.isEmpty()) {
|
if (!containerAtoms.isEmpty()) {
|
||||||
containerAtoms.peek().add(leaf);
|
containerAtoms.peek().add(leaf);
|
||||||
} else if (leaf.type == Atom.TYPE_sidx) {
|
} else if (leaf.type == Atom.TYPE_sidx) {
|
||||||
ChunkIndex segmentIndex = parseSidx(track.durationUs, leaf.data, inputPosition);
|
ChunkIndex segmentIndex = parseSidx(leaf.data, inputPosition);
|
||||||
extractorOutput.seekMap(segmentIndex);
|
extractorOutput.seekMap(segmentIndex);
|
||||||
haveOutputSeekMap = true;
|
haveOutputSeekMap = true;
|
||||||
}
|
}
|
||||||
@ -292,13 +297,14 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException {
|
private void onMoovContainerAtomRead(ContainerAtom moov) {
|
||||||
List<Atom.LeafAtom> moovChildren = moov.leafChildren;
|
Assertions.checkState(sideloadedTrack == null, "Unexpected moov box.");
|
||||||
int moovChildrenSize = moovChildren.size();
|
List<Atom.LeafAtom> moovLeafChildren = moov.leafChildren;
|
||||||
|
int moovLeafChildrenSize = moovLeafChildren.size();
|
||||||
|
|
||||||
DrmInitData.Mapped drmInitData = null;
|
DrmInitData.Mapped drmInitData = null;
|
||||||
for (int i = 0; i < moovChildrenSize; i++) {
|
for (int i = 0; i < moovLeafChildrenSize; i++) {
|
||||||
LeafAtom child = moovChildren.get(i);
|
LeafAtom child = moovLeafChildren.get(i);
|
||||||
if (child.type == Atom.TYPE_pssh) {
|
if (child.type == Atom.TYPE_pssh) {
|
||||||
if (drmInitData == null) {
|
if (drmInitData == null) {
|
||||||
drmInitData = new DrmInitData.Mapped();
|
drmInitData = new DrmInitData.Mapped();
|
||||||
@ -317,90 +323,119 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
extractorOutput.drmInitData(drmInitData);
|
extractorOutput.drmInitData(drmInitData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read declaration of Track Fragments in the Moov box.
|
||||||
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
|
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
|
||||||
extendsDefaults = parseTrex(mvex.getLeafAtomOfType(Atom.TYPE_trex).data);
|
SparseArray<DefaultSampleValues> defaultSampleValuesArray = new SparseArray<>();
|
||||||
track = AtomParsers.parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak),
|
int mvexChildrenSize = mvex.leafChildren.size();
|
||||||
moov.getLeafAtomOfType(Atom.TYPE_mvhd), false);
|
for (int i = 0; i < mvexChildrenSize; i++) {
|
||||||
if (track == null) {
|
Atom.LeafAtom atom = mvex.leafChildren.get(i);
|
||||||
throw new ParserException("Track type not supported.");
|
if (atom.type == Atom.TYPE_trex) {
|
||||||
|
Pair<Integer, DefaultSampleValues> trexData = parseTrex(atom.data);
|
||||||
|
defaultSampleValuesArray.put(trexData.first, trexData.second);
|
||||||
}
|
}
|
||||||
trackOutput.format(track.format);
|
}
|
||||||
|
|
||||||
|
// Construction of Tracks and TrackOutputs.
|
||||||
|
trackBundles.clear();
|
||||||
|
int moovContainerChildrenSize = moov.containerChildren.size();
|
||||||
|
int trackBundlesSize = 0;
|
||||||
|
for (int i = 0; i < moovContainerChildrenSize; i++) {
|
||||||
|
Atom.ContainerAtom atom = moov.containerChildren.get(i);
|
||||||
|
if (atom.type == Atom.TYPE_trak) {
|
||||||
|
Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), false);
|
||||||
|
if (track != null) {
|
||||||
|
DefaultSampleValues defaultSampleValues = defaultSampleValuesArray.get(track.id);
|
||||||
|
TrackBundle bundle = new TrackBundle(track, new TrackFragment(),
|
||||||
|
extractorOutput.track(trackBundlesSize++), defaultSampleValues);
|
||||||
|
bundle.output.format(track.format);
|
||||||
|
trackBundles.put(track.id, bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extractorOutput.endTracks();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
|
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
|
||||||
fragmentRun.reset();
|
parseMoof(moof, trackBundles, flags, extendedTypeScratch);
|
||||||
parseMoof(track, extendsDefaults, moof, fragmentRun, workaroundFlags, extendedTypeScratch);
|
|
||||||
sampleIndex = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a trex atom (defined in 14496-12).
|
* Parses a trex atom (defined in 14496-12).
|
||||||
*/
|
*/
|
||||||
private static DefaultSampleValues parseTrex(ParsableByteArray trex) {
|
private static Pair<Integer, DefaultSampleValues> parseTrex(ParsableByteArray trex) {
|
||||||
trex.setPosition(Atom.FULL_HEADER_SIZE + 4);
|
trex.setPosition(Atom.FULL_HEADER_SIZE);
|
||||||
|
int trackId = trex.readInt();
|
||||||
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();
|
||||||
int defaultSampleFlags = trex.readInt();
|
int defaultSampleFlags = trex.readInt();
|
||||||
return new DefaultSampleValues(defaultSampleDescriptionIndex, defaultSampleDuration,
|
|
||||||
defaultSampleSize, defaultSampleFlags);
|
return Pair.create(trackId, new DefaultSampleValues(defaultSampleDescriptionIndex,
|
||||||
|
defaultSampleDuration, defaultSampleSize, defaultSampleFlags));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void parseMoof(Track track, DefaultSampleValues extendsDefaults,
|
private static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray,
|
||||||
ContainerAtom moof, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch)
|
int flags, byte[] extendedTypeScratch) throws ParserException {
|
||||||
throws ParserException {
|
int moofContainerChildrenSize = moof.containerChildren.size();
|
||||||
if (moof.getChildAtomOfTypeCount(Atom.TYPE_traf) != 1) {
|
for (int i = 0; i < moofContainerChildrenSize; i++) {
|
||||||
throw new ParserException("Traf count in moof != 1 (unsupported).");
|
Atom.ContainerAtom child = moof.containerChildren.get(i);
|
||||||
|
if (child.type == Atom.TYPE_traf) {
|
||||||
|
parseTraf(child, trackBundleArray, flags, extendedTypeScratch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
parseTraf(track, extendsDefaults, moof.getContainerAtomOfType(Atom.TYPE_traf),
|
|
||||||
out, workaroundFlags, extendedTypeScratch);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a traf atom (defined in 14496-12).
|
* Parses a traf atom (defined in 14496-12).
|
||||||
*/
|
*/
|
||||||
private static void parseTraf(Track track, DefaultSampleValues extendsDefaults,
|
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
|
||||||
ContainerAtom traf, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch)
|
int flags, byte[] extendedTypeScratch) throws ParserException {
|
||||||
throws ParserException {
|
|
||||||
if (traf.getChildAtomOfTypeCount(Atom.TYPE_trun) != 1) {
|
if (traf.getChildAtomOfTypeCount(Atom.TYPE_trun) != 1) {
|
||||||
throw new ParserException("Trun count in traf != 1 (unsupported).");
|
throw new ParserException("Trun count in traf != 1 (unsupported).");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
|
||||||
|
TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags);
|
||||||
|
if (trackBundle == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TrackFragment fragment = trackBundle.fragment;
|
||||||
|
trackBundle.currentSampleIndex = 0;
|
||||||
|
fragment.reset();
|
||||||
|
|
||||||
LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
|
LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
|
||||||
long decodeTime;
|
long decodeTime;
|
||||||
if (tfdtAtom == null || (workaroundFlags & WORKAROUND_IGNORE_TFDT_BOX) != 0) {
|
if (tfdtAtom == null || (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) != 0) {
|
||||||
decodeTime = 0;
|
decodeTime = 0;
|
||||||
} else {
|
} else {
|
||||||
decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
|
decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
|
||||||
}
|
}
|
||||||
|
|
||||||
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
|
|
||||||
parseTfhd(extendsDefaults, tfhd.data, out);
|
|
||||||
|
|
||||||
LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun);
|
LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun);
|
||||||
parseTrun(track, out.header, decodeTime, workaroundFlags, trun.data, out);
|
parseTrun(trackBundle, decodeTime, flags, trun.data);
|
||||||
|
|
||||||
LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
|
LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
|
||||||
if (saiz != null) {
|
if (saiz != null) {
|
||||||
TrackEncryptionBox trackEncryptionBox =
|
TrackEncryptionBox trackEncryptionBox = trackBundle.track
|
||||||
track.sampleDescriptionEncryptionBoxes[out.header.sampleDescriptionIndex];
|
.sampleDescriptionEncryptionBoxes[fragment.header.sampleDescriptionIndex];
|
||||||
parseSaiz(trackEncryptionBox, saiz.data, out);
|
parseSaiz(trackEncryptionBox, saiz.data, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio);
|
LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio);
|
||||||
if (saio != null) {
|
if (saio != null) {
|
||||||
parseSaio(saio.data, out);
|
parseSaio(saio.data, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc);
|
LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc);
|
||||||
if (senc != null) {
|
if (senc != null) {
|
||||||
parseSenc(senc.data, out);
|
parseSenc(senc.data, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
int childrenSize = traf.leafChildren.size();
|
int childrenSize = traf.leafChildren.size();
|
||||||
for (int i = 0; i < childrenSize; i++) {
|
for (int i = 0; i < childrenSize; i++) {
|
||||||
LeafAtom atom = traf.leafChildren.get(i);
|
LeafAtom atom = traf.leafChildren.get(i);
|
||||||
if (atom.type == Atom.TYPE_uuid) {
|
if (atom.type == Atom.TYPE_uuid) {
|
||||||
parseUuid(atom.data, out, extendedTypeScratch);
|
parseUuid(atom.data, fragment, extendedTypeScratch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -441,7 +476,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
* Parses a saio atom (defined in 14496-12).
|
* Parses a saio atom (defined in 14496-12).
|
||||||
*
|
*
|
||||||
* @param saio The saio atom to parse.
|
* @param saio The saio atom to parse.
|
||||||
* @param out The track fragment to populate with data from the saio atom.
|
* @param out The {@link TrackFragment} to populate with data from the saio atom.
|
||||||
*/
|
*/
|
||||||
private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException {
|
private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException {
|
||||||
saio.setPosition(Atom.HEADER_SIZE);
|
saio.setPosition(Atom.HEADER_SIZE);
|
||||||
@ -463,36 +498,44 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a tfhd atom (defined in 14496-12).
|
* Parses a tfhd atom (defined in 14496-12), updates the corresponding {@link TrackFragment} and
|
||||||
|
* returns the {@link TrackBundle} of the corresponding {@link Track}. If the tfhd does not refer
|
||||||
|
* to any {@link TrackBundle}, {@code null} is returned and no changes are made.
|
||||||
*
|
*
|
||||||
* @param extendsDefaults Default sample values from the trex atom.
|
|
||||||
* @param tfhd The tfhd atom to parse.
|
* @param tfhd The tfhd atom to parse.
|
||||||
* @param out The track fragment to populate with data from the tfhd atom.
|
* @param trackBundles The track bundles, one of which corresponds to the tfhd atom being parsed.
|
||||||
|
* @return The {@link TrackBundle} to which the {@link TrackFragment} belongs, or null if the tfhd
|
||||||
|
* does not refer to any {@link TrackBundle}.
|
||||||
*/
|
*/
|
||||||
private static void parseTfhd(DefaultSampleValues extendsDefaults, ParsableByteArray tfhd,
|
private static TrackBundle parseTfhd(ParsableByteArray tfhd,
|
||||||
TrackFragment out) {
|
SparseArray<TrackBundle> trackBundles, int flags) {
|
||||||
tfhd.setPosition(Atom.HEADER_SIZE);
|
tfhd.setPosition(Atom.HEADER_SIZE);
|
||||||
int fullAtom = tfhd.readInt();
|
int fullAtom = tfhd.readInt();
|
||||||
int flags = Atom.parseFullAtomFlags(fullAtom);
|
int atomFlags = Atom.parseFullAtomFlags(fullAtom);
|
||||||
|
int trackId = tfhd.readInt();
|
||||||
tfhd.skipBytes(4); // trackId
|
TrackBundle trackBundle = trackBundles.get((flags & FLAG_SIDELOADED) == 0 ? trackId : 0);
|
||||||
if ((flags & 0x01 /* base_data_offset_present */) != 0) {
|
if (trackBundle == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ((atomFlags & 0x01 /* base_data_offset_present */) != 0) {
|
||||||
long baseDataPosition = tfhd.readUnsignedLongToLong();
|
long baseDataPosition = tfhd.readUnsignedLongToLong();
|
||||||
out.dataPosition = baseDataPosition;
|
trackBundle.fragment.dataPosition = baseDataPosition;
|
||||||
out.auxiliaryDataPosition = baseDataPosition;
|
trackBundle.fragment.auxiliaryDataPosition = baseDataPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DefaultSampleValues defaultSampleValues = trackBundle.defaultSampleValues;
|
||||||
int defaultSampleDescriptionIndex =
|
int defaultSampleDescriptionIndex =
|
||||||
((flags & 0x02 /* default_sample_description_index_present */) != 0)
|
((atomFlags & 0x02 /* default_sample_description_index_present */) != 0)
|
||||||
? tfhd.readUnsignedIntToInt() - 1 : extendsDefaults.sampleDescriptionIndex;
|
? tfhd.readUnsignedIntToInt() - 1 : defaultSampleValues.sampleDescriptionIndex;
|
||||||
int defaultSampleDuration = ((flags & 0x08 /* default_sample_duration_present */) != 0)
|
int defaultSampleDuration = ((atomFlags & 0x08 /* default_sample_duration_present */) != 0)
|
||||||
? tfhd.readUnsignedIntToInt() : extendsDefaults.duration;
|
? tfhd.readUnsignedIntToInt() : defaultSampleValues.duration;
|
||||||
int defaultSampleSize = ((flags & 0x10 /* default_sample_size_present */) != 0)
|
int defaultSampleSize = ((atomFlags & 0x10 /* default_sample_size_present */) != 0)
|
||||||
? tfhd.readUnsignedIntToInt() : extendsDefaults.size;
|
? tfhd.readUnsignedIntToInt() : defaultSampleValues.size;
|
||||||
int defaultSampleFlags = ((flags & 0x20 /* default_sample_flags_present */) != 0)
|
int defaultSampleFlags = ((atomFlags & 0x20 /* default_sample_flags_present */) != 0)
|
||||||
? tfhd.readUnsignedIntToInt() : extendsDefaults.flags;
|
? tfhd.readUnsignedIntToInt() : defaultSampleValues.flags;
|
||||||
out.header = new DefaultSampleValues(defaultSampleDescriptionIndex, defaultSampleDuration,
|
trackBundle.fragment.header = new DefaultSampleValues(defaultSampleDescriptionIndex,
|
||||||
defaultSampleSize, defaultSampleFlags);
|
defaultSampleDuration, defaultSampleSize, defaultSampleFlags);
|
||||||
|
return trackBundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -511,34 +554,38 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
/**
|
/**
|
||||||
* Parses a trun atom (defined in 14496-12).
|
* Parses a trun atom (defined in 14496-12).
|
||||||
*
|
*
|
||||||
* @param track The corresponding track.
|
* @param trackBundle The {@link TrackBundle} that contains the {@link TrackFragment} into
|
||||||
* @param defaultSampleValues Default sample values.
|
* which parsed data should be placed.
|
||||||
* @param decodeTime The decode time.
|
* @param decodeTime The decode time of the first sample in the fragment run.
|
||||||
|
* @param flags Flags to allow any required workaround to be executed.
|
||||||
* @param trun The trun atom to parse.
|
* @param trun The trun atom to parse.
|
||||||
* @param out The {@TrackFragment} into which parsed data should be placed.
|
|
||||||
*/
|
*/
|
||||||
private static void parseTrun(Track track, DefaultSampleValues defaultSampleValues,
|
private static void parseTrun(TrackBundle trackBundle, long decodeTime, int flags,
|
||||||
long decodeTime, int workaroundFlags, ParsableByteArray trun, TrackFragment out) {
|
ParsableByteArray trun) {
|
||||||
trun.setPosition(Atom.HEADER_SIZE);
|
trun.setPosition(Atom.HEADER_SIZE);
|
||||||
int fullAtom = trun.readInt();
|
int fullAtom = trun.readInt();
|
||||||
int flags = Atom.parseFullAtomFlags(fullAtom);
|
int atomFlags = Atom.parseFullAtomFlags(fullAtom);
|
||||||
|
|
||||||
|
Track track = trackBundle.track;
|
||||||
|
TrackFragment fragment = trackBundle.fragment;
|
||||||
|
DefaultSampleValues defaultSampleValues = fragment.header;
|
||||||
|
|
||||||
int sampleCount = trun.readUnsignedIntToInt();
|
int sampleCount = trun.readUnsignedIntToInt();
|
||||||
if ((flags & 0x01 /* data_offset_present */) != 0) {
|
if ((atomFlags & 0x01 /* data_offset_present */) != 0) {
|
||||||
out.dataPosition += trun.readInt();
|
fragment.dataPosition += trun.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean firstSampleFlagsPresent = (flags & 0x04 /* first_sample_flags_present */) != 0;
|
boolean firstSampleFlagsPresent = (atomFlags & 0x04 /* first_sample_flags_present */) != 0;
|
||||||
int firstSampleFlags = defaultSampleValues.flags;
|
int firstSampleFlags = defaultSampleValues.flags;
|
||||||
if (firstSampleFlagsPresent) {
|
if (firstSampleFlagsPresent) {
|
||||||
firstSampleFlags = trun.readUnsignedIntToInt();
|
firstSampleFlags = trun.readUnsignedIntToInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean sampleDurationsPresent = (flags & 0x100 /* sample_duration_present */) != 0;
|
boolean sampleDurationsPresent = (atomFlags & 0x100 /* sample_duration_present */) != 0;
|
||||||
boolean sampleSizesPresent = (flags & 0x200 /* sample_size_present */) != 0;
|
boolean sampleSizesPresent = (atomFlags & 0x200 /* sample_size_present */) != 0;
|
||||||
boolean sampleFlagsPresent = (flags & 0x400 /* sample_flags_present */) != 0;
|
boolean sampleFlagsPresent = (atomFlags & 0x400 /* sample_flags_present */) != 0;
|
||||||
boolean sampleCompositionTimeOffsetsPresent =
|
boolean sampleCompositionTimeOffsetsPresent =
|
||||||
(flags & 0x800 /* sample_composition_time_offsets_present */) != 0;
|
(atomFlags & 0x800 /* sample_composition_time_offsets_present */) != 0;
|
||||||
|
|
||||||
// Offset to the entire video timeline. In the presence of B-frames this is usually used to
|
// Offset to the entire video timeline. In the presence of B-frames this is usually used to
|
||||||
// ensure that the first frame's presentation timestamp is zero.
|
// ensure that the first frame's presentation timestamp is zero.
|
||||||
@ -551,16 +598,16 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
edtsOffset = Util.scaleLargeTimestamp(track.editListMediaTimes[0], 1000, track.timescale);
|
edtsOffset = Util.scaleLargeTimestamp(track.editListMediaTimes[0], 1000, track.timescale);
|
||||||
}
|
}
|
||||||
|
|
||||||
out.initTables(sampleCount);
|
fragment.initTables(sampleCount);
|
||||||
int[] sampleSizeTable = out.sampleSizeTable;
|
int[] sampleSizeTable = fragment.sampleSizeTable;
|
||||||
int[] sampleCompositionTimeOffsetTable = out.sampleCompositionTimeOffsetTable;
|
int[] sampleCompositionTimeOffsetTable = fragment.sampleCompositionTimeOffsetTable;
|
||||||
long[] sampleDecodingTimeTable = out.sampleDecodingTimeTable;
|
long[] sampleDecodingTimeTable = fragment.sampleDecodingTimeTable;
|
||||||
boolean[] sampleIsSyncFrameTable = out.sampleIsSyncFrameTable;
|
boolean[] sampleIsSyncFrameTable = fragment.sampleIsSyncFrameTable;
|
||||||
|
|
||||||
long timescale = track.timescale;
|
long timescale = track.timescale;
|
||||||
long cumulativeTime = decodeTime;
|
long cumulativeTime = decodeTime;
|
||||||
boolean workaroundEveryVideoFrameIsSyncFrame = track.type == Track.TYPE_vide
|
boolean workaroundEveryVideoFrameIsSyncFrame = track.type == Track.TYPE_vide
|
||||||
&& (workaroundFlags & WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0;
|
&& (flags & FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0;
|
||||||
for (int i = 0; i < sampleCount; i++) {
|
for (int i = 0; i < sampleCount; i++) {
|
||||||
// Use trun values if present, otherwise tfhd, otherwise trex.
|
// Use trun values if present, otherwise tfhd, otherwise trex.
|
||||||
int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt()
|
int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt()
|
||||||
@ -633,7 +680,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
/**
|
/**
|
||||||
* Parses a sidx atom (defined in 14496-12).
|
* Parses a sidx atom (defined in 14496-12).
|
||||||
*/
|
*/
|
||||||
private static ChunkIndex parseSidx(long durationUs, ParsableByteArray atom, long inputPosition)
|
private static ChunkIndex parseSidx(ParsableByteArray atom, long inputPosition)
|
||||||
throws ParserException {
|
throws ParserException {
|
||||||
atom.setPosition(Atom.HEADER_SIZE);
|
atom.setPosition(Atom.HEADER_SIZE);
|
||||||
int fullAtom = atom.readInt();
|
int fullAtom = atom.readInt();
|
||||||
@ -684,17 +731,31 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
offset += sizes[i];
|
offset += sizes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ChunkIndex(durationUs, sizes, offsets, durationsUs, timesUs);
|
return new ChunkIndex(sizes, offsets, durationsUs, timesUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException {
|
private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
int bytesToSkip = (int) (fragmentRun.auxiliaryDataPosition - input.getPosition());
|
TrackBundle nextTrackBundle = null;
|
||||||
|
long nextDataOffset = Long.MAX_VALUE;
|
||||||
|
int trackBundlesSize = trackBundles.size();
|
||||||
|
for (int i = 0; i < trackBundlesSize; i++) {
|
||||||
|
TrackFragment trackFragment = trackBundles.valueAt(i).fragment;
|
||||||
|
if (trackFragment.sampleEncryptionDataNeedsFill
|
||||||
|
&& trackFragment.auxiliaryDataPosition < nextDataOffset) {
|
||||||
|
nextDataOffset = trackFragment.auxiliaryDataPosition;
|
||||||
|
nextTrackBundle = trackBundles.valueAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextTrackBundle == null) {
|
||||||
|
parserState = STATE_READING_SAMPLE_START;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int bytesToSkip = (int) (nextDataOffset - input.getPosition());
|
||||||
if (bytesToSkip < 0) {
|
if (bytesToSkip < 0) {
|
||||||
throw new ParserException("Offset to encryption data was negative.");
|
throw new ParserException("Offset to encryption data was negative.");
|
||||||
}
|
}
|
||||||
input.skipFully(bytesToSkip);
|
input.skipFully(bytesToSkip);
|
||||||
fragmentRun.fillEncryptionData(input);
|
nextTrackBundle.fragment.fillEncryptionData(input);
|
||||||
parserState = STATE_READING_SAMPLE_START;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -713,7 +774,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
*/
|
*/
|
||||||
private boolean readSample(ExtractorInput input) throws IOException, InterruptedException {
|
private boolean readSample(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
if (parserState == STATE_READING_SAMPLE_START) {
|
if (parserState == STATE_READING_SAMPLE_START) {
|
||||||
if (sampleIndex == fragmentRun.length) {
|
if (currentTrackBundle == null) {
|
||||||
|
currentTrackBundle = getNextFragmentRun(trackBundles);
|
||||||
|
if (currentTrackBundle == null) {
|
||||||
// We've run out of samples in the current mdat. Discard any trailing data and prepare to
|
// We've run out of samples in the current mdat. Discard any trailing data and prepare to
|
||||||
// read the header of the next atom.
|
// read the header of the next atom.
|
||||||
int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
|
int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
|
||||||
@ -724,25 +787,31 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
enterReadingAtomHeaderState();
|
enterReadingAtomHeaderState();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sampleIndex == 0) {
|
|
||||||
// We're reading the first sample in the current mdat. Discard any preceding data.
|
long nextDataPosition = currentTrackBundle.fragment.dataPosition;
|
||||||
int bytesToSkip = (int) (fragmentRun.dataPosition - input.getPosition());
|
// We skip bytes preceding the next sample to read.
|
||||||
|
int bytesToSkip = (int) (nextDataPosition - input.getPosition());
|
||||||
if (bytesToSkip < 0) {
|
if (bytesToSkip < 0) {
|
||||||
throw new ParserException("Offset to sample data was negative.");
|
throw new ParserException("Offset to sample data was negative.");
|
||||||
}
|
}
|
||||||
input.skipFully(bytesToSkip);
|
input.skipFully(bytesToSkip);
|
||||||
}
|
}
|
||||||
sampleSize = fragmentRun.sampleSizeTable[sampleIndex];
|
sampleSize = currentTrackBundle.fragment
|
||||||
if (fragmentRun.definesEncryptionData) {
|
.sampleSizeTable[currentTrackBundle.currentSampleIndex];
|
||||||
sampleBytesWritten = appendSampleEncryptionData(fragmentRun.sampleEncryptionData);
|
if (currentTrackBundle.fragment.definesEncryptionData) {
|
||||||
|
sampleBytesWritten = appendSampleEncryptionData(currentTrackBundle);
|
||||||
sampleSize += sampleBytesWritten;
|
sampleSize += sampleBytesWritten;
|
||||||
} else {
|
} else {
|
||||||
sampleBytesWritten = 0;
|
sampleBytesWritten = 0;
|
||||||
}
|
}
|
||||||
sampleCurrentNalBytesRemaining = 0;
|
|
||||||
parserState = STATE_READING_SAMPLE_CONTINUE;
|
parserState = STATE_READING_SAMPLE_CONTINUE;
|
||||||
|
sampleCurrentNalBytesRemaining = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TrackFragment fragment = currentTrackBundle.fragment;
|
||||||
|
Track track = currentTrackBundle.track;
|
||||||
|
TrackOutput output = currentTrackBundle.output;
|
||||||
|
int sampleIndex = currentTrackBundle.currentSampleIndex;
|
||||||
if (track.nalUnitLengthFieldLength != -1) {
|
if (track.nalUnitLengthFieldLength != -1) {
|
||||||
// Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case
|
// Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case
|
||||||
// they're only 1 or 2 bytes long.
|
// they're only 1 or 2 bytes long.
|
||||||
@ -763,49 +832,88 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt();
|
sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt();
|
||||||
// Write a start code for the current NAL unit.
|
// Write a start code for the current NAL unit.
|
||||||
nalStartCode.setPosition(0);
|
nalStartCode.setPosition(0);
|
||||||
trackOutput.sampleData(nalStartCode, 4);
|
output.sampleData(nalStartCode, 4);
|
||||||
sampleBytesWritten += 4;
|
sampleBytesWritten += 4;
|
||||||
sampleSize += nalUnitLengthFieldLengthDiff;
|
sampleSize += nalUnitLengthFieldLengthDiff;
|
||||||
} else {
|
} else {
|
||||||
// Write the payload of the NAL unit.
|
// Write the payload of the NAL unit.
|
||||||
int writtenBytes = trackOutput.sampleData(input, sampleCurrentNalBytesRemaining, false);
|
int writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false);
|
||||||
sampleBytesWritten += writtenBytes;
|
sampleBytesWritten += writtenBytes;
|
||||||
sampleCurrentNalBytesRemaining -= writtenBytes;
|
sampleCurrentNalBytesRemaining -= writtenBytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
while (sampleBytesWritten < sampleSize) {
|
while (sampleBytesWritten < sampleSize) {
|
||||||
int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false);
|
int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
|
||||||
sampleBytesWritten += writtenBytes;
|
sampleBytesWritten += writtenBytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long sampleTimeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L;
|
long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L;
|
||||||
int sampleFlags = (fragmentRun.definesEncryptionData ? C.SAMPLE_FLAG_ENCRYPTED : 0)
|
int sampleFlags = (fragment.definesEncryptionData ? C.SAMPLE_FLAG_ENCRYPTED : 0)
|
||||||
| (fragmentRun.sampleIsSyncFrameTable[sampleIndex] ? C.SAMPLE_FLAG_SYNC : 0);
|
| (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.SAMPLE_FLAG_SYNC : 0);
|
||||||
int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex;
|
int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
|
||||||
byte[] encryptionKey = fragmentRun.definesEncryptionData
|
byte[] encryptionKey = fragment.definesEncryptionData
|
||||||
? track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId : null;
|
? track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId : null;
|
||||||
trackOutput.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);
|
output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);
|
||||||
|
|
||||||
sampleIndex++;
|
currentTrackBundle.currentSampleIndex++;
|
||||||
|
if (currentTrackBundle.currentSampleIndex == fragment.length) {
|
||||||
|
currentTrackBundle = null;
|
||||||
|
}
|
||||||
parserState = STATE_READING_SAMPLE_START;
|
parserState = STATE_READING_SAMPLE_START;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int appendSampleEncryptionData(ParsableByteArray sampleEncryptionData) {
|
/**
|
||||||
int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex;
|
* Returns the {@link TrackBundle} whose fragment run has the earliest file position out of those
|
||||||
TrackEncryptionBox encryptionBox =
|
* yet to be consumed, or null if all have been consumed.
|
||||||
track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex];
|
*/
|
||||||
|
private static TrackBundle getNextFragmentRun(SparseArray<TrackBundle> trackBundles) {
|
||||||
|
TrackBundle nextTrackBundle = null;
|
||||||
|
long nextTrackRunOffset = Long.MAX_VALUE;
|
||||||
|
|
||||||
|
int trackBundlesSize = trackBundles.size();
|
||||||
|
for (int i = 0; i < trackBundlesSize; i++) {
|
||||||
|
TrackBundle trackBundle = trackBundles.valueAt(i);
|
||||||
|
if (trackBundle.currentSampleIndex == trackBundle.fragment.length) {
|
||||||
|
// This track fragment contains no more runs in the next mdat box.
|
||||||
|
} else {
|
||||||
|
long trunOffset = trackBundle.fragment.dataPosition;
|
||||||
|
if (trunOffset < nextTrackRunOffset) {
|
||||||
|
nextTrackBundle = trackBundle;
|
||||||
|
nextTrackRunOffset = trunOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nextTrackBundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the corresponding encryption data to the {@link TrackOutput} contained in the given
|
||||||
|
* {@link TrackBundle}.
|
||||||
|
*
|
||||||
|
* @param trackBundle The {@link TrackBundle} that contains the {@link Track} for which the
|
||||||
|
* Sample encryption data must be output.
|
||||||
|
* @return The number of written bytes.
|
||||||
|
*/
|
||||||
|
private int appendSampleEncryptionData(TrackBundle trackBundle) {
|
||||||
|
TrackFragment trackFragment = trackBundle.fragment;
|
||||||
|
ParsableByteArray sampleEncryptionData = trackFragment.sampleEncryptionData;
|
||||||
|
int sampleDescriptionIndex = trackFragment.header.sampleDescriptionIndex;
|
||||||
|
TrackEncryptionBox encryptionBox = trackBundle.track
|
||||||
|
.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex];
|
||||||
int vectorSize = encryptionBox.initializationVectorSize;
|
int vectorSize = encryptionBox.initializationVectorSize;
|
||||||
boolean subsampleEncryption = fragmentRun.sampleHasSubsampleEncryptionTable[sampleIndex];
|
boolean subsampleEncryption = trackFragment
|
||||||
|
.sampleHasSubsampleEncryptionTable[trackBundle.currentSampleIndex];
|
||||||
|
|
||||||
// Write the signal byte, containing the vector size and the subsample encryption flag.
|
// Write the signal byte, containing the vector size and the subsample encryption flag.
|
||||||
encryptionSignalByte.data[0] = (byte) (vectorSize | (subsampleEncryption ? 0x80 : 0));
|
encryptionSignalByte.data[0] = (byte) (vectorSize | (subsampleEncryption ? 0x80 : 0));
|
||||||
encryptionSignalByte.setPosition(0);
|
encryptionSignalByte.setPosition(0);
|
||||||
trackOutput.sampleData(encryptionSignalByte, 1);
|
TrackOutput output = trackBundle.output;
|
||||||
|
output.sampleData(encryptionSignalByte, 1);
|
||||||
// Write the vector.
|
// Write the vector.
|
||||||
trackOutput.sampleData(sampleEncryptionData, vectorSize);
|
output.sampleData(sampleEncryptionData, vectorSize);
|
||||||
// If we don't have subsample encryption data, we're done.
|
// If we don't have subsample encryption data, we're done.
|
||||||
if (!subsampleEncryption) {
|
if (!subsampleEncryption) {
|
||||||
return 1 + vectorSize;
|
return 1 + vectorSize;
|
||||||
@ -814,7 +922,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
int subsampleCount = sampleEncryptionData.readUnsignedShort();
|
int subsampleCount = sampleEncryptionData.readUnsignedShort();
|
||||||
sampleEncryptionData.skipBytes(-2);
|
sampleEncryptionData.skipBytes(-2);
|
||||||
int subsampleDataLength = 2 + 6 * subsampleCount;
|
int subsampleDataLength = 2 + 6 * subsampleCount;
|
||||||
trackOutput.sampleData(sampleEncryptionData, subsampleDataLength);
|
output.sampleData(sampleEncryptionData, subsampleDataLength);
|
||||||
return 1 + vectorSize + subsampleDataLength;
|
return 1 + vectorSize + subsampleDataLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -835,4 +943,25 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
|| atom == Atom.TYPE_traf || atom == Atom.TYPE_mvex || atom == Atom.TYPE_edts;
|
|| atom == Atom.TYPE_traf || atom == Atom.TYPE_mvex || atom == Atom.TYPE_edts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds data corresponding to a single track.
|
||||||
|
*/
|
||||||
|
private static final class TrackBundle {
|
||||||
|
|
||||||
|
public final Track track;
|
||||||
|
public final TrackFragment fragment;
|
||||||
|
public final TrackOutput output;
|
||||||
|
public final DefaultSampleValues defaultSampleValues;
|
||||||
|
public int currentSampleIndex;
|
||||||
|
|
||||||
|
public TrackBundle(Track track, TrackFragment fragment, TrackOutput output,
|
||||||
|
DefaultSampleValues defaultSampleValues) {
|
||||||
|
this.track = Assertions.checkNotNull(track);
|
||||||
|
this.fragment = Assertions.checkNotNull(fragment);
|
||||||
|
this.output = Assertions.checkNotNull(output);
|
||||||
|
this.defaultSampleValues = Assertions.checkNotNull(defaultSampleValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -338,9 +338,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
Track track = new Track(j, streamElementType, timescale, C.UNKNOWN_TIME_US, durationUs,
|
Track track = new Track(j, streamElementType, timescale, C.UNKNOWN_TIME_US, durationUs,
|
||||||
formats[j], trackEncryptionBoxes, nalUnitLengthFieldLength, null, null);
|
formats[j], trackEncryptionBoxes, nalUnitLengthFieldLength, null, null);
|
||||||
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
|
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
|
||||||
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
|
FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
|
||||||
| FragmentedMp4Extractor.WORKAROUND_IGNORE_TFDT_BOX);
|
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track);
|
||||||
extractor.setTrack(track);
|
|
||||||
extractorWrappers[j] = new ChunkExtractorWrapper(extractor);
|
extractorWrappers[j] = new ChunkExtractorWrapper(extractor);
|
||||||
}
|
}
|
||||||
elementIndex = i;
|
elementIndex = i;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user