diff options
Diffstat (limited to 'tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java')
-rw-r--r-- | tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java | 826 |
1 files changed, 0 insertions, 826 deletions
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java deleted file mode 100644 index 48c7e3e1..00000000 --- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ /dev/null @@ -1,826 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.extractor.mp4; - -import androidx.annotation.IntDef; -import androidx.annotation.Nullable; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.ParserException; -import com.google.android.exoplayer2.audio.Ac4Util; -import com.google.android.exoplayer2.extractor.Extractor; -import com.google.android.exoplayer2.extractor.ExtractorInput; -import com.google.android.exoplayer2.extractor.ExtractorOutput; -import com.google.android.exoplayer2.extractor.ExtractorsFactory; -import com.google.android.exoplayer2.extractor.GaplessInfoHolder; -import com.google.android.exoplayer2.extractor.PositionHolder; -import com.google.android.exoplayer2.extractor.SeekMap; -import com.google.android.exoplayer2.extractor.SeekPoint; -import com.google.android.exoplayer2.extractor.TrackOutput; -import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom; -import com.google.android.exoplayer2.metadata.Metadata; -import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.MimeTypes; -import com.google.android.exoplayer2.util.NalUnitUtil; -import com.google.android.exoplayer2.util.ParsableByteArray; -import java.io.IOException; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.List; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; - -/** - * Extracts data from the MP4 container format. - */ -public final class Mp4Extractor implements Extractor, SeekMap { - - /** Factory for {@link Mp4Extractor} instances. */ - public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new Mp4Extractor()}; - - /** - * Flags controlling the behavior of the extractor. Possible flag value is {@link - * #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}. - */ - @Documented - @Retention(RetentionPolicy.SOURCE) - @IntDef( - flag = true, - value = {FLAG_WORKAROUND_IGNORE_EDIT_LISTS}) - public @interface Flags {} - /** - * Flag to ignore any edit lists in the stream. - */ - public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 1; - - /** Parser states. */ - @Documented - @Retention(RetentionPolicy.SOURCE) - @IntDef({STATE_READING_ATOM_HEADER, STATE_READING_ATOM_PAYLOAD, STATE_READING_SAMPLE}) - private @interface State {} - - private static final int STATE_READING_ATOM_HEADER = 0; - private static final int STATE_READING_ATOM_PAYLOAD = 1; - private static final int STATE_READING_SAMPLE = 2; - - /** Brand stored in the ftyp atom for QuickTime media. */ - private static final int BRAND_QUICKTIME = 0x71742020; - - /** - * When seeking within the source, if the offset is greater than or equal to this value (or the - * offset is negative), the source will be reloaded. - */ - private static final long RELOAD_MINIMUM_SEEK_DISTANCE = 256 * 1024; - - /** - * For poorly interleaved streams, the maximum byte difference one track is allowed to be read - * ahead before the source will be reloaded at a new position to read another track. - */ - private static final long MAXIMUM_READ_AHEAD_BYTES_STREAM = 10 * 1024 * 1024; - - private final @Flags int flags; - - // Temporary arrays. - private final ParsableByteArray nalStartCode; - private final ParsableByteArray nalLength; - private final ParsableByteArray scratch; - - private final ParsableByteArray atomHeader; - private final ArrayDeque<ContainerAtom> containerAtoms; - - @State private int parserState; - private int atomType; - private long atomSize; - private int atomHeaderBytesRead; - @Nullable private ParsableByteArray atomData; - - private int sampleTrackIndex; - private int sampleBytesRead; - private int sampleBytesWritten; - private int sampleCurrentNalBytesRemaining; - - // Extractor outputs. - private @MonotonicNonNull ExtractorOutput extractorOutput; - private Mp4Track[] tracks; - private long[][] accumulatedSampleSizes; - private int firstVideoTrackIndex; - private long durationUs; - private boolean isQuickTime; - - /** - * Creates a new extractor for unfragmented MP4 streams. - */ - public Mp4Extractor() { - this(0); - } - - /** - * Creates a new extractor for unfragmented MP4 streams, using the specified flags to control the - * extractor's behavior. - * - * @param flags Flags that control the extractor's behavior. - */ - public Mp4Extractor(@Flags int flags) { - this.flags = flags; - atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); - containerAtoms = new ArrayDeque<>(); - nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); - nalLength = new ParsableByteArray(4); - scratch = new ParsableByteArray(); - sampleTrackIndex = C.INDEX_UNSET; - } - - @Override - public boolean sniff(ExtractorInput input) throws IOException { - return Sniffer.sniffUnfragmented(input); - } - - @Override - public void init(ExtractorOutput output) { - extractorOutput = output; - } - - @Override - public void seek(long position, long timeUs) { - containerAtoms.clear(); - atomHeaderBytesRead = 0; - sampleTrackIndex = C.INDEX_UNSET; - sampleBytesRead = 0; - sampleBytesWritten = 0; - sampleCurrentNalBytesRemaining = 0; - if (position == 0) { - enterReadingAtomHeaderState(); - } else if (tracks != null) { - updateSampleIndices(timeUs); - } - } - - @Override - public void release() { - // Do nothing - } - - @Override - public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException { - while (true) { - switch (parserState) { - case STATE_READING_ATOM_HEADER: - if (!readAtomHeader(input)) { - return RESULT_END_OF_INPUT; - } - break; - case STATE_READING_ATOM_PAYLOAD: - if (readAtomPayload(input, seekPosition)) { - return RESULT_SEEK; - } - break; - case STATE_READING_SAMPLE: - return readSample(input, seekPosition); - default: - throw new IllegalStateException(); - } - } - } - - // SeekMap implementation. - - @Override - public boolean isSeekable() { - return true; - } - - @Override - public long getDurationUs() { - return durationUs; - } - - @Override - public SeekPoints getSeekPoints(long timeUs) { - if (tracks.length == 0) { - return new SeekPoints(SeekPoint.START); - } - - long firstTimeUs; - long firstOffset; - long secondTimeUs = C.TIME_UNSET; - long secondOffset = C.POSITION_UNSET; - - // If we have a video track, use it to establish one or two seek points. - if (firstVideoTrackIndex != C.INDEX_UNSET) { - TrackSampleTable sampleTable = tracks[firstVideoTrackIndex].sampleTable; - int sampleIndex = getSynchronizationSampleIndex(sampleTable, timeUs); - if (sampleIndex == C.INDEX_UNSET) { - return new SeekPoints(SeekPoint.START); - } - long sampleTimeUs = sampleTable.timestampsUs[sampleIndex]; - firstTimeUs = sampleTimeUs; - firstOffset = sampleTable.offsets[sampleIndex]; - if (sampleTimeUs < timeUs && sampleIndex < sampleTable.sampleCount - 1) { - int secondSampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); - if (secondSampleIndex != C.INDEX_UNSET && secondSampleIndex != sampleIndex) { - secondTimeUs = sampleTable.timestampsUs[secondSampleIndex]; - secondOffset = sampleTable.offsets[secondSampleIndex]; - } - } - } else { - firstTimeUs = timeUs; - firstOffset = Long.MAX_VALUE; - } - - // Take into account other tracks. - for (int i = 0; i < tracks.length; i++) { - if (i != firstVideoTrackIndex) { - TrackSampleTable sampleTable = tracks[i].sampleTable; - firstOffset = maybeAdjustSeekOffset(sampleTable, firstTimeUs, firstOffset); - if (secondTimeUs != C.TIME_UNSET) { - secondOffset = maybeAdjustSeekOffset(sampleTable, secondTimeUs, secondOffset); - } - } - } - - SeekPoint firstSeekPoint = new SeekPoint(firstTimeUs, firstOffset); - if (secondTimeUs == C.TIME_UNSET) { - return new SeekPoints(firstSeekPoint); - } else { - SeekPoint secondSeekPoint = new SeekPoint(secondTimeUs, secondOffset); - return new SeekPoints(firstSeekPoint, secondSeekPoint); - } - } - - // Private methods. - - private void enterReadingAtomHeaderState() { - parserState = STATE_READING_ATOM_HEADER; - atomHeaderBytesRead = 0; - } - - private boolean readAtomHeader(ExtractorInput input) throws IOException { - if (atomHeaderBytesRead == 0) { - // Read the standard length atom header. - if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) { - return false; - } - atomHeaderBytesRead = Atom.HEADER_SIZE; - atomHeader.setPosition(0); - atomSize = atomHeader.readUnsignedInt(); - atomType = atomHeader.readInt(); - } - - if (atomSize == Atom.DEFINES_LARGE_SIZE) { - // Read the large size. - int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; - input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining); - atomHeaderBytesRead += headerBytesRemaining; - atomSize = atomHeader.readUnsignedLongToLong(); - } else if (atomSize == Atom.EXTENDS_TO_END_SIZE) { - // The atom extends to the end of the file. Note that if the atom is within a container we can - // work out its size even if the input length is unknown. - long endPosition = input.getLength(); - if (endPosition == C.LENGTH_UNSET) { - @Nullable ContainerAtom containerAtom = containerAtoms.peek(); - if (containerAtom != null) { - endPosition = containerAtom.endPosition; - } - } - if (endPosition != C.LENGTH_UNSET) { - atomSize = endPosition - input.getPosition() + atomHeaderBytesRead; - } - } - - if (atomSize < atomHeaderBytesRead) { - throw new ParserException("Atom size less than header length (unsupported)."); - } - - if (shouldParseContainerAtom(atomType)) { - long endPosition = input.getPosition() + atomSize - atomHeaderBytesRead; - if (atomSize != atomHeaderBytesRead && atomType == Atom.TYPE_meta) { - maybeSkipRemainingMetaAtomHeaderBytes(input); - } - containerAtoms.push(new ContainerAtom(atomType, endPosition)); - if (atomSize == atomHeaderBytesRead) { - processAtomEnded(endPosition); - } else { - // Start reading the first child atom. - enterReadingAtomHeaderState(); - } - } else if (shouldParseLeafAtom(atomType)) { - // We don't support parsing of leaf atoms that define extended atom sizes, or that have - // lengths greater than Integer.MAX_VALUE. - Assertions.checkState(atomHeaderBytesRead == Atom.HEADER_SIZE); - Assertions.checkState(atomSize <= Integer.MAX_VALUE); - atomData = new ParsableByteArray((int) atomSize); - System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE); - parserState = STATE_READING_ATOM_PAYLOAD; - } else { - atomData = null; - parserState = STATE_READING_ATOM_PAYLOAD; - } - - return true; - } - - /** - * Processes the atom payload. If {@link #atomData} is null and the size is at or above the - * threshold {@link #RELOAD_MINIMUM_SEEK_DISTANCE}, {@code true} is returned and the caller should - * restart loading at the position in {@code positionHolder}. Otherwise, the atom is read/skipped. - */ - private boolean readAtomPayload(ExtractorInput input, PositionHolder positionHolder) - throws IOException { - long atomPayloadSize = atomSize - atomHeaderBytesRead; - long atomEndPosition = input.getPosition() + atomPayloadSize; - boolean seekRequired = false; - if (atomData != null) { - input.readFully(atomData.data, atomHeaderBytesRead, (int) atomPayloadSize); - if (atomType == Atom.TYPE_ftyp) { - isQuickTime = processFtypAtom(atomData); - } else if (!containerAtoms.isEmpty()) { - containerAtoms.peek().add(new Atom.LeafAtom(atomType, atomData)); - } - } else { - // We don't need the data. Skip or seek, depending on how large the atom is. - if (atomPayloadSize < RELOAD_MINIMUM_SEEK_DISTANCE) { - input.skipFully((int) atomPayloadSize); - } else { - positionHolder.position = input.getPosition() + atomPayloadSize; - seekRequired = true; - } - } - processAtomEnded(atomEndPosition); - return seekRequired && parserState != STATE_READING_SAMPLE; - } - - private void processAtomEnded(long atomEndPosition) throws ParserException { - while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == atomEndPosition) { - Atom.ContainerAtom containerAtom = containerAtoms.pop(); - if (containerAtom.type == Atom.TYPE_moov) { - // We've reached the end of the moov atom. Process it and prepare to read samples. - processMoovAtom(containerAtom); - containerAtoms.clear(); - parserState = STATE_READING_SAMPLE; - } else if (!containerAtoms.isEmpty()) { - containerAtoms.peek().add(containerAtom); - } - } - if (parserState != STATE_READING_SAMPLE) { - enterReadingAtomHeaderState(); - } - } - - /** - * Updates the stored track metadata to reflect the contents of the specified moov atom. - */ - private void processMoovAtom(ContainerAtom moov) throws ParserException { - int firstVideoTrackIndex = C.INDEX_UNSET; - long durationUs = C.TIME_UNSET; - List<Mp4Track> tracks = new ArrayList<>(); - - // Process metadata. - @Nullable Metadata udtaMetadata = null; - GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder(); - @Nullable Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); - if (udta != null) { - udtaMetadata = AtomParsers.parseUdta(udta, isQuickTime); - if (udtaMetadata != null) { - gaplessInfoHolder.setFromMetadata(udtaMetadata); - } - } - @Nullable Metadata mdtaMetadata = null; - @Nullable Atom.ContainerAtom meta = moov.getContainerAtomOfType(Atom.TYPE_meta); - if (meta != null) { - mdtaMetadata = AtomParsers.parseMdtaFromMeta(meta); - } - - boolean ignoreEditLists = (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0; - ArrayList<TrackSampleTable> trackSampleTables = - getTrackSampleTables(moov, gaplessInfoHolder, ignoreEditLists); - - int trackCount = trackSampleTables.size(); - for (int i = 0; i < trackCount; i++) { - TrackSampleTable trackSampleTable = trackSampleTables.get(i); - Track track = trackSampleTable.track; - long trackDurationUs = - track.durationUs != C.TIME_UNSET ? track.durationUs : trackSampleTable.durationUs; - durationUs = Math.max(durationUs, trackDurationUs); - Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, - extractorOutput.track(i, track.type)); - - // Each sample has up to three bytes of overhead for the start code that replaces its length. - // Allow ten source samples per output sample, like the platform extractor. - int maxInputSize = trackSampleTable.maximumSize + 3 * 10; - Format.Builder formatBuilder = track.format.buildUpon(); - formatBuilder.setMaxInputSize(maxInputSize); - if (track.type == C.TRACK_TYPE_VIDEO - && trackDurationUs > 0 - && trackSampleTable.sampleCount > 1) { - float frameRate = trackSampleTable.sampleCount / (trackDurationUs / 1000000f); - formatBuilder.setFrameRate(frameRate); - } - MetadataUtil.setFormatMetadata( - track.type, udtaMetadata, mdtaMetadata, gaplessInfoHolder, formatBuilder); - mp4Track.trackOutput.format(formatBuilder.build()); - - if (track.type == C.TRACK_TYPE_VIDEO && firstVideoTrackIndex == C.INDEX_UNSET) { - firstVideoTrackIndex = tracks.size(); - } - tracks.add(mp4Track); - } - this.firstVideoTrackIndex = firstVideoTrackIndex; - this.durationUs = durationUs; - this.tracks = tracks.toArray(new Mp4Track[0]); - accumulatedSampleSizes = calculateAccumulatedSampleSizes(this.tracks); - - extractorOutput.endTracks(); - extractorOutput.seekMap(this); - } - - private ArrayList<TrackSampleTable> getTrackSampleTables( - ContainerAtom moov, GaplessInfoHolder gaplessInfoHolder, boolean ignoreEditLists) - throws ParserException { - ArrayList<TrackSampleTable> trackSampleTables = new ArrayList<>(); - for (int i = 0; i < moov.containerChildren.size(); i++) { - Atom.ContainerAtom atom = moov.containerChildren.get(i); - if (atom.type != Atom.TYPE_trak) { - continue; - } - @Nullable - Track track = - AtomParsers.parseTrak( - atom, - moov.getLeafAtomOfType(Atom.TYPE_mvhd), - /* duration= */ C.TIME_UNSET, - /* drmInitData= */ null, - ignoreEditLists, - isQuickTime); - if (track == null) { - continue; - } - Atom.ContainerAtom stblAtom = - atom.getContainerAtomOfType(Atom.TYPE_mdia) - .getContainerAtomOfType(Atom.TYPE_minf) - .getContainerAtomOfType(Atom.TYPE_stbl); - TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder); - if (trackSampleTable.sampleCount == 0) { - continue; - } - trackSampleTables.add(trackSampleTable); - } - return trackSampleTables; - } - - /** - * Attempts to extract the next sample in the current mdat atom for the specified track. - * - * <p>Returns {@link #RESULT_SEEK} if the source should be reloaded from the position in {@code - * positionHolder}. - * - * <p>Returns {@link #RESULT_END_OF_INPUT} if no samples are left. Otherwise, returns {@link - * #RESULT_CONTINUE}. - * - * @param input The {@link ExtractorInput} from which to read data. - * @param positionHolder If {@link #RESULT_SEEK} is returned, this holder is updated to hold the - * position of the required data. - * @return One of the {@code RESULT_*} flags in {@link Extractor}. - * @throws IOException If an error occurs reading from the input. - */ - private int readSample(ExtractorInput input, PositionHolder positionHolder) throws IOException { - long inputPosition = input.getPosition(); - if (sampleTrackIndex == C.INDEX_UNSET) { - sampleTrackIndex = getTrackIndexOfNextReadSample(inputPosition); - if (sampleTrackIndex == C.INDEX_UNSET) { - return RESULT_END_OF_INPUT; - } - } - Mp4Track track = tracks[sampleTrackIndex]; - TrackOutput trackOutput = track.trackOutput; - int sampleIndex = track.sampleIndex; - long position = track.sampleTable.offsets[sampleIndex]; - int sampleSize = track.sampleTable.sizes[sampleIndex]; - long skipAmount = position - inputPosition + sampleBytesRead; - if (skipAmount < 0 || skipAmount >= RELOAD_MINIMUM_SEEK_DISTANCE) { - positionHolder.position = position; - return RESULT_SEEK; - } - if (track.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) { - // The sample information is contained in a cdat atom. The header must be discarded for - // committing. - skipAmount += Atom.HEADER_SIZE; - sampleSize -= Atom.HEADER_SIZE; - } - input.skipFully((int) skipAmount); - if (track.track.nalUnitLengthFieldLength != 0) { - // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case - // they're only 1 or 2 bytes long. - byte[] nalLengthData = nalLength.data; - nalLengthData[0] = 0; - nalLengthData[1] = 0; - nalLengthData[2] = 0; - int nalUnitLengthFieldLength = track.track.nalUnitLengthFieldLength; - int nalUnitLengthFieldLengthDiff = 4 - track.track.nalUnitLengthFieldLength; - // NAL units are length delimited, but the decoder requires start code delimited units. - // Loop until we've written the sample to the track output, replacing length delimiters with - // start codes as we encounter them. - while (sampleBytesWritten < sampleSize) { - if (sampleCurrentNalBytesRemaining == 0) { - // Read the NAL length so that we know where we find the next one. - input.readFully(nalLengthData, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength); - sampleBytesRead += nalUnitLengthFieldLength; - nalLength.setPosition(0); - int nalLengthInt = nalLength.readInt(); - if (nalLengthInt < 0) { - throw new ParserException("Invalid NAL length"); - } - sampleCurrentNalBytesRemaining = nalLengthInt; - // Write a start code for the current NAL unit. - nalStartCode.setPosition(0); - trackOutput.sampleData(nalStartCode, 4); - sampleBytesWritten += 4; - sampleSize += nalUnitLengthFieldLengthDiff; - } else { - // Write the payload of the NAL unit. - int writtenBytes = trackOutput.sampleData(input, sampleCurrentNalBytesRemaining, false); - sampleBytesRead += writtenBytes; - sampleBytesWritten += writtenBytes; - sampleCurrentNalBytesRemaining -= writtenBytes; - } - } - } else { - if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType)) { - if (sampleBytesWritten == 0) { - Ac4Util.getAc4SampleHeader(sampleSize, scratch); - trackOutput.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE); - sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE; - } - sampleSize += Ac4Util.SAMPLE_HEADER_SIZE; - } - while (sampleBytesWritten < sampleSize) { - int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false); - sampleBytesRead += writtenBytes; - sampleBytesWritten += writtenBytes; - sampleCurrentNalBytesRemaining -= writtenBytes; - } - } - trackOutput.sampleMetadata(track.sampleTable.timestampsUs[sampleIndex], - track.sampleTable.flags[sampleIndex], sampleSize, 0, null); - track.sampleIndex++; - sampleTrackIndex = C.INDEX_UNSET; - sampleBytesRead = 0; - sampleBytesWritten = 0; - sampleCurrentNalBytesRemaining = 0; - return RESULT_CONTINUE; - } - - /** - * Returns the index of the track that contains the next sample to be read, or {@link - * C#INDEX_UNSET} if no samples remain. - * - * <p>The preferred choice is the sample with the smallest offset not requiring a source reload, - * or if not available the sample with the smallest overall offset to avoid subsequent source - * reloads. - * - * <p>To deal with poor sample interleaving, we also check whether the required memory to catch up - * with the next logical sample (based on sample time) exceeds {@link - * #MAXIMUM_READ_AHEAD_BYTES_STREAM}. If this is the case, we continue with this sample even - * though it may require a source reload. - */ - private int getTrackIndexOfNextReadSample(long inputPosition) { - long preferredSkipAmount = Long.MAX_VALUE; - boolean preferredRequiresReload = true; - int preferredTrackIndex = C.INDEX_UNSET; - long preferredAccumulatedBytes = Long.MAX_VALUE; - long minAccumulatedBytes = Long.MAX_VALUE; - boolean minAccumulatedBytesRequiresReload = true; - int minAccumulatedBytesTrackIndex = C.INDEX_UNSET; - for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) { - Mp4Track track = tracks[trackIndex]; - int sampleIndex = track.sampleIndex; - if (sampleIndex == track.sampleTable.sampleCount) { - continue; - } - long sampleOffset = track.sampleTable.offsets[sampleIndex]; - long sampleAccumulatedBytes = accumulatedSampleSizes[trackIndex][sampleIndex]; - long skipAmount = sampleOffset - inputPosition; - boolean requiresReload = skipAmount < 0 || skipAmount >= RELOAD_MINIMUM_SEEK_DISTANCE; - if ((!requiresReload && preferredRequiresReload) - || (requiresReload == preferredRequiresReload && skipAmount < preferredSkipAmount)) { - preferredRequiresReload = requiresReload; - preferredSkipAmount = skipAmount; - preferredTrackIndex = trackIndex; - preferredAccumulatedBytes = sampleAccumulatedBytes; - } - if (sampleAccumulatedBytes < minAccumulatedBytes) { - minAccumulatedBytes = sampleAccumulatedBytes; - minAccumulatedBytesRequiresReload = requiresReload; - minAccumulatedBytesTrackIndex = trackIndex; - } - } - return minAccumulatedBytes == Long.MAX_VALUE - || !minAccumulatedBytesRequiresReload - || preferredAccumulatedBytes < minAccumulatedBytes + MAXIMUM_READ_AHEAD_BYTES_STREAM - ? preferredTrackIndex - : minAccumulatedBytesTrackIndex; - } - - /** - * Updates every track's sample index to point its latest sync sample before/at {@code timeUs}. - */ - private void updateSampleIndices(long timeUs) { - for (Mp4Track track : tracks) { - TrackSampleTable sampleTable = track.sampleTable; - int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs); - if (sampleIndex == C.INDEX_UNSET) { - // Handle the case where the requested time is before the first synchronization sample. - sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); - } - track.sampleIndex = sampleIndex; - } - } - - /** - * Possibly skips the version and flags fields (1+3 byte) of a full meta atom of the {@code - * input}. - * - * <p>Atoms of type {@link Atom#TYPE_meta} are defined to be full atoms which have four additional - * bytes for a version and a flags field (see 4.2 'Object Structure' in ISO/IEC 14496-12:2005). - * QuickTime do not have such a full box structure. Since some of these files are encoded wrongly, - * we can't rely on the file type though. Instead we must check the 8 bytes after the common - * header bytes ourselves. - */ - private void maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input) throws IOException { - scratch.reset(8); - // Peek the next 8 bytes which can be either - // (iso) [1 byte version + 3 bytes flags][4 byte size of next atom] - // (qt) [4 byte size of next atom ][4 byte hdlr atom type ] - // In case of (iso) we need to skip the next 4 bytes. - input.peekFully(scratch.data, 0, 8); - scratch.skipBytes(4); - if (scratch.readInt() == Atom.TYPE_hdlr) { - input.resetPeekPosition(); - } else { - input.skipFully(4); - } - } - - /** - * For each sample of each track, calculates accumulated size of all samples which need to be read - * before this sample can be used. - */ - private static long[][] calculateAccumulatedSampleSizes(Mp4Track[] tracks) { - long[][] accumulatedSampleSizes = new long[tracks.length][]; - int[] nextSampleIndex = new int[tracks.length]; - long[] nextSampleTimesUs = new long[tracks.length]; - boolean[] tracksFinished = new boolean[tracks.length]; - for (int i = 0; i < tracks.length; i++) { - accumulatedSampleSizes[i] = new long[tracks[i].sampleTable.sampleCount]; - nextSampleTimesUs[i] = tracks[i].sampleTable.timestampsUs[0]; - } - long accumulatedSampleSize = 0; - int finishedTracks = 0; - while (finishedTracks < tracks.length) { - long minTimeUs = Long.MAX_VALUE; - int minTimeTrackIndex = -1; - for (int i = 0; i < tracks.length; i++) { - if (!tracksFinished[i] && nextSampleTimesUs[i] <= minTimeUs) { - minTimeTrackIndex = i; - minTimeUs = nextSampleTimesUs[i]; - } - } - int trackSampleIndex = nextSampleIndex[minTimeTrackIndex]; - accumulatedSampleSizes[minTimeTrackIndex][trackSampleIndex] = accumulatedSampleSize; - accumulatedSampleSize += tracks[minTimeTrackIndex].sampleTable.sizes[trackSampleIndex]; - nextSampleIndex[minTimeTrackIndex] = ++trackSampleIndex; - if (trackSampleIndex < accumulatedSampleSizes[minTimeTrackIndex].length) { - nextSampleTimesUs[minTimeTrackIndex] = - tracks[minTimeTrackIndex].sampleTable.timestampsUs[trackSampleIndex]; - } else { - tracksFinished[minTimeTrackIndex] = true; - finishedTracks++; - } - } - return accumulatedSampleSizes; - } - - /** - * Adjusts a seek point offset to take into account the track with the given {@code sampleTable}, - * for a given {@code seekTimeUs}. - * - * @param sampleTable The sample table to use. - * @param seekTimeUs The seek time in microseconds. - * @param offset The current offset. - * @return The adjusted offset. - */ - private static long maybeAdjustSeekOffset( - TrackSampleTable sampleTable, long seekTimeUs, long offset) { - int sampleIndex = getSynchronizationSampleIndex(sampleTable, seekTimeUs); - if (sampleIndex == C.INDEX_UNSET) { - return offset; - } - long sampleOffset = sampleTable.offsets[sampleIndex]; - return Math.min(sampleOffset, offset); - } - - /** - * Returns the index of the synchronization sample before or at {@code timeUs}, or the index of - * the first synchronization sample if located after {@code timeUs}, or {@link C#INDEX_UNSET} if - * there are no synchronization samples in the table. - * - * @param sampleTable The sample table in which to locate a synchronization sample. - * @param timeUs A time in microseconds. - * @return The index of the synchronization sample before or at {@code timeUs}, or the index of - * the first synchronization sample if located after {@code timeUs}, or {@link C#INDEX_UNSET} - * if there are no synchronization samples in the table. - */ - private static int getSynchronizationSampleIndex(TrackSampleTable sampleTable, long timeUs) { - int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs); - if (sampleIndex == C.INDEX_UNSET) { - // Handle the case where the requested time is before the first synchronization sample. - sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); - } - return sampleIndex; - } - - /** - * Process an ftyp atom to determine whether the media is QuickTime. - * - * @param atomData The ftyp atom data. - * @return Whether the media is QuickTime. - */ - private static boolean processFtypAtom(ParsableByteArray atomData) { - atomData.setPosition(Atom.HEADER_SIZE); - int majorBrand = atomData.readInt(); - if (majorBrand == BRAND_QUICKTIME) { - return true; - } - atomData.skipBytes(4); // minor_version - while (atomData.bytesLeft() > 0) { - if (atomData.readInt() == BRAND_QUICKTIME) { - return true; - } - } - return false; - } - - /** Returns whether the extractor should decode a leaf atom with type {@code atom}. */ - private static boolean shouldParseLeafAtom(int atom) { - return atom == Atom.TYPE_mdhd - || atom == Atom.TYPE_mvhd - || atom == Atom.TYPE_hdlr - || atom == Atom.TYPE_stsd - || atom == Atom.TYPE_stts - || atom == Atom.TYPE_stss - || atom == Atom.TYPE_ctts - || atom == Atom.TYPE_elst - || atom == Atom.TYPE_stsc - || atom == Atom.TYPE_stsz - || atom == Atom.TYPE_stz2 - || atom == Atom.TYPE_stco - || atom == Atom.TYPE_co64 - || atom == Atom.TYPE_tkhd - || atom == Atom.TYPE_ftyp - || atom == Atom.TYPE_udta - || atom == Atom.TYPE_keys - || atom == Atom.TYPE_ilst; - } - - /** Returns whether the extractor should decode a container atom with type {@code atom}. */ - private static boolean shouldParseContainerAtom(int atom) { - return atom == Atom.TYPE_moov - || atom == Atom.TYPE_trak - || atom == Atom.TYPE_mdia - || atom == Atom.TYPE_minf - || atom == Atom.TYPE_stbl - || atom == Atom.TYPE_edts - || atom == Atom.TYPE_meta; - } - - private static final class Mp4Track { - - public final Track track; - public final TrackSampleTable sampleTable; - public final TrackOutput trackOutput; - - public int sampleIndex; - - public Mp4Track(Track track, TrackSampleTable sampleTable, TrackOutput trackOutput) { - this.track = track; - this.sampleTable = sampleTable; - this.trackOutput = trackOutput; - } - - } - -} |