commit
ad3348cc69
@ -95,6 +95,8 @@
|
||||
* MP4: Parse bitrates from `esds` boxes.
|
||||
* Ogg: Allow duplicate Opus ID and comment headers
|
||||
([#10038](https://github.com/google/ExoPlayer/issues/10038)).
|
||||
* Add support for AVI
|
||||
([#2092](https://github.com/google/ExoPlayer/issues/2092)).
|
||||
* UI:
|
||||
* Fix delivery of events to `OnClickListener`s set on `PlayerView` and
|
||||
`LegacyPlayerView`, in the case that `useController=false`
|
||||
|
@ -44,7 +44,7 @@ public final class FileTypes {
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG,
|
||||
MIDI
|
||||
MIDI, AVI
|
||||
})
|
||||
public @interface Type {}
|
||||
/** Unknown file type. */
|
||||
@ -81,6 +81,8 @@ public final class FileTypes {
|
||||
public static final int JPEG = 14;
|
||||
/** File type for the MIDI format. */
|
||||
public static final int MIDI = 15;
|
||||
/** File type for the AVI format. */
|
||||
public static final int AVI = 16;
|
||||
|
||||
@VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||
|
||||
@ -116,6 +118,7 @@ public final class FileTypes {
|
||||
private static final String EXTENSION_WEBVTT = ".webvtt";
|
||||
private static final String EXTENSION_JPG = ".jpg";
|
||||
private static final String EXTENSION_JPEG = ".jpeg";
|
||||
private static final String EXTENSION_AVI = ".avi";
|
||||
|
||||
private FileTypes() {}
|
||||
|
||||
@ -179,6 +182,8 @@ public final class FileTypes {
|
||||
return FileTypes.WEBVTT;
|
||||
case MimeTypes.IMAGE_JPEG:
|
||||
return FileTypes.JPEG;
|
||||
case MimeTypes.VIDEO_AVI:
|
||||
return FileTypes.AVI;
|
||||
default:
|
||||
return FileTypes.UNKNOWN;
|
||||
}
|
||||
@ -244,6 +249,8 @@ public final class FileTypes {
|
||||
return FileTypes.WEBVTT;
|
||||
} else if (filename.endsWith(EXTENSION_JPG) || filename.endsWith(EXTENSION_JPEG)) {
|
||||
return FileTypes.JPEG;
|
||||
} else if (filename.endsWith(EXTENSION_AVI)) {
|
||||
return FileTypes.AVI;
|
||||
} else {
|
||||
return FileTypes.UNKNOWN;
|
||||
}
|
||||
|
@ -56,6 +56,10 @@ public final class MimeTypes {
|
||||
@UnstableApi public static final String VIDEO_FLV = BASE_TYPE_VIDEO + "/x-flv";
|
||||
public static final String VIDEO_DOLBY_VISION = BASE_TYPE_VIDEO + "/dolby-vision";
|
||||
public static final String VIDEO_OGG = BASE_TYPE_VIDEO + "/ogg";
|
||||
public static final String VIDEO_AVI = BASE_TYPE_VIDEO + "/x-msvideo";
|
||||
public static final String VIDEO_MJPEG = BASE_TYPE_VIDEO + "/mjpeg";
|
||||
public static final String VIDEO_MP42 = BASE_TYPE_VIDEO + "/mp42";
|
||||
public static final String VIDEO_MP43 = BASE_TYPE_VIDEO + "/mp43";
|
||||
@UnstableApi public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown";
|
||||
|
||||
// audio/ MIME types
|
||||
|
@ -27,6 +27,7 @@ import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.TimestampAdjuster;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.extractor.amr.AmrExtractor;
|
||||
import androidx.media3.extractor.avi.AviExtractor;
|
||||
import androidx.media3.extractor.flac.FlacExtractor;
|
||||
import androidx.media3.extractor.flv.FlvExtractor;
|
||||
import androidx.media3.extractor.jpeg.JpegExtractor;
|
||||
@ -103,8 +104,11 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
FileTypes.AC3,
|
||||
FileTypes.AC4,
|
||||
FileTypes.MP3,
|
||||
FileTypes.JPEG,
|
||||
// The following extractors are not part of the optimized ordering, and were appended
|
||||
// without further analysis.
|
||||
FileTypes.AVI,
|
||||
FileTypes.MIDI,
|
||||
FileTypes.JPEG,
|
||||
};
|
||||
|
||||
private static final ExtensionLoader FLAC_EXTENSION_LOADER =
|
||||
@ -309,7 +313,8 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
@Override
|
||||
public synchronized Extractor[] createExtractors(
|
||||
Uri uri, Map<String, List<String>> responseHeaders) {
|
||||
List<Extractor> extractors = new ArrayList<>(/* initialCapacity= */ 14);
|
||||
List<Extractor> extractors =
|
||||
new ArrayList<>(/* initialCapacity= */ DEFAULT_EXTRACTOR_ORDER.length);
|
||||
|
||||
@FileTypes.Type
|
||||
int responseHeadersInferredFileType = inferFileTypeFromResponseHeaders(responseHeaders);
|
||||
@ -412,6 +417,9 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
extractors.add(midiExtractor);
|
||||
}
|
||||
break;
|
||||
case FileTypes.AVI:
|
||||
extractors.add(new AviExtractor());
|
||||
break;
|
||||
case FileTypes.WEBVTT:
|
||||
case FileTypes.UNKNOWN:
|
||||
default:
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2022 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 androidx.media3.extractor.avi;
|
||||
|
||||
/**
|
||||
* A chunk, as defined in the AVI spec.
|
||||
*
|
||||
* <p>See https://docs.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference.
|
||||
*/
|
||||
/* package */ interface AviChunk {
|
||||
|
||||
/** Returns the chunk type fourcc. */
|
||||
int getType();
|
||||
}
|
@ -0,0 +1,557 @@
|
||||
/*
|
||||
* Copyright 2022 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 androidx.media3.extractor.avi;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.ParserException;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.ParsableByteArray;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.extractor.DummyExtractorOutput;
|
||||
import androidx.media3.extractor.Extractor;
|
||||
import androidx.media3.extractor.ExtractorInput;
|
||||
import androidx.media3.extractor.ExtractorOutput;
|
||||
import androidx.media3.extractor.PositionHolder;
|
||||
import androidx.media3.extractor.SeekMap;
|
||||
import androidx.media3.extractor.TrackOutput;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* Extracts data from the AVI container format.
|
||||
*
|
||||
* <p>Spec: https://docs.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference.
|
||||
*/
|
||||
@UnstableApi
|
||||
public final class AviExtractor implements Extractor {
|
||||
|
||||
private static final String TAG = "AviExtractor";
|
||||
|
||||
public static final int FOURCC_RIFF = 0x46464952;
|
||||
public static final int FOURCC_AVI_ = 0x20495641; // AVI<space>
|
||||
public static final int FOURCC_LIST = 0x5453494c;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int FOURCC_avih = 0x68697661;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int FOURCC_hdrl = 0x6c726468;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int FOURCC_strl = 0x6c727473;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int FOURCC_movi = 0x69766f6d;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int FOURCC_idx1 = 0x31786469;
|
||||
|
||||
public static final int FOURCC_JUNK = 0x4b4e554a;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int FOURCC_strf = 0x66727473;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int FOURCC_strn = 0x6e727473;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int FOURCC_strh = 0x68727473;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int FOURCC_auds = 0x73647561;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int FOURCC_txts = 0x73747874;
|
||||
|
||||
@SuppressWarnings("ConstantCaseForConstants")
|
||||
public static final int FOURCC_vids = 0x73646976;
|
||||
|
||||
/** Parser states. */
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
STATE_SKIPPING_TO_HDRL,
|
||||
STATE_READING_HDRL_HEADER,
|
||||
STATE_READING_HDRL_BODY,
|
||||
STATE_FINDING_MOVI_HEADER,
|
||||
STATE_FINDING_IDX1_HEADER,
|
||||
STATE_READING_IDX1_BODY,
|
||||
STATE_READING_SAMPLES,
|
||||
})
|
||||
private @interface State {}
|
||||
|
||||
private static final int STATE_SKIPPING_TO_HDRL = 0;
|
||||
private static final int STATE_READING_HDRL_HEADER = 1;
|
||||
private static final int STATE_READING_HDRL_BODY = 2;
|
||||
private static final int STATE_FINDING_MOVI_HEADER = 3;
|
||||
private static final int STATE_FINDING_IDX1_HEADER = 4;
|
||||
private static final int STATE_READING_IDX1_BODY = 5;
|
||||
private static final int STATE_READING_SAMPLES = 6;
|
||||
|
||||
private static final int AVIIF_KEYFRAME = 16;
|
||||
|
||||
/**
|
||||
* Maximum size to skip using {@link ExtractorInput#skip}. Boxes larger than this size are skipped
|
||||
* using {@link #RESULT_SEEK}.
|
||||
*/
|
||||
private static final long RELOAD_MINIMUM_SEEK_DISTANCE = 256 * 1024;
|
||||
|
||||
private final ParsableByteArray scratch;
|
||||
private final ChunkHeaderHolder chunkHeaderHolder;
|
||||
|
||||
private @State int state;
|
||||
private ExtractorOutput extractorOutput;
|
||||
private @MonotonicNonNull AviMainHeaderChunk aviHeader;
|
||||
private long durationUs;
|
||||
private ChunkReader[] chunkReaders;
|
||||
|
||||
private long pendingReposition;
|
||||
@Nullable private ChunkReader currentChunkReader;
|
||||
private int hdrlSize;
|
||||
private long moviStart;
|
||||
private long moviEnd;
|
||||
private int idx1BodySize;
|
||||
private boolean seekMapHasBeenOutput;
|
||||
|
||||
public AviExtractor() {
|
||||
scratch = new ParsableByteArray(/* limit= */ 12);
|
||||
chunkHeaderHolder = new ChunkHeaderHolder();
|
||||
extractorOutput = new DummyExtractorOutput();
|
||||
chunkReaders = new ChunkReader[0];
|
||||
moviStart = C.POSITION_UNSET;
|
||||
moviEnd = C.POSITION_UNSET;
|
||||
hdrlSize = C.LENGTH_UNSET;
|
||||
durationUs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
// Extractor implementation.
|
||||
|
||||
@Override
|
||||
public void init(ExtractorOutput output) {
|
||||
this.state = STATE_SKIPPING_TO_HDRL;
|
||||
this.extractorOutput = output;
|
||||
pendingReposition = C.POSITION_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sniff(ExtractorInput input) throws IOException {
|
||||
input.peekFully(scratch.getData(), /* offset= */ 0, /* length= */ 12);
|
||||
scratch.setPosition(0);
|
||||
if (scratch.readLittleEndianInt() != FOURCC_RIFF) {
|
||||
return false;
|
||||
}
|
||||
scratch.skipBytes(4); // Skip the RIFF chunk length.
|
||||
return scratch.readLittleEndianInt() == FOURCC_AVI_;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ExtractorInput input, PositionHolder positionHolder) throws IOException {
|
||||
if (resolvePendingReposition(input, positionHolder)) {
|
||||
return RESULT_SEEK;
|
||||
}
|
||||
switch (state) {
|
||||
case STATE_SKIPPING_TO_HDRL:
|
||||
// Check for RIFF and AVI fourcc's just in case the caller did not sniff, in order to
|
||||
// provide a meaningful error if the input is not an AVI file.
|
||||
if (sniff(input)) {
|
||||
input.skipFully(/* length= */ 12);
|
||||
} else {
|
||||
throw ParserException.createForMalformedContainer(
|
||||
/* message= */ "AVI Header List not found", /* cause= */ null);
|
||||
}
|
||||
state = STATE_READING_HDRL_HEADER;
|
||||
return RESULT_CONTINUE;
|
||||
case STATE_READING_HDRL_HEADER:
|
||||
input.readFully(scratch.getData(), /* offset= */ 0, /* length= */ 12);
|
||||
scratch.setPosition(0);
|
||||
chunkHeaderHolder.populateWithListHeaderFrom(scratch);
|
||||
if (chunkHeaderHolder.listType != FOURCC_hdrl) {
|
||||
throw ParserException.createForMalformedContainer(
|
||||
/* message= */ "hdrl expected, found: " + chunkHeaderHolder.listType,
|
||||
/* cause= */ null);
|
||||
}
|
||||
hdrlSize = chunkHeaderHolder.size;
|
||||
state = STATE_READING_HDRL_BODY;
|
||||
return RESULT_CONTINUE;
|
||||
case STATE_READING_HDRL_BODY:
|
||||
// hdrlSize includes the LIST type (hdrl), so we subtract 4 to the size.
|
||||
int bytesToRead = hdrlSize - 4;
|
||||
ParsableByteArray hdrlBody = new ParsableByteArray(bytesToRead);
|
||||
input.readFully(hdrlBody.getData(), /* offset= */ 0, bytesToRead);
|
||||
parseHdrlBody(hdrlBody);
|
||||
state = STATE_FINDING_MOVI_HEADER;
|
||||
return RESULT_CONTINUE;
|
||||
case STATE_FINDING_MOVI_HEADER:
|
||||
if (moviStart != C.POSITION_UNSET && input.getPosition() != moviStart) {
|
||||
pendingReposition = moviStart;
|
||||
return RESULT_CONTINUE;
|
||||
}
|
||||
input.peekFully(scratch.getData(), /* offset= */ 0, /* length= */ 12);
|
||||
input.resetPeekPosition();
|
||||
scratch.setPosition(0);
|
||||
chunkHeaderHolder.populateFrom(scratch);
|
||||
int listType = scratch.readLittleEndianInt();
|
||||
if (chunkHeaderHolder.chunkType == FOURCC_RIFF) {
|
||||
// We are at the start of the file. The movi chunk is in the RIFF chunk, so we skip the
|
||||
// header, so as to read the RIFF chunk's body.
|
||||
input.skipFully(12);
|
||||
return RESULT_CONTINUE;
|
||||
}
|
||||
if (chunkHeaderHolder.chunkType != FOURCC_LIST || listType != FOURCC_movi) {
|
||||
// The chunk header (8 bytes) plus the whole body.
|
||||
pendingReposition = input.getPosition() + chunkHeaderHolder.size + 8;
|
||||
return RESULT_CONTINUE;
|
||||
}
|
||||
moviStart = input.getPosition();
|
||||
// Size includes the list type, but not the LIST or size fields, so we add 8.
|
||||
moviEnd = moviStart + chunkHeaderHolder.size + 8;
|
||||
if (!seekMapHasBeenOutput) {
|
||||
if (Assertions.checkNotNull(aviHeader).hasIndex()) {
|
||||
state = STATE_FINDING_IDX1_HEADER;
|
||||
pendingReposition = moviEnd;
|
||||
return RESULT_CONTINUE;
|
||||
} else {
|
||||
extractorOutput.seekMap(new SeekMap.Unseekable(durationUs));
|
||||
seekMapHasBeenOutput = true;
|
||||
}
|
||||
}
|
||||
// No need to parse the idx1, so we start reading the samples from the movi chunk straight
|
||||
// away. We skip 12 bytes to move to the start of the movi's body.
|
||||
pendingReposition = input.getPosition() + 12;
|
||||
state = STATE_READING_SAMPLES;
|
||||
return RESULT_CONTINUE;
|
||||
case STATE_FINDING_IDX1_HEADER:
|
||||
input.readFully(scratch.getData(), /* offset= */ 0, /* length= */ 8);
|
||||
scratch.setPosition(0);
|
||||
int idx1Fourcc = scratch.readLittleEndianInt();
|
||||
int boxSize = scratch.readLittleEndianInt();
|
||||
if (idx1Fourcc == FOURCC_idx1) {
|
||||
state = STATE_READING_IDX1_BODY;
|
||||
idx1BodySize = boxSize;
|
||||
} else {
|
||||
// This one is not idx1, skip to the next box.
|
||||
pendingReposition = input.getPosition() + boxSize;
|
||||
}
|
||||
return RESULT_CONTINUE;
|
||||
case STATE_READING_IDX1_BODY:
|
||||
ParsableByteArray idx1Body = new ParsableByteArray(idx1BodySize);
|
||||
input.readFully(idx1Body.getData(), /* offset= */ 0, /* length= */ idx1BodySize);
|
||||
parseIdx1Body(idx1Body);
|
||||
state = STATE_READING_SAMPLES;
|
||||
pendingReposition = moviStart;
|
||||
return RESULT_CONTINUE;
|
||||
case STATE_READING_SAMPLES:
|
||||
return readMoviChunks(input);
|
||||
default:
|
||||
throw new AssertionError(); // Should never happen.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position, long timeUs) {
|
||||
pendingReposition = C.POSITION_UNSET;
|
||||
currentChunkReader = null;
|
||||
for (ChunkReader chunkReader : chunkReaders) {
|
||||
chunkReader.seekToPosition(position);
|
||||
}
|
||||
if (position == 0) {
|
||||
if (chunkReaders.length == 0) {
|
||||
// Still unprepared.
|
||||
state = STATE_SKIPPING_TO_HDRL;
|
||||
} else {
|
||||
state = STATE_FINDING_MOVI_HEADER;
|
||||
}
|
||||
return;
|
||||
}
|
||||
state = STATE_READING_SAMPLES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
// Nothing to release.
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
/**
|
||||
* Returns whether a {@link #RESULT_SEEK} is required for the pending reposition. A seek may not
|
||||
* be necessary when the desired position (as held by {@link #pendingReposition}) is after the
|
||||
* {@link ExtractorInput#getPosition() current position}, but not further than {@link
|
||||
* #RELOAD_MINIMUM_SEEK_DISTANCE}.
|
||||
*/
|
||||
private boolean resolvePendingReposition(ExtractorInput input, PositionHolder positionHolder)
|
||||
throws IOException {
|
||||
boolean needSeek = false;
|
||||
if (pendingReposition != C.POSITION_UNSET) {
|
||||
long currentPosition = input.getPosition();
|
||||
if (pendingReposition < currentPosition
|
||||
|| pendingReposition > currentPosition + RELOAD_MINIMUM_SEEK_DISTANCE) {
|
||||
positionHolder.position = pendingReposition;
|
||||
needSeek = true;
|
||||
} else {
|
||||
// The distance to the target position is short enough that it makes sense to just skip the
|
||||
// bytes, instead of doing a seek which might re-create an HTTP connection.
|
||||
input.skipFully((int) (pendingReposition - currentPosition));
|
||||
}
|
||||
}
|
||||
pendingReposition = C.POSITION_UNSET;
|
||||
return needSeek;
|
||||
}
|
||||
|
||||
private void parseHdrlBody(ParsableByteArray hrdlBody) throws IOException {
|
||||
ListChunk headerList = ListChunk.parseFrom(FOURCC_hdrl, hrdlBody);
|
||||
if (headerList.getType() != FOURCC_hdrl) {
|
||||
throw ParserException.createForMalformedContainer(
|
||||
/* message= */ "Unexpected header list type " + headerList.getType(), /* cause= */ null);
|
||||
}
|
||||
@Nullable AviMainHeaderChunk aviHeader = headerList.getChild(AviMainHeaderChunk.class);
|
||||
if (aviHeader == null) {
|
||||
throw ParserException.createForMalformedContainer(
|
||||
/* message= */ "AviHeader not found", /* cause= */ null);
|
||||
}
|
||||
this.aviHeader = aviHeader;
|
||||
// This is usually wrong, so it will be overwritten by video if present
|
||||
durationUs = aviHeader.totalFrames * (long) aviHeader.frameDurationUs;
|
||||
ArrayList<ChunkReader> chunkReaderList = new ArrayList<>();
|
||||
int streamId = 0;
|
||||
for (AviChunk aviChunk : headerList.children) {
|
||||
if (aviChunk.getType() == FOURCC_strl) {
|
||||
ListChunk streamList = (ListChunk) aviChunk;
|
||||
// Note the streamId needs to increment even if the corresponding `strl` is discarded.
|
||||
// See
|
||||
// https://docs.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference#avi-stream-headers.
|
||||
@Nullable ChunkReader chunkReader = processStreamList(streamList, streamId++);
|
||||
if (chunkReader != null) {
|
||||
chunkReaderList.add(chunkReader);
|
||||
}
|
||||
}
|
||||
}
|
||||
chunkReaders = chunkReaderList.toArray(new ChunkReader[0]);
|
||||
extractorOutput.endTracks();
|
||||
}
|
||||
|
||||
/** Builds and outputs the {@link SeekMap} from the idx1 chunk. */
|
||||
private void parseIdx1Body(ParsableByteArray body) {
|
||||
long seekOffset = peekSeekOffset(body);
|
||||
while (body.bytesLeft() >= 16) {
|
||||
int chunkId = body.readLittleEndianInt();
|
||||
int flags = body.readLittleEndianInt();
|
||||
long offset = body.readLittleEndianInt() + seekOffset;
|
||||
body.readLittleEndianInt(); // We ignore the size.
|
||||
ChunkReader chunkReader = getChunkReader(chunkId);
|
||||
if (chunkReader == null) {
|
||||
// We ignore unknown chunk IDs.
|
||||
continue;
|
||||
}
|
||||
if ((flags & AVIIF_KEYFRAME) == AVIIF_KEYFRAME) {
|
||||
chunkReader.appendKeyFrameToIndex(offset);
|
||||
}
|
||||
chunkReader.incrementIndexChunkCount();
|
||||
}
|
||||
for (ChunkReader chunkReader : chunkReaders) {
|
||||
chunkReader.compactIndex();
|
||||
}
|
||||
seekMapHasBeenOutput = true;
|
||||
extractorOutput.seekMap(new AviSeekMap(durationUs));
|
||||
}
|
||||
|
||||
private long peekSeekOffset(ParsableByteArray idx1Body) {
|
||||
// The spec states the offset is based on the start of the movi list type fourcc, but it also
|
||||
// says some files base the offset on the start of the file. We use a best effort approach to
|
||||
// figure out which is the case. See:
|
||||
// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/api/Aviriff/ns-aviriff-avioldindex#dwoffset.
|
||||
if (idx1Body.bytesLeft() < 16) {
|
||||
// There are no full entries in the index, meaning we don't need to apply an offset.
|
||||
return 0;
|
||||
}
|
||||
int startingPosition = idx1Body.getPosition();
|
||||
idx1Body.skipBytes(8); // Skip chunkId (4 bytes) and flags (4 bytes).
|
||||
int offset = idx1Body.readLittleEndianInt();
|
||||
|
||||
// moviStart poitns at the start of the LIST, while the seek offset is based at the start of the
|
||||
// movi fourCC, so we add 8 to reconcile the difference.
|
||||
long seekOffset = offset > moviStart ? 0L : moviStart + 8;
|
||||
idx1Body.setPosition(startingPosition);
|
||||
return seekOffset;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ChunkReader getChunkReader(int chunkId) {
|
||||
for (ChunkReader chunkReader : chunkReaders) {
|
||||
if (chunkReader.handlesChunkId(chunkId)) {
|
||||
return chunkReader;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int readMoviChunks(ExtractorInput input) throws IOException {
|
||||
if (input.getPosition() >= moviEnd) {
|
||||
return C.RESULT_END_OF_INPUT;
|
||||
} else if (currentChunkReader != null) {
|
||||
if (currentChunkReader.onChunkData(input)) {
|
||||
currentChunkReader = null;
|
||||
}
|
||||
} else {
|
||||
alignInputToEvenPosition(input);
|
||||
input.peekFully(scratch.getData(), /* offset= */ 0, 12);
|
||||
scratch.setPosition(0);
|
||||
int chunkType = scratch.readLittleEndianInt();
|
||||
if (chunkType == FOURCC_LIST) {
|
||||
scratch.setPosition(8);
|
||||
int listType = scratch.readLittleEndianInt();
|
||||
input.skipFully(listType == FOURCC_movi ? 12 : 8);
|
||||
input.resetPeekPosition();
|
||||
return RESULT_CONTINUE;
|
||||
}
|
||||
int size = scratch.readLittleEndianInt();
|
||||
if (chunkType == FOURCC_JUNK) {
|
||||
pendingReposition = input.getPosition() + size + 8;
|
||||
return RESULT_CONTINUE;
|
||||
}
|
||||
input.skipFully(8);
|
||||
input.resetPeekPosition();
|
||||
ChunkReader chunkReader = getChunkReader(chunkType);
|
||||
if (chunkReader == null) {
|
||||
// No handler for this chunk. We skip it.
|
||||
pendingReposition = input.getPosition() + size;
|
||||
return RESULT_CONTINUE;
|
||||
} else {
|
||||
chunkReader.onChunkStart(size);
|
||||
this.currentChunkReader = chunkReader;
|
||||
}
|
||||
}
|
||||
return RESULT_CONTINUE;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ChunkReader processStreamList(ListChunk streamList, int streamId) {
|
||||
AviStreamHeaderChunk aviStreamHeaderChunk = streamList.getChild(AviStreamHeaderChunk.class);
|
||||
StreamFormatChunk streamFormatChunk = streamList.getChild(StreamFormatChunk.class);
|
||||
if (aviStreamHeaderChunk == null) {
|
||||
Log.w(TAG, "Missing Stream Header");
|
||||
return null;
|
||||
}
|
||||
if (streamFormatChunk == null) {
|
||||
Log.w(TAG, "Missing Stream Format");
|
||||
return null;
|
||||
}
|
||||
long durationUs = aviStreamHeaderChunk.getDurationUs();
|
||||
Format streamFormat = streamFormatChunk.format;
|
||||
Format.Builder builder = streamFormat.buildUpon();
|
||||
builder.setId(streamId);
|
||||
int suggestedBufferSize = aviStreamHeaderChunk.suggestedBufferSize;
|
||||
if (suggestedBufferSize != 0) {
|
||||
builder.setMaxInputSize(suggestedBufferSize);
|
||||
}
|
||||
StreamNameChunk streamName = streamList.getChild(StreamNameChunk.class);
|
||||
if (streamName != null) {
|
||||
builder.setLabel(streamName.name);
|
||||
}
|
||||
int trackType = MimeTypes.getTrackType(streamFormat.sampleMimeType);
|
||||
if (trackType == C.TRACK_TYPE_AUDIO || trackType == C.TRACK_TYPE_VIDEO) {
|
||||
TrackOutput trackOutput = extractorOutput.track(streamId, trackType);
|
||||
trackOutput.format(builder.build());
|
||||
ChunkReader chunkReader =
|
||||
new ChunkReader(
|
||||
streamId, trackType, durationUs, aviStreamHeaderChunk.length, trackOutput);
|
||||
this.durationUs = durationUs;
|
||||
return chunkReader;
|
||||
} else {
|
||||
// We don't currently support tracks other than video and audio.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips one byte from the given {@code input} if the current position is odd.
|
||||
*
|
||||
* <p>This isn't documented anywhere, but AVI files are aligned to even bytes and fill gaps with
|
||||
* zeros.
|
||||
*/
|
||||
private static void alignInputToEvenPosition(ExtractorInput input) throws IOException {
|
||||
if ((input.getPosition() & 1) == 1) {
|
||||
input.skipFully(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal classes.
|
||||
|
||||
private class AviSeekMap implements SeekMap {
|
||||
|
||||
private final long durationUs;
|
||||
|
||||
public AviSeekMap(long durationUs) {
|
||||
this.durationUs = durationUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSeekable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDurationUs() {
|
||||
return durationUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
SeekPoints result = chunkReaders[0].getSeekPoints(timeUs);
|
||||
for (int i = 1; i < chunkReaders.length; i++) {
|
||||
SeekPoints seekPoints = chunkReaders[i].getSeekPoints(timeUs);
|
||||
if (seekPoints.first.position < result.first.position) {
|
||||
result = seekPoints;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ChunkHeaderHolder {
|
||||
public int chunkType;
|
||||
public int size;
|
||||
public int listType;
|
||||
|
||||
public void populateWithListHeaderFrom(ParsableByteArray headerBytes) throws ParserException {
|
||||
populateFrom(headerBytes);
|
||||
if (chunkType != AviExtractor.FOURCC_LIST) {
|
||||
throw ParserException.createForMalformedContainer(
|
||||
/* message= */ "LIST expected, found: " + chunkType, /* cause= */ null);
|
||||
}
|
||||
listType = headerBytes.readLittleEndianInt();
|
||||
}
|
||||
|
||||
public void populateFrom(ParsableByteArray headerBytes) {
|
||||
chunkType = headerBytes.readLittleEndianInt();
|
||||
size = headerBytes.readLittleEndianInt();
|
||||
listType = 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2022 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 androidx.media3.extractor.avi;
|
||||
|
||||
import androidx.media3.common.util.ParsableByteArray;
|
||||
|
||||
/** Wrapper around the AVIMAINHEADER structure */
|
||||
/* package */ final class AviMainHeaderChunk implements AviChunk {
|
||||
|
||||
private static final int AVIF_HAS_INDEX = 0x10;
|
||||
|
||||
public static AviMainHeaderChunk parseFrom(ParsableByteArray body) {
|
||||
int microSecPerFrame = body.readLittleEndianInt();
|
||||
body.skipBytes(8); // Skip dwMaxBytesPerSec (4 bytes), dwPaddingGranularity (4 bytes).
|
||||
int flags = body.readLittleEndianInt();
|
||||
int totalFrames = body.readLittleEndianInt();
|
||||
body.skipBytes(4); // dwInitialFrames (4 bytes).
|
||||
int streams = body.readLittleEndianInt();
|
||||
body.skipBytes(12); // dwSuggestedBufferSize (4 bytes), dwWidth (4 bytes), dwHeight (4 bytes).
|
||||
return new AviMainHeaderChunk(microSecPerFrame, flags, totalFrames, streams);
|
||||
}
|
||||
|
||||
public final int frameDurationUs;
|
||||
public final int flags;
|
||||
public final int totalFrames;
|
||||
public final int streams;
|
||||
|
||||
private AviMainHeaderChunk(int frameDurationUs, int flags, int totalFrames, int streams) {
|
||||
this.frameDurationUs = frameDurationUs;
|
||||
this.flags = flags;
|
||||
this.totalFrames = totalFrames;
|
||||
this.streams = streams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return AviExtractor.FOURCC_avih;
|
||||
}
|
||||
|
||||
public boolean hasIndex() {
|
||||
return (flags & AVIF_HAS_INDEX) == AVIF_HAS_INDEX;
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2022 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 androidx.media3.extractor.avi;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.ParsableByteArray;
|
||||
import androidx.media3.common.util.Util;
|
||||
|
||||
/** Parses and holds information from the AVISTREAMHEADER structure. */
|
||||
/* package */ final class AviStreamHeaderChunk implements AviChunk {
|
||||
private static final String TAG = "AviStreamHeaderChunk";
|
||||
|
||||
public static AviStreamHeaderChunk parseFrom(ParsableByteArray body) {
|
||||
int streamType = body.readLittleEndianInt();
|
||||
body.skipBytes(12); // fccHandler (4 bytes), dwFlags (4 bytes), wPriority (2 bytes),
|
||||
// wLanguage (2 bytes).
|
||||
int initialFrames = body.readLittleEndianInt();
|
||||
int scale = body.readLittleEndianInt();
|
||||
int rate = body.readLittleEndianInt();
|
||||
body.skipBytes(4); // dwStart (4 bytes).
|
||||
int length = body.readLittleEndianInt();
|
||||
int suggestedBufferSize = body.readLittleEndianInt();
|
||||
body.skipBytes(8); // dwQuality (4 bytes), dwSampleSize (4 bytes).
|
||||
return new AviStreamHeaderChunk(
|
||||
streamType, initialFrames, scale, rate, length, suggestedBufferSize);
|
||||
}
|
||||
|
||||
public final int streamType;
|
||||
public final int initialFrames;
|
||||
public final int scale;
|
||||
public final int rate;
|
||||
public final int length;
|
||||
public final int suggestedBufferSize;
|
||||
|
||||
private AviStreamHeaderChunk(
|
||||
int streamType, int initialFrames, int scale, int rate, int length, int suggestedBufferSize) {
|
||||
this.streamType = streamType;
|
||||
this.initialFrames = initialFrames;
|
||||
this.scale = scale;
|
||||
this.rate = rate;
|
||||
this.length = length;
|
||||
this.suggestedBufferSize = suggestedBufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return AviExtractor.FOURCC_strh;
|
||||
}
|
||||
|
||||
public @C.TrackType int getTrackType() {
|
||||
switch (streamType) {
|
||||
case AviExtractor.FOURCC_auds:
|
||||
return C.TRACK_TYPE_AUDIO;
|
||||
case AviExtractor.FOURCC_vids:
|
||||
return C.TRACK_TYPE_VIDEO;
|
||||
case AviExtractor.FOURCC_txts:
|
||||
return C.TRACK_TYPE_TEXT;
|
||||
default:
|
||||
Log.w(TAG, "Found unsupported streamType fourCC: " + Integer.toHexString(streamType));
|
||||
return C.TRACK_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public float getFrameRate() {
|
||||
return rate / (float) scale;
|
||||
}
|
||||
|
||||
public long getDurationUs() {
|
||||
return Util.scaleLargeTimestamp(
|
||||
/* timestamp= */ length,
|
||||
/* multiplier= */ C.MICROS_PER_SECOND * scale,
|
||||
/* divisor= */ rate);
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright 2022 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 androidx.media3.extractor.avi;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.extractor.ExtractorInput;
|
||||
import androidx.media3.extractor.SeekMap;
|
||||
import androidx.media3.extractor.SeekPoint;
|
||||
import androidx.media3.extractor.TrackOutput;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Reads chunks holding sample data. */
|
||||
/* package */ final class ChunkReader {
|
||||
|
||||
/** Parser states. */
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
CHUNK_TYPE_VIDEO_COMPRESSED,
|
||||
CHUNK_TYPE_VIDEO_UNCOMPRESSED,
|
||||
CHUNK_TYPE_AUDIO,
|
||||
})
|
||||
private @interface ChunkType {}
|
||||
|
||||
private static final int INITIAL_INDEX_SIZE = 512;
|
||||
private static final int CHUNK_TYPE_VIDEO_COMPRESSED = ('d' << 16) | ('c' << 24);
|
||||
private static final int CHUNK_TYPE_VIDEO_UNCOMPRESSED = ('d' << 16) | ('b' << 24);
|
||||
private static final int CHUNK_TYPE_AUDIO = ('w' << 16) | ('b' << 24);
|
||||
|
||||
protected final TrackOutput trackOutput;
|
||||
|
||||
/** The chunk id fourCC (example: `01wb`), as defined in the index and the movi. */
|
||||
private final int chunkId;
|
||||
/** Secondary chunk id. Bad muxers sometimes use an uncompressed video id (db) for key frames */
|
||||
private final int alternativeChunkId;
|
||||
|
||||
private final long durationUs;
|
||||
private final int streamHeaderChunkCount;
|
||||
|
||||
private int currentChunkSize;
|
||||
private int bytesRemainingInCurrentChunk;
|
||||
|
||||
/** Number of chunks as calculated by the index */
|
||||
private int currentChunkIndex;
|
||||
|
||||
private int indexChunkCount;
|
||||
private int indexSize;
|
||||
private long[] keyFrameOffsets;
|
||||
private int[] keyFrameIndices;
|
||||
|
||||
public ChunkReader(
|
||||
int id,
|
||||
@C.TrackType int trackType,
|
||||
long durationnUs,
|
||||
int streamHeaderChunkCount,
|
||||
TrackOutput trackOutput) {
|
||||
Assertions.checkArgument(trackType == C.TRACK_TYPE_AUDIO || trackType == C.TRACK_TYPE_VIDEO);
|
||||
this.durationUs = durationnUs;
|
||||
this.streamHeaderChunkCount = streamHeaderChunkCount;
|
||||
this.trackOutput = trackOutput;
|
||||
@ChunkType
|
||||
int chunkType =
|
||||
trackType == C.TRACK_TYPE_VIDEO ? CHUNK_TYPE_VIDEO_COMPRESSED : CHUNK_TYPE_AUDIO;
|
||||
chunkId = getChunkIdFourCc(id, chunkType);
|
||||
alternativeChunkId =
|
||||
trackType == C.TRACK_TYPE_VIDEO ? getChunkIdFourCc(id, CHUNK_TYPE_VIDEO_UNCOMPRESSED) : -1;
|
||||
keyFrameOffsets = new long[INITIAL_INDEX_SIZE];
|
||||
keyFrameIndices = new int[INITIAL_INDEX_SIZE];
|
||||
}
|
||||
|
||||
public void appendKeyFrameToIndex(long offset) {
|
||||
if (indexSize == keyFrameIndices.length) {
|
||||
keyFrameOffsets = Arrays.copyOf(keyFrameOffsets, keyFrameOffsets.length * 3 / 2);
|
||||
keyFrameIndices = Arrays.copyOf(keyFrameIndices, keyFrameIndices.length * 3 / 2);
|
||||
}
|
||||
keyFrameOffsets[indexSize] = offset;
|
||||
keyFrameIndices[indexSize] = indexChunkCount;
|
||||
indexSize++;
|
||||
}
|
||||
|
||||
public void advanceCurrentChunk() {
|
||||
currentChunkIndex++;
|
||||
}
|
||||
|
||||
public long getCurrentChunkTimestampUs() {
|
||||
return getChunkTimestampUs(currentChunkIndex);
|
||||
}
|
||||
|
||||
public long getFrameDurationUs() {
|
||||
return getChunkTimestampUs(/* chunkIndex= */ 1);
|
||||
}
|
||||
|
||||
public void incrementIndexChunkCount() {
|
||||
indexChunkCount++;
|
||||
}
|
||||
|
||||
public void compactIndex() {
|
||||
keyFrameOffsets = Arrays.copyOf(keyFrameOffsets, indexSize);
|
||||
keyFrameIndices = Arrays.copyOf(keyFrameIndices, indexSize);
|
||||
}
|
||||
|
||||
public boolean handlesChunkId(int chunkId) {
|
||||
return this.chunkId == chunkId || alternativeChunkId == chunkId;
|
||||
}
|
||||
|
||||
public boolean isCurrentFrameAKeyFrame() {
|
||||
return Arrays.binarySearch(keyFrameIndices, currentChunkIndex) >= 0;
|
||||
}
|
||||
|
||||
public boolean isVideo() {
|
||||
return (chunkId & CHUNK_TYPE_VIDEO_COMPRESSED) == CHUNK_TYPE_VIDEO_COMPRESSED;
|
||||
}
|
||||
|
||||
public boolean isAudio() {
|
||||
return (chunkId & CHUNK_TYPE_AUDIO) == CHUNK_TYPE_AUDIO;
|
||||
}
|
||||
|
||||
/** Prepares for parsing a chunk with the given {@code size}. */
|
||||
public void onChunkStart(int size) {
|
||||
currentChunkSize = size;
|
||||
bytesRemainingInCurrentChunk = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data associated to the current chunk and returns whether the full chunk has been
|
||||
* parsed.
|
||||
*/
|
||||
public boolean onChunkData(ExtractorInput input) throws IOException {
|
||||
bytesRemainingInCurrentChunk -=
|
||||
trackOutput.sampleData(input, bytesRemainingInCurrentChunk, false);
|
||||
boolean done = bytesRemainingInCurrentChunk == 0;
|
||||
if (done) {
|
||||
if (currentChunkSize > 0) {
|
||||
trackOutput.sampleMetadata(
|
||||
getCurrentChunkTimestampUs(),
|
||||
(isCurrentFrameAKeyFrame() ? C.BUFFER_FLAG_KEY_FRAME : 0),
|
||||
currentChunkSize,
|
||||
0,
|
||||
null);
|
||||
}
|
||||
advanceCurrentChunk();
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
public void seekToPosition(long position) {
|
||||
if (indexSize == 0) {
|
||||
currentChunkIndex = 0;
|
||||
} else {
|
||||
int index =
|
||||
Util.binarySearchFloor(
|
||||
keyFrameOffsets, position, /* inclusive= */ true, /* stayInBounds= */ true);
|
||||
currentChunkIndex = keyFrameIndices[index];
|
||||
}
|
||||
}
|
||||
|
||||
public SeekMap.SeekPoints getSeekPoints(long timeUs) {
|
||||
int targetFrameIndex = (int) (timeUs / getFrameDurationUs());
|
||||
int keyFrameIndex =
|
||||
Util.binarySearchFloor(
|
||||
keyFrameIndices, targetFrameIndex, /* inclusive= */ true, /* stayInBounds= */ true);
|
||||
if (keyFrameIndices[keyFrameIndex] == targetFrameIndex) {
|
||||
return new SeekMap.SeekPoints(getSeekPoint(keyFrameIndex));
|
||||
}
|
||||
// The target frame is not a key frame, we look for the two closest ones.
|
||||
SeekPoint precedingKeyFrameSeekPoint = getSeekPoint(keyFrameIndex);
|
||||
if (keyFrameIndex + 1 < keyFrameOffsets.length) {
|
||||
return new SeekMap.SeekPoints(precedingKeyFrameSeekPoint, getSeekPoint(keyFrameIndex + 1));
|
||||
} else {
|
||||
return new SeekMap.SeekPoints(precedingKeyFrameSeekPoint);
|
||||
}
|
||||
}
|
||||
|
||||
private long getChunkTimestampUs(int chunkIndex) {
|
||||
return durationUs * chunkIndex / streamHeaderChunkCount;
|
||||
}
|
||||
|
||||
private SeekPoint getSeekPoint(int keyFrameIndex) {
|
||||
return new SeekPoint(
|
||||
keyFrameIndices[keyFrameIndex] * getFrameDurationUs(), keyFrameOffsets[keyFrameIndex]);
|
||||
}
|
||||
|
||||
private static int getChunkIdFourCc(int streamId, @ChunkType int chunkType) {
|
||||
int tens = streamId / 10;
|
||||
int ones = streamId % 10;
|
||||
return (('0' + ones) << 8) | ('0' + tens) | chunkType;
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2022 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 androidx.media3.extractor.avi;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.ParsableByteArray;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/** Represents an AVI LIST. */
|
||||
/* package */ final class ListChunk implements AviChunk {
|
||||
|
||||
public static ListChunk parseFrom(int listType, ParsableByteArray body) {
|
||||
ImmutableList.Builder<AviChunk> builder = new ImmutableList.Builder<>();
|
||||
int listBodyEndPosition = body.limit();
|
||||
@C.TrackType int currentTrackType = C.TRACK_TYPE_NONE;
|
||||
while (body.bytesLeft() > 8) {
|
||||
int type = body.readLittleEndianInt();
|
||||
int size = body.readLittleEndianInt();
|
||||
int innerBoxBodyEndPosition = body.getPosition() + size;
|
||||
body.setLimit(innerBoxBodyEndPosition);
|
||||
@Nullable AviChunk aviChunk;
|
||||
if (type == AviExtractor.FOURCC_LIST) {
|
||||
int innerListType = body.readLittleEndianInt();
|
||||
aviChunk = parseFrom(innerListType, body);
|
||||
} else {
|
||||
aviChunk = createBox(type, currentTrackType, body);
|
||||
}
|
||||
if (aviChunk != null) {
|
||||
if (aviChunk.getType() == AviExtractor.FOURCC_strh) {
|
||||
currentTrackType = ((AviStreamHeaderChunk) aviChunk).getTrackType();
|
||||
}
|
||||
builder.add(aviChunk);
|
||||
}
|
||||
body.setPosition(innerBoxBodyEndPosition);
|
||||
body.setLimit(listBodyEndPosition);
|
||||
}
|
||||
return new ListChunk(listType, builder.build());
|
||||
}
|
||||
|
||||
public final ImmutableList<AviChunk> children;
|
||||
private final int type;
|
||||
|
||||
private ListChunk(int type, ImmutableList<AviChunk> children) {
|
||||
this.type = type;
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends AviChunk> T getChild(Class<T> c) {
|
||||
for (AviChunk aviChunk : children) {
|
||||
if (aviChunk.getClass() == c) {
|
||||
return (T) aviChunk;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static AviChunk createBox(
|
||||
int chunkType, @C.TrackType int trackType, ParsableByteArray body) {
|
||||
switch (chunkType) {
|
||||
case AviExtractor.FOURCC_avih:
|
||||
return AviMainHeaderChunk.parseFrom(body);
|
||||
case AviExtractor.FOURCC_strh:
|
||||
return AviStreamHeaderChunk.parseFrom(body);
|
||||
case AviExtractor.FOURCC_strf:
|
||||
return StreamFormatChunk.parseFrom(trackType, body);
|
||||
case AviExtractor.FOURCC_strn:
|
||||
return StreamNameChunk.parseFrom(body);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2022 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 androidx.media3.extractor.avi;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.ParsableByteArray;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/** Holds the {@link Format} information contained in an STRF chunk. */
|
||||
/* package */ final class StreamFormatChunk implements AviChunk {
|
||||
private static final String TAG = "StreamFormatChunk";
|
||||
|
||||
@Nullable
|
||||
public static AviChunk parseFrom(int trackType, ParsableByteArray body) {
|
||||
if (trackType == C.TRACK_TYPE_VIDEO) {
|
||||
return parseBitmapInfoHeader(body);
|
||||
} else if (trackType == C.TRACK_TYPE_AUDIO) {
|
||||
return parseWaveFormatEx(body);
|
||||
} else {
|
||||
Log.w(
|
||||
TAG,
|
||||
"Ignoring strf box for unsupported track type: " + Util.getTrackTypeString(trackType));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public final Format format;
|
||||
|
||||
public StreamFormatChunk(Format format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return AviExtractor.FOURCC_strf;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static AviChunk parseBitmapInfoHeader(ParsableByteArray body) {
|
||||
body.skipBytes(4); // biSize.
|
||||
int width = body.readLittleEndianInt();
|
||||
int height = body.readLittleEndianInt();
|
||||
body.skipBytes(4); // biPlanes (2 bytes), biBitCount (2 bytes).
|
||||
int compression = body.readLittleEndianInt();
|
||||
String mimeType = getMimeTypeFromCompression(compression);
|
||||
if (mimeType == null) {
|
||||
Log.w(TAG, "Ignoring track with unsupported compression " + compression);
|
||||
return null;
|
||||
}
|
||||
Format.Builder formatBuilder = new Format.Builder();
|
||||
formatBuilder.setWidth(width).setHeight(height).setSampleMimeType(mimeType);
|
||||
return new StreamFormatChunk(formatBuilder.build());
|
||||
}
|
||||
|
||||
// Syntax defined by the WAVEFORMATEX structure. See
|
||||
// https://docs.microsoft.com/en-us/previous-versions/dd757713(v=vs.85).
|
||||
@Nullable
|
||||
private static AviChunk parseWaveFormatEx(ParsableByteArray body) {
|
||||
int formatTag = body.readLittleEndianUnsignedShort();
|
||||
@Nullable String mimeType = getMimeTypeFromTag(formatTag);
|
||||
if (mimeType == null) {
|
||||
Log.w(TAG, "Ignoring track with unsupported format tag " + formatTag);
|
||||
return null;
|
||||
}
|
||||
int channelCount = body.readLittleEndianUnsignedShort();
|
||||
int samplesPerSecond = body.readLittleEndianInt();
|
||||
body.skipBytes(6); // averageBytesPerSecond (4 bytes), nBlockAlign (2 bytes).
|
||||
int bitsPerSample = body.readUnsignedShort();
|
||||
int pcmEncoding = Util.getPcmEncoding(bitsPerSample);
|
||||
int cbSize = body.readLittleEndianUnsignedShort();
|
||||
byte[] codecData = new byte[cbSize];
|
||||
body.readBytes(codecData, /* offset= */ 0, codecData.length);
|
||||
|
||||
Format.Builder formatBuilder = new Format.Builder();
|
||||
formatBuilder
|
||||
.setSampleMimeType(mimeType)
|
||||
.setChannelCount(channelCount)
|
||||
.setSampleRate(samplesPerSecond);
|
||||
if (MimeTypes.AUDIO_RAW.equals(mimeType) && pcmEncoding != C.ENCODING_INVALID) {
|
||||
formatBuilder.setPcmEncoding(pcmEncoding);
|
||||
}
|
||||
if (MimeTypes.AUDIO_AAC.equals(mimeType) && codecData.length > 0) {
|
||||
formatBuilder.setInitializationData(ImmutableList.of(codecData));
|
||||
}
|
||||
return new StreamFormatChunk(formatBuilder.build());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getMimeTypeFromTag(int tag) {
|
||||
switch (tag) {
|
||||
case 0x1: // WAVE_FORMAT_PCM
|
||||
return MimeTypes.AUDIO_RAW;
|
||||
case 0x55: // WAVE_FORMAT_MPEGLAYER3
|
||||
return MimeTypes.AUDIO_MPEG;
|
||||
case 0xff: // WAVE_FORMAT_AAC
|
||||
return MimeTypes.AUDIO_AAC;
|
||||
case 0x2000: // WAVE_FORMAT_DVM - AC3
|
||||
return MimeTypes.AUDIO_AC3;
|
||||
case 0x2001: // WAVE_FORMAT_DTS2
|
||||
return MimeTypes.AUDIO_DTS;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getMimeTypeFromCompression(int compression) {
|
||||
switch (compression) {
|
||||
case 0x3234504d: // MP42
|
||||
return MimeTypes.VIDEO_MP42;
|
||||
case 0x3334504d: // MP43
|
||||
return MimeTypes.VIDEO_MP43;
|
||||
case 0x34363248: // H264
|
||||
case 0x31637661: // avc1
|
||||
case 0x31435641: // AVC1
|
||||
return MimeTypes.VIDEO_H264;
|
||||
case 0x44495633: // 3VID
|
||||
case 0x78766964: // divx
|
||||
case 0x58564944: // DIVX
|
||||
case 0x30355844: // DX50
|
||||
case 0x34504d46: // FMP4
|
||||
case 0x64697678: // xvid
|
||||
case 0x44495658: // XVID
|
||||
return MimeTypes.VIDEO_MP4V;
|
||||
case 0x47504a4d: // MJPG
|
||||
case 0x67706a6d: // mjpg
|
||||
return MimeTypes.VIDEO_MJPEG;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2022 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 androidx.media3.extractor.avi;
|
||||
|
||||
import androidx.media3.common.util.ParsableByteArray;
|
||||
|
||||
/** Parses and contains the name from the STRN chunk. */
|
||||
/* package */ final class StreamNameChunk implements AviChunk {
|
||||
|
||||
public static StreamNameChunk parseFrom(ParsableByteArray body) {
|
||||
return new StreamNameChunk(body.readString(body.bytesLeft()));
|
||||
}
|
||||
|
||||
public final String name;
|
||||
|
||||
private StreamNameChunk(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return AviExtractor.FOURCC_strn;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
@NonNullApi
|
||||
package androidx.media3.extractor.avi;
|
||||
|
||||
import androidx.media3.common.util.NonNullApi;
|
@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import android.net.Uri;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.extractor.amr.AmrExtractor;
|
||||
import androidx.media3.extractor.avi.AviExtractor;
|
||||
import androidx.media3.extractor.flac.FlacExtractor;
|
||||
import androidx.media3.extractor.flv.FlvExtractor;
|
||||
import androidx.media3.extractor.jpeg.JpegExtractor;
|
||||
@ -70,6 +71,7 @@ public final class DefaultExtractorsFactoryTest {
|
||||
Ac3Extractor.class,
|
||||
Ac4Extractor.class,
|
||||
Mp3Extractor.class,
|
||||
AviExtractor.class,
|
||||
JpegExtractor.class)
|
||||
.inOrder();
|
||||
}
|
||||
@ -112,6 +114,7 @@ public final class DefaultExtractorsFactoryTest {
|
||||
AdtsExtractor.class,
|
||||
Ac3Extractor.class,
|
||||
Ac4Extractor.class,
|
||||
AviExtractor.class,
|
||||
JpegExtractor.class)
|
||||
.inOrder();
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2022 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 androidx.media3.extractor.avi;
|
||||
|
||||
import androidx.media3.test.utils.ExtractorAsserts;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
|
||||
|
||||
/** Tests for {@link AviExtractor}. */
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
public final class AviExtractorTest {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static ImmutableList<ExtractorAsserts.SimulationConfig> params() {
|
||||
return ExtractorAsserts.configs();
|
||||
}
|
||||
|
||||
@Parameter public ExtractorAsserts.SimulationConfig simulationConfig;
|
||||
|
||||
@Test
|
||||
public void aviSample() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(AviExtractor::new, "media/avi/sample.avi", simulationConfig);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,751 @@
|
||||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 4080000
|
||||
getPosition(0) = [[timeUs=0, position=9996]]
|
||||
getPosition(1) = [[timeUs=0, position=9996]]
|
||||
getPosition(2040000) = [[timeUs=1835152, position=160708], [timeUs=2335648, position=198850]]
|
||||
getPosition(4080000) = [[timeUs=3837136, position=308434]]
|
||||
numberOfTracks = 2
|
||||
track 0:
|
||||
total output bytes = 165531
|
||||
sample count = 66
|
||||
format 0:
|
||||
id = 0
|
||||
sampleMimeType = video/mp4v-es
|
||||
maxInputSize = 6761
|
||||
width = 960
|
||||
height = 400
|
||||
sample 0:
|
||||
time = 1334666
|
||||
flags = 1
|
||||
data = length 5891, hash 4627CBC3
|
||||
sample 1:
|
||||
time = 1376374
|
||||
flags = 0
|
||||
data = length 3154, hash B7484F2C
|
||||
sample 2:
|
||||
time = 1418083
|
||||
flags = 0
|
||||
data = length 2409, hash 93E50DB6
|
||||
sample 3:
|
||||
time = 1459791
|
||||
flags = 0
|
||||
data = length 2296, hash 73A46768
|
||||
sample 4:
|
||||
time = 1501499
|
||||
flags = 0
|
||||
data = length 2514, hash F71DCA93
|
||||
sample 5:
|
||||
time = 1543208
|
||||
flags = 0
|
||||
data = length 2614, hash BDD6744E
|
||||
sample 6:
|
||||
time = 1584916
|
||||
flags = 0
|
||||
data = length 2797, hash 81BED431
|
||||
sample 7:
|
||||
time = 1626624
|
||||
flags = 0
|
||||
data = length 1549, hash D892E824
|
||||
sample 8:
|
||||
time = 1668333
|
||||
flags = 0
|
||||
data = length 2714, hash B3EE7E2A
|
||||
sample 9:
|
||||
time = 1710041
|
||||
flags = 0
|
||||
data = length 2002, hash BC9E16ED
|
||||
sample 10:
|
||||
time = 1751749
|
||||
flags = 0
|
||||
data = length 2726, hash C31D5A82
|
||||
sample 11:
|
||||
time = 1793458
|
||||
flags = 0
|
||||
data = length 2639, hash AE67DC59
|
||||
sample 12:
|
||||
time = 1835166
|
||||
flags = 1
|
||||
data = length 5011, hash 630ADA59
|
||||
sample 13:
|
||||
time = 1876874
|
||||
flags = 0
|
||||
data = length 4356, hash 76CE0D21
|
||||
sample 14:
|
||||
time = 1918583
|
||||
flags = 0
|
||||
data = length 1986, hash AC41A7FC
|
||||
sample 15:
|
||||
time = 1960291
|
||||
flags = 0
|
||||
data = length 2792, hash 497D3A2D
|
||||
sample 16:
|
||||
time = 2001999
|
||||
flags = 0
|
||||
data = length 2176, hash FADAC8ED
|
||||
sample 17:
|
||||
time = 2043708
|
||||
flags = 0
|
||||
data = length 2463, hash 379DE4C8
|
||||
sample 18:
|
||||
time = 2085416
|
||||
flags = 0
|
||||
data = length 2472, hash 9E68BAC5
|
||||
sample 19:
|
||||
time = 2127124
|
||||
flags = 0
|
||||
data = length 1960, hash 38BC3EFC
|
||||
sample 20:
|
||||
time = 2168832
|
||||
flags = 0
|
||||
data = length 1833, hash 139C885B
|
||||
sample 21:
|
||||
time = 2210541
|
||||
flags = 0
|
||||
data = length 1865, hash A14BE838
|
||||
sample 22:
|
||||
time = 2252249
|
||||
flags = 0
|
||||
data = length 1491, hash 8EC33935
|
||||
sample 23:
|
||||
time = 2293957
|
||||
flags = 0
|
||||
data = length 1403, hash 78D87F2C
|
||||
sample 24:
|
||||
time = 2335666
|
||||
flags = 1
|
||||
data = length 4936, hash C34CC2D0
|
||||
sample 25:
|
||||
time = 2377374
|
||||
flags = 0
|
||||
data = length 2539, hash D0EDEC2B
|
||||
sample 26:
|
||||
time = 2419082
|
||||
flags = 0
|
||||
data = length 3052, hash 3F68900F
|
||||
sample 27:
|
||||
time = 2460791
|
||||
flags = 0
|
||||
data = length 2998, hash B531AC4
|
||||
sample 28:
|
||||
time = 2502499
|
||||
flags = 0
|
||||
data = length 1670, hash 734A2739
|
||||
sample 29:
|
||||
time = 2544207
|
||||
flags = 0
|
||||
data = length 1634, hash 60A39EA5
|
||||
sample 30:
|
||||
time = 2585916
|
||||
flags = 0
|
||||
data = length 1623, hash B18B39FE
|
||||
sample 31:
|
||||
time = 2627624
|
||||
flags = 0
|
||||
data = length 806, hash DA70C12B
|
||||
sample 32:
|
||||
time = 2669332
|
||||
flags = 0
|
||||
data = length 990, hash A1642D2C
|
||||
sample 33:
|
||||
time = 2711041
|
||||
flags = 0
|
||||
data = length 903, hash 411ECEA3
|
||||
sample 34:
|
||||
time = 2752749
|
||||
flags = 0
|
||||
data = length 713, hash A4DAFA22
|
||||
sample 35:
|
||||
time = 2794457
|
||||
flags = 0
|
||||
data = length 749, hash F39941EF
|
||||
sample 36:
|
||||
time = 2836166
|
||||
flags = 1
|
||||
data = length 5258, hash 19670F6D
|
||||
sample 37:
|
||||
time = 2877874
|
||||
flags = 0
|
||||
data = length 1932, hash 3F7F6D21
|
||||
sample 38:
|
||||
time = 2919582
|
||||
flags = 0
|
||||
data = length 731, hash 45EF5D68
|
||||
sample 39:
|
||||
time = 2961291
|
||||
flags = 0
|
||||
data = length 1076, hash 8C23B3FF
|
||||
sample 40:
|
||||
time = 3002999
|
||||
flags = 0
|
||||
data = length 1560, hash D6133304
|
||||
sample 41:
|
||||
time = 3044707
|
||||
flags = 0
|
||||
data = length 2564, hash B7B256B
|
||||
sample 42:
|
||||
time = 3086416
|
||||
flags = 0
|
||||
data = length 2789, hash 97736B63
|
||||
sample 43:
|
||||
time = 3128124
|
||||
flags = 0
|
||||
data = length 2469, hash C65A89B6
|
||||
sample 44:
|
||||
time = 3169832
|
||||
flags = 0
|
||||
data = length 2203, hash D89686B4
|
||||
sample 45:
|
||||
time = 3211541
|
||||
flags = 0
|
||||
data = length 2097, hash 91358D88
|
||||
sample 46:
|
||||
time = 3253249
|
||||
flags = 0
|
||||
data = length 2043, hash 50547CF1
|
||||
sample 47:
|
||||
time = 3294957
|
||||
flags = 0
|
||||
data = length 2198, hash F93F1606
|
||||
sample 48:
|
||||
time = 3336666
|
||||
flags = 1
|
||||
data = length 5084, hash BEC89380
|
||||
sample 49:
|
||||
time = 3378374
|
||||
flags = 0
|
||||
data = length 3043, hash F3C50E5A
|
||||
sample 50:
|
||||
time = 3420082
|
||||
flags = 0
|
||||
data = length 2786, hash 49C8C67C
|
||||
sample 51:
|
||||
time = 3461791
|
||||
flags = 0
|
||||
data = length 2652, hash D0A93BE7
|
||||
sample 52:
|
||||
time = 3503499
|
||||
flags = 0
|
||||
data = length 2675, hash 81F7F5BD
|
||||
sample 53:
|
||||
time = 3545207
|
||||
flags = 0
|
||||
data = length 2916, hash E2A38AE1
|
||||
sample 54:
|
||||
time = 3586916
|
||||
flags = 0
|
||||
data = length 2574, hash 50EC13BD
|
||||
sample 55:
|
||||
time = 3628624
|
||||
flags = 0
|
||||
data = length 2644, hash 3DF461F4
|
||||
sample 56:
|
||||
time = 3670332
|
||||
flags = 0
|
||||
data = length 2932, hash E2F2DAB0
|
||||
sample 57:
|
||||
time = 3712041
|
||||
flags = 0
|
||||
data = length 2625, hash 100D69E1
|
||||
sample 58:
|
||||
time = 3753749
|
||||
flags = 0
|
||||
data = length 2773, hash 347DCC1F
|
||||
sample 59:
|
||||
time = 3795457
|
||||
flags = 0
|
||||
data = length 2348, hash 51FC01A3
|
||||
sample 60:
|
||||
time = 3837166
|
||||
flags = 1
|
||||
data = length 5356, hash 190A3CAE
|
||||
sample 61:
|
||||
time = 3878874
|
||||
flags = 0
|
||||
data = length 3172, hash 538FA2AE
|
||||
sample 62:
|
||||
time = 3920582
|
||||
flags = 0
|
||||
data = length 2393, hash 525B26D6
|
||||
sample 63:
|
||||
time = 3962291
|
||||
flags = 0
|
||||
data = length 2307, hash C894745F
|
||||
sample 64:
|
||||
time = 4003999
|
||||
flags = 0
|
||||
data = length 2490, hash 800FED70
|
||||
sample 65:
|
||||
time = 4045707
|
||||
flags = 0
|
||||
data = length 2115, hash A2512D3
|
||||
track 1:
|
||||
total output bytes = 44160
|
||||
sample count = 115
|
||||
format 0:
|
||||
id = 1
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 384
|
||||
channelCount = 2
|
||||
sampleRate = 48000
|
||||
sample 0:
|
||||
time = 1296000
|
||||
flags = 1
|
||||
data = length 384, hash 4338D4A1
|
||||
sample 1:
|
||||
time = 1320000
|
||||
flags = 1
|
||||
data = length 384, hash C65E6D68
|
||||
sample 2:
|
||||
time = 1344000
|
||||
flags = 1
|
||||
data = length 384, hash AE2762E8
|
||||
sample 3:
|
||||
time = 1368000
|
||||
flags = 1
|
||||
data = length 384, hash 8CFEAA7F
|
||||
sample 4:
|
||||
time = 1392000
|
||||
flags = 1
|
||||
data = length 384, hash A96A80B4
|
||||
sample 5:
|
||||
time = 1416000
|
||||
flags = 1
|
||||
data = length 384, hash 69A84538
|
||||
sample 6:
|
||||
time = 1440000
|
||||
flags = 1
|
||||
data = length 384, hash 9131F77E
|
||||
sample 7:
|
||||
time = 1464000
|
||||
flags = 1
|
||||
data = length 384, hash 818091B1
|
||||
sample 8:
|
||||
time = 1488000
|
||||
flags = 1
|
||||
data = length 384, hash 6DBECC10
|
||||
sample 9:
|
||||
time = 1512000
|
||||
flags = 1
|
||||
data = length 384, hash A9912967
|
||||
sample 10:
|
||||
time = 1536000
|
||||
flags = 1
|
||||
data = length 384, hash 4DA97472
|
||||
sample 11:
|
||||
time = 1560000
|
||||
flags = 1
|
||||
data = length 384, hash 31B363DC
|
||||
sample 12:
|
||||
time = 1584000
|
||||
flags = 1
|
||||
data = length 384, hash E15BB36C
|
||||
sample 13:
|
||||
time = 1608000
|
||||
flags = 1
|
||||
data = length 384, hash 159C963C
|
||||
sample 14:
|
||||
time = 1632000
|
||||
flags = 1
|
||||
data = length 384, hash 50B874D
|
||||
sample 15:
|
||||
time = 1656000
|
||||
flags = 1
|
||||
data = length 384, hash 8727F339
|
||||
sample 16:
|
||||
time = 1680000
|
||||
flags = 1
|
||||
data = length 384, hash C0853B6
|
||||
sample 17:
|
||||
time = 1704000
|
||||
flags = 1
|
||||
data = length 384, hash 9E026376
|
||||
sample 18:
|
||||
time = 1728000
|
||||
flags = 1
|
||||
data = length 384, hash C190380F
|
||||
sample 19:
|
||||
time = 1752000
|
||||
flags = 1
|
||||
data = length 384, hash 20925F33
|
||||
sample 20:
|
||||
time = 1776000
|
||||
flags = 1
|
||||
data = length 384, hash 9F740DAF
|
||||
sample 21:
|
||||
time = 1800000
|
||||
flags = 1
|
||||
data = length 384, hash F75757D6
|
||||
sample 22:
|
||||
time = 1824000
|
||||
flags = 1
|
||||
data = length 384, hash 46D76ED0
|
||||
sample 23:
|
||||
time = 1848000
|
||||
flags = 1
|
||||
data = length 384, hash 11DC480F
|
||||
sample 24:
|
||||
time = 1872000
|
||||
flags = 1
|
||||
data = length 384, hash 3428D6D8
|
||||
sample 25:
|
||||
time = 1896000
|
||||
flags = 1
|
||||
data = length 384, hash 16A11668
|
||||
sample 26:
|
||||
time = 1920000
|
||||
flags = 1
|
||||
data = length 384, hash 4CFBA63C
|
||||
sample 27:
|
||||
time = 1944000
|
||||
flags = 1
|
||||
data = length 384, hash 2B6702A9
|
||||
sample 28:
|
||||
time = 1968000
|
||||
flags = 1
|
||||
data = length 384, hash D047CEF9
|
||||
sample 29:
|
||||
time = 1992000
|
||||
flags = 1
|
||||
data = length 384, hash 25F05663
|
||||
sample 30:
|
||||
time = 2016000
|
||||
flags = 1
|
||||
data = length 384, hash 947441C7
|
||||
sample 31:
|
||||
time = 2040000
|
||||
flags = 1
|
||||
data = length 384, hash E82145F7
|
||||
sample 32:
|
||||
time = 2064000
|
||||
flags = 1
|
||||
data = length 384, hash 6C40F859
|
||||
sample 33:
|
||||
time = 2088000
|
||||
flags = 1
|
||||
data = length 384, hash 273FBEF8
|
||||
sample 34:
|
||||
time = 2112000
|
||||
flags = 1
|
||||
data = length 384, hash 2FF062B6
|
||||
sample 35:
|
||||
time = 2136000
|
||||
flags = 1
|
||||
data = length 384, hash 73FF8D58
|
||||
sample 36:
|
||||
time = 2160000
|
||||
flags = 1
|
||||
data = length 384, hash F2BAB943
|
||||
sample 37:
|
||||
time = 2184000
|
||||
flags = 1
|
||||
data = length 384, hash 507DEF9F
|
||||
sample 38:
|
||||
time = 2208000
|
||||
flags = 1
|
||||
data = length 384, hash 913E927A
|
||||
sample 39:
|
||||
time = 2232000
|
||||
flags = 1
|
||||
data = length 384, hash AFFD0AED
|
||||
sample 40:
|
||||
time = 2256000
|
||||
flags = 1
|
||||
data = length 384, hash EE0C6F4C
|
||||
sample 41:
|
||||
time = 2280000
|
||||
flags = 1
|
||||
data = length 384, hash 70726632
|
||||
sample 42:
|
||||
time = 2304000
|
||||
flags = 1
|
||||
data = length 384, hash B5D49F8
|
||||
sample 43:
|
||||
time = 2328000
|
||||
flags = 1
|
||||
data = length 384, hash B341AF3F
|
||||
sample 44:
|
||||
time = 2352000
|
||||
flags = 1
|
||||
data = length 384, hash 6AC1D8C4
|
||||
sample 45:
|
||||
time = 2376000
|
||||
flags = 1
|
||||
data = length 384, hash BC666685
|
||||
sample 46:
|
||||
time = 2400000
|
||||
flags = 1
|
||||
data = length 384, hash E58054E8
|
||||
sample 47:
|
||||
time = 2424000
|
||||
flags = 1
|
||||
data = length 384, hash 404AB403
|
||||
sample 48:
|
||||
time = 2448000
|
||||
flags = 1
|
||||
data = length 384, hash 265A86B8
|
||||
sample 49:
|
||||
time = 2472000
|
||||
flags = 1
|
||||
data = length 384, hash 306316F6
|
||||
sample 50:
|
||||
time = 2496000
|
||||
flags = 1
|
||||
data = length 384, hash 7BFDEA60
|
||||
sample 51:
|
||||
time = 2520000
|
||||
flags = 1
|
||||
data = length 384, hash 2EFF8E5B
|
||||
sample 52:
|
||||
time = 2544000
|
||||
flags = 1
|
||||
data = length 384, hash C06CE84C
|
||||
sample 53:
|
||||
time = 2568000
|
||||
flags = 1
|
||||
data = length 384, hash 9069A01E
|
||||
sample 54:
|
||||
time = 2592000
|
||||
flags = 1
|
||||
data = length 384, hash 4A78F181
|
||||
sample 55:
|
||||
time = 2616000
|
||||
flags = 1
|
||||
data = length 384, hash 57FD4BE7
|
||||
sample 56:
|
||||
time = 2640000
|
||||
flags = 1
|
||||
data = length 384, hash B09DB688
|
||||
sample 57:
|
||||
time = 2664000
|
||||
flags = 1
|
||||
data = length 384, hash 5602C52F
|
||||
sample 58:
|
||||
time = 2688000
|
||||
flags = 1
|
||||
data = length 384, hash 77762F5D
|
||||
sample 59:
|
||||
time = 2712000
|
||||
flags = 1
|
||||
data = length 384, hash 6A0BDB6
|
||||
sample 60:
|
||||
time = 2736000
|
||||
flags = 1
|
||||
data = length 384, hash 2428C91
|
||||
sample 61:
|
||||
time = 2760000
|
||||
flags = 1
|
||||
data = length 384, hash DEB54354
|
||||
sample 62:
|
||||
time = 2784000
|
||||
flags = 1
|
||||
data = length 384, hash FB0B7BEE
|
||||
sample 63:
|
||||
time = 2808000
|
||||
flags = 1
|
||||
data = length 384, hash BDD82F68
|
||||
sample 64:
|
||||
time = 2832000
|
||||
flags = 1
|
||||
data = length 384, hash BAB3B808
|
||||
sample 65:
|
||||
time = 2856000
|
||||
flags = 1
|
||||
data = length 384, hash E9183572
|
||||
sample 66:
|
||||
time = 2880000
|
||||
flags = 1
|
||||
data = length 384, hash 9E36BC40
|
||||
sample 67:
|
||||
time = 2904000
|
||||
flags = 1
|
||||
data = length 384, hash 937ED026
|
||||
sample 68:
|
||||
time = 2928000
|
||||
flags = 1
|
||||
data = length 384, hash BF337AD1
|
||||
sample 69:
|
||||
time = 2952000
|
||||
flags = 1
|
||||
data = length 384, hash E381C534
|
||||
sample 70:
|
||||
time = 2976000
|
||||
flags = 1
|
||||
data = length 384, hash 6C9E1D71
|
||||
sample 71:
|
||||
time = 3000000
|
||||
flags = 1
|
||||
data = length 384, hash 1C359B93
|
||||
sample 72:
|
||||
time = 3024000
|
||||
flags = 1
|
||||
data = length 384, hash 3D137C16
|
||||
sample 73:
|
||||
time = 3048000
|
||||
flags = 1
|
||||
data = length 384, hash 90D23677
|
||||
sample 74:
|
||||
time = 3072000
|
||||
flags = 1
|
||||
data = length 384, hash 438F4839
|
||||
sample 75:
|
||||
time = 3096000
|
||||
flags = 1
|
||||
data = length 384, hash EBAF44EF
|
||||
sample 76:
|
||||
time = 3120000
|
||||
flags = 1
|
||||
data = length 384, hash D8F64C54
|
||||
sample 77:
|
||||
time = 3144000
|
||||
flags = 1
|
||||
data = length 384, hash 994F2EA6
|
||||
sample 78:
|
||||
time = 3168000
|
||||
flags = 1
|
||||
data = length 384, hash 7E9DF6E4
|
||||
sample 79:
|
||||
time = 3192000
|
||||
flags = 1
|
||||
data = length 384, hash 577F18B8
|
||||
sample 80:
|
||||
time = 3216000
|
||||
flags = 1
|
||||
data = length 384, hash A47FCEE
|
||||
sample 81:
|
||||
time = 3240000
|
||||
flags = 1
|
||||
data = length 384, hash 5A1C435E
|
||||
sample 82:
|
||||
time = 3264000
|
||||
flags = 1
|
||||
data = length 384, hash 1D9EDC36
|
||||
sample 83:
|
||||
time = 3288000
|
||||
flags = 1
|
||||
data = length 384, hash 3355333
|
||||
sample 84:
|
||||
time = 3312000
|
||||
flags = 1
|
||||
data = length 384, hash 27CA9735
|
||||
sample 85:
|
||||
time = 3336000
|
||||
flags = 1
|
||||
data = length 384, hash EB74B3F7
|
||||
sample 86:
|
||||
time = 3360000
|
||||
flags = 1
|
||||
data = length 384, hash 22AC46CB
|
||||
sample 87:
|
||||
time = 3384000
|
||||
flags = 1
|
||||
data = length 384, hash 6002643D
|
||||
sample 88:
|
||||
time = 3408000
|
||||
flags = 1
|
||||
data = length 384, hash 487449E
|
||||
sample 89:
|
||||
time = 3432000
|
||||
flags = 1
|
||||
data = length 384, hash C5B10A14
|
||||
sample 90:
|
||||
time = 3456000
|
||||
flags = 1
|
||||
data = length 384, hash 6050635D
|
||||
sample 91:
|
||||
time = 3480000
|
||||
flags = 1
|
||||
data = length 384, hash 437EAD63
|
||||
sample 92:
|
||||
time = 3504000
|
||||
flags = 1
|
||||
data = length 384, hash 55A02C25
|
||||
sample 93:
|
||||
time = 3528000
|
||||
flags = 1
|
||||
data = length 384, hash 171CCC00
|
||||
sample 94:
|
||||
time = 3552000
|
||||
flags = 1
|
||||
data = length 384, hash 911127C8
|
||||
sample 95:
|
||||
time = 3576000
|
||||
flags = 1
|
||||
data = length 384, hash AA157B50
|
||||
sample 96:
|
||||
time = 3600000
|
||||
flags = 1
|
||||
data = length 384, hash 26F2D866
|
||||
sample 97:
|
||||
time = 3624000
|
||||
flags = 1
|
||||
data = length 384, hash 67ADB3A9
|
||||
sample 98:
|
||||
time = 3648000
|
||||
flags = 1
|
||||
data = length 384, hash F118D82D
|
||||
sample 99:
|
||||
time = 3672000
|
||||
flags = 1
|
||||
data = length 384, hash F51C252B
|
||||
sample 100:
|
||||
time = 3696000
|
||||
flags = 1
|
||||
data = length 384, hash BD13B97C
|
||||
sample 101:
|
||||
time = 3720000
|
||||
flags = 1
|
||||
data = length 384, hash 24BCF0AB
|
||||
sample 102:
|
||||
time = 3744000
|
||||
flags = 1
|
||||
data = length 384, hash 18DE9193
|
||||
sample 103:
|
||||
time = 3768000
|
||||
flags = 1
|
||||
data = length 384, hash 234D8C99
|
||||
sample 104:
|
||||
time = 3792000
|
||||
flags = 1
|
||||
data = length 384, hash EDFD2511
|
||||
sample 105:
|
||||
time = 3816000
|
||||
flags = 1
|
||||
data = length 384, hash 69E3E157
|
||||
sample 106:
|
||||
time = 3840000
|
||||
flags = 1
|
||||
data = length 384, hash AC90ADEC
|
||||
sample 107:
|
||||
time = 3864000
|
||||
flags = 1
|
||||
data = length 384, hash 6A333A56
|
||||
sample 108:
|
||||
time = 3888000
|
||||
flags = 1
|
||||
data = length 384, hash 493D75A3
|
||||
sample 109:
|
||||
time = 3912000
|
||||
flags = 1
|
||||
data = length 384, hash 53FE2A9E
|
||||
sample 110:
|
||||
time = 3936000
|
||||
flags = 1
|
||||
data = length 384, hash 65D6147C
|
||||
sample 111:
|
||||
time = 3960000
|
||||
flags = 1
|
||||
data = length 384, hash 5E744FB2
|
||||
sample 112:
|
||||
time = 3984000
|
||||
flags = 1
|
||||
data = length 384, hash 68AEB7CA
|
||||
sample 113:
|
||||
time = 4008000
|
||||
flags = 1
|
||||
data = length 384, hash AC2972C
|
||||
sample 114:
|
||||
time = 4032000
|
||||
flags = 1
|
||||
data = length 384, hash E2A06CB9
|
||||
tracksEnded = true
|
@ -0,0 +1,487 @@
|
||||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 4080000
|
||||
getPosition(0) = [[timeUs=0, position=9996]]
|
||||
getPosition(1) = [[timeUs=0, position=9996]]
|
||||
getPosition(2040000) = [[timeUs=1835152, position=160708], [timeUs=2335648, position=198850]]
|
||||
getPosition(4080000) = [[timeUs=3837136, position=308434]]
|
||||
numberOfTracks = 2
|
||||
track 0:
|
||||
total output bytes = 102418
|
||||
sample count = 42
|
||||
format 0:
|
||||
id = 0
|
||||
sampleMimeType = video/mp4v-es
|
||||
maxInputSize = 6761
|
||||
width = 960
|
||||
height = 400
|
||||
sample 0:
|
||||
time = 2335666
|
||||
flags = 1
|
||||
data = length 4936, hash C34CC2D0
|
||||
sample 1:
|
||||
time = 2377374
|
||||
flags = 0
|
||||
data = length 2539, hash D0EDEC2B
|
||||
sample 2:
|
||||
time = 2419082
|
||||
flags = 0
|
||||
data = length 3052, hash 3F68900F
|
||||
sample 3:
|
||||
time = 2460791
|
||||
flags = 0
|
||||
data = length 2998, hash B531AC4
|
||||
sample 4:
|
||||
time = 2502499
|
||||
flags = 0
|
||||
data = length 1670, hash 734A2739
|
||||
sample 5:
|
||||
time = 2544207
|
||||
flags = 0
|
||||
data = length 1634, hash 60A39EA5
|
||||
sample 6:
|
||||
time = 2585916
|
||||
flags = 0
|
||||
data = length 1623, hash B18B39FE
|
||||
sample 7:
|
||||
time = 2627624
|
||||
flags = 0
|
||||
data = length 806, hash DA70C12B
|
||||
sample 8:
|
||||
time = 2669332
|
||||
flags = 0
|
||||
data = length 990, hash A1642D2C
|
||||
sample 9:
|
||||
time = 2711041
|
||||
flags = 0
|
||||
data = length 903, hash 411ECEA3
|
||||
sample 10:
|
||||
time = 2752749
|
||||
flags = 0
|
||||
data = length 713, hash A4DAFA22
|
||||
sample 11:
|
||||
time = 2794457
|
||||
flags = 0
|
||||
data = length 749, hash F39941EF
|
||||
sample 12:
|
||||
time = 2836166
|
||||
flags = 1
|
||||
data = length 5258, hash 19670F6D
|
||||
sample 13:
|
||||
time = 2877874
|
||||
flags = 0
|
||||
data = length 1932, hash 3F7F6D21
|
||||
sample 14:
|
||||
time = 2919582
|
||||
flags = 0
|
||||
data = length 731, hash 45EF5D68
|
||||
sample 15:
|
||||
time = 2961291
|
||||
flags = 0
|
||||
data = length 1076, hash 8C23B3FF
|
||||
sample 16:
|
||||
time = 3002999
|
||||
flags = 0
|
||||
data = length 1560, hash D6133304
|
||||
sample 17:
|
||||
time = 3044707
|
||||
flags = 0
|
||||
data = length 2564, hash B7B256B
|
||||
sample 18:
|
||||
time = 3086416
|
||||
flags = 0
|
||||
data = length 2789, hash 97736B63
|
||||
sample 19:
|
||||
time = 3128124
|
||||
flags = 0
|
||||
data = length 2469, hash C65A89B6
|
||||
sample 20:
|
||||
time = 3169832
|
||||
flags = 0
|
||||
data = length 2203, hash D89686B4
|
||||
sample 21:
|
||||
time = 3211541
|
||||
flags = 0
|
||||
data = length 2097, hash 91358D88
|
||||
sample 22:
|
||||
time = 3253249
|
||||
flags = 0
|
||||
data = length 2043, hash 50547CF1
|
||||
sample 23:
|
||||
time = 3294957
|
||||
flags = 0
|
||||
data = length 2198, hash F93F1606
|
||||
sample 24:
|
||||
time = 3336666
|
||||
flags = 1
|
||||
data = length 5084, hash BEC89380
|
||||
sample 25:
|
||||
time = 3378374
|
||||
flags = 0
|
||||
data = length 3043, hash F3C50E5A
|
||||
sample 26:
|
||||
time = 3420082
|
||||
flags = 0
|
||||
data = length 2786, hash 49C8C67C
|
||||
sample 27:
|
||||
time = 3461791
|
||||
flags = 0
|
||||
data = length 2652, hash D0A93BE7
|
||||
sample 28:
|
||||
time = 3503499
|
||||
flags = 0
|
||||
data = length 2675, hash 81F7F5BD
|
||||
sample 29:
|
||||
time = 3545207
|
||||
flags = 0
|
||||
data = length 2916, hash E2A38AE1
|
||||
sample 30:
|
||||
time = 3586916
|
||||
flags = 0
|
||||
data = length 2574, hash 50EC13BD
|
||||
sample 31:
|
||||
time = 3628624
|
||||
flags = 0
|
||||
data = length 2644, hash 3DF461F4
|
||||
sample 32:
|
||||
time = 3670332
|
||||
flags = 0
|
||||
data = length 2932, hash E2F2DAB0
|
||||
sample 33:
|
||||
time = 3712041
|
||||
flags = 0
|
||||
data = length 2625, hash 100D69E1
|
||||
sample 34:
|
||||
time = 3753749
|
||||
flags = 0
|
||||
data = length 2773, hash 347DCC1F
|
||||
sample 35:
|
||||
time = 3795457
|
||||
flags = 0
|
||||
data = length 2348, hash 51FC01A3
|
||||
sample 36:
|
||||
time = 3837166
|
||||
flags = 1
|
||||
data = length 5356, hash 190A3CAE
|
||||
sample 37:
|
||||
time = 3878874
|
||||
flags = 0
|
||||
data = length 3172, hash 538FA2AE
|
||||
sample 38:
|
||||
time = 3920582
|
||||
flags = 0
|
||||
data = length 2393, hash 525B26D6
|
||||
sample 39:
|
||||
time = 3962291
|
||||
flags = 0
|
||||
data = length 2307, hash C894745F
|
||||
sample 40:
|
||||
time = 4003999
|
||||
flags = 0
|
||||
data = length 2490, hash 800FED70
|
||||
sample 41:
|
||||
time = 4045707
|
||||
flags = 0
|
||||
data = length 2115, hash A2512D3
|
||||
track 1:
|
||||
total output bytes = 28032
|
||||
sample count = 73
|
||||
format 0:
|
||||
id = 1
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 384
|
||||
channelCount = 2
|
||||
sampleRate = 48000
|
||||
sample 0:
|
||||
time = 2304000
|
||||
flags = 1
|
||||
data = length 384, hash B5D49F8
|
||||
sample 1:
|
||||
time = 2328000
|
||||
flags = 1
|
||||
data = length 384, hash B341AF3F
|
||||
sample 2:
|
||||
time = 2352000
|
||||
flags = 1
|
||||
data = length 384, hash 6AC1D8C4
|
||||
sample 3:
|
||||
time = 2376000
|
||||
flags = 1
|
||||
data = length 384, hash BC666685
|
||||
sample 4:
|
||||
time = 2400000
|
||||
flags = 1
|
||||
data = length 384, hash E58054E8
|
||||
sample 5:
|
||||
time = 2424000
|
||||
flags = 1
|
||||
data = length 384, hash 404AB403
|
||||
sample 6:
|
||||
time = 2448000
|
||||
flags = 1
|
||||
data = length 384, hash 265A86B8
|
||||
sample 7:
|
||||
time = 2472000
|
||||
flags = 1
|
||||
data = length 384, hash 306316F6
|
||||
sample 8:
|
||||
time = 2496000
|
||||
flags = 1
|
||||
data = length 384, hash 7BFDEA60
|
||||
sample 9:
|
||||
time = 2520000
|
||||
flags = 1
|
||||
data = length 384, hash 2EFF8E5B
|
||||
sample 10:
|
||||
time = 2544000
|
||||
flags = 1
|
||||
data = length 384, hash C06CE84C
|
||||
sample 11:
|
||||
time = 2568000
|
||||
flags = 1
|
||||
data = length 384, hash 9069A01E
|
||||
sample 12:
|
||||
time = 2592000
|
||||
flags = 1
|
||||
data = length 384, hash 4A78F181
|
||||
sample 13:
|
||||
time = 2616000
|
||||
flags = 1
|
||||
data = length 384, hash 57FD4BE7
|
||||
sample 14:
|
||||
time = 2640000
|
||||
flags = 1
|
||||
data = length 384, hash B09DB688
|
||||
sample 15:
|
||||
time = 2664000
|
||||
flags = 1
|
||||
data = length 384, hash 5602C52F
|
||||
sample 16:
|
||||
time = 2688000
|
||||
flags = 1
|
||||
data = length 384, hash 77762F5D
|
||||
sample 17:
|
||||
time = 2712000
|
||||
flags = 1
|
||||
data = length 384, hash 6A0BDB6
|
||||
sample 18:
|
||||
time = 2736000
|
||||
flags = 1
|
||||
data = length 384, hash 2428C91
|
||||
sample 19:
|
||||
time = 2760000
|
||||
flags = 1
|
||||
data = length 384, hash DEB54354
|
||||
sample 20:
|
||||
time = 2784000
|
||||
flags = 1
|
||||
data = length 384, hash FB0B7BEE
|
||||
sample 21:
|
||||
time = 2808000
|
||||
flags = 1
|
||||
data = length 384, hash BDD82F68
|
||||
sample 22:
|
||||
time = 2832000
|
||||
flags = 1
|
||||
data = length 384, hash BAB3B808
|
||||
sample 23:
|
||||
time = 2856000
|
||||
flags = 1
|
||||
data = length 384, hash E9183572
|
||||
sample 24:
|
||||
time = 2880000
|
||||
flags = 1
|
||||
data = length 384, hash 9E36BC40
|
||||
sample 25:
|
||||
time = 2904000
|
||||
flags = 1
|
||||
data = length 384, hash 937ED026
|
||||
sample 26:
|
||||
time = 2928000
|
||||
flags = 1
|
||||
data = length 384, hash BF337AD1
|
||||
sample 27:
|
||||
time = 2952000
|
||||
flags = 1
|
||||
data = length 384, hash E381C534
|
||||
sample 28:
|
||||
time = 2976000
|
||||
flags = 1
|
||||
data = length 384, hash 6C9E1D71
|
||||
sample 29:
|
||||
time = 3000000
|
||||
flags = 1
|
||||
data = length 384, hash 1C359B93
|
||||
sample 30:
|
||||
time = 3024000
|
||||
flags = 1
|
||||
data = length 384, hash 3D137C16
|
||||
sample 31:
|
||||
time = 3048000
|
||||
flags = 1
|
||||
data = length 384, hash 90D23677
|
||||
sample 32:
|
||||
time = 3072000
|
||||
flags = 1
|
||||
data = length 384, hash 438F4839
|
||||
sample 33:
|
||||
time = 3096000
|
||||
flags = 1
|
||||
data = length 384, hash EBAF44EF
|
||||
sample 34:
|
||||
time = 3120000
|
||||
flags = 1
|
||||
data = length 384, hash D8F64C54
|
||||
sample 35:
|
||||
time = 3144000
|
||||
flags = 1
|
||||
data = length 384, hash 994F2EA6
|
||||
sample 36:
|
||||
time = 3168000
|
||||
flags = 1
|
||||
data = length 384, hash 7E9DF6E4
|
||||
sample 37:
|
||||
time = 3192000
|
||||
flags = 1
|
||||
data = length 384, hash 577F18B8
|
||||
sample 38:
|
||||
time = 3216000
|
||||
flags = 1
|
||||
data = length 384, hash A47FCEE
|
||||
sample 39:
|
||||
time = 3240000
|
||||
flags = 1
|
||||
data = length 384, hash 5A1C435E
|
||||
sample 40:
|
||||
time = 3264000
|
||||
flags = 1
|
||||
data = length 384, hash 1D9EDC36
|
||||
sample 41:
|
||||
time = 3288000
|
||||
flags = 1
|
||||
data = length 384, hash 3355333
|
||||
sample 42:
|
||||
time = 3312000
|
||||
flags = 1
|
||||
data = length 384, hash 27CA9735
|
||||
sample 43:
|
||||
time = 3336000
|
||||
flags = 1
|
||||
data = length 384, hash EB74B3F7
|
||||
sample 44:
|
||||
time = 3360000
|
||||
flags = 1
|
||||
data = length 384, hash 22AC46CB
|
||||
sample 45:
|
||||
time = 3384000
|
||||
flags = 1
|
||||
data = length 384, hash 6002643D
|
||||
sample 46:
|
||||
time = 3408000
|
||||
flags = 1
|
||||
data = length 384, hash 487449E
|
||||
sample 47:
|
||||
time = 3432000
|
||||
flags = 1
|
||||
data = length 384, hash C5B10A14
|
||||
sample 48:
|
||||
time = 3456000
|
||||
flags = 1
|
||||
data = length 384, hash 6050635D
|
||||
sample 49:
|
||||
time = 3480000
|
||||
flags = 1
|
||||
data = length 384, hash 437EAD63
|
||||
sample 50:
|
||||
time = 3504000
|
||||
flags = 1
|
||||
data = length 384, hash 55A02C25
|
||||
sample 51:
|
||||
time = 3528000
|
||||
flags = 1
|
||||
data = length 384, hash 171CCC00
|
||||
sample 52:
|
||||
time = 3552000
|
||||
flags = 1
|
||||
data = length 384, hash 911127C8
|
||||
sample 53:
|
||||
time = 3576000
|
||||
flags = 1
|
||||
data = length 384, hash AA157B50
|
||||
sample 54:
|
||||
time = 3600000
|
||||
flags = 1
|
||||
data = length 384, hash 26F2D866
|
||||
sample 55:
|
||||
time = 3624000
|
||||
flags = 1
|
||||
data = length 384, hash 67ADB3A9
|
||||
sample 56:
|
||||
time = 3648000
|
||||
flags = 1
|
||||
data = length 384, hash F118D82D
|
||||
sample 57:
|
||||
time = 3672000
|
||||
flags = 1
|
||||
data = length 384, hash F51C252B
|
||||
sample 58:
|
||||
time = 3696000
|
||||
flags = 1
|
||||
data = length 384, hash BD13B97C
|
||||
sample 59:
|
||||
time = 3720000
|
||||
flags = 1
|
||||
data = length 384, hash 24BCF0AB
|
||||
sample 60:
|
||||
time = 3744000
|
||||
flags = 1
|
||||
data = length 384, hash 18DE9193
|
||||
sample 61:
|
||||
time = 3768000
|
||||
flags = 1
|
||||
data = length 384, hash 234D8C99
|
||||
sample 62:
|
||||
time = 3792000
|
||||
flags = 1
|
||||
data = length 384, hash EDFD2511
|
||||
sample 63:
|
||||
time = 3816000
|
||||
flags = 1
|
||||
data = length 384, hash 69E3E157
|
||||
sample 64:
|
||||
time = 3840000
|
||||
flags = 1
|
||||
data = length 384, hash AC90ADEC
|
||||
sample 65:
|
||||
time = 3864000
|
||||
flags = 1
|
||||
data = length 384, hash 6A333A56
|
||||
sample 66:
|
||||
time = 3888000
|
||||
flags = 1
|
||||
data = length 384, hash 493D75A3
|
||||
sample 67:
|
||||
time = 3912000
|
||||
flags = 1
|
||||
data = length 384, hash 53FE2A9E
|
||||
sample 68:
|
||||
time = 3936000
|
||||
flags = 1
|
||||
data = length 384, hash 65D6147C
|
||||
sample 69:
|
||||
time = 3960000
|
||||
flags = 1
|
||||
data = length 384, hash 5E744FB2
|
||||
sample 70:
|
||||
time = 3984000
|
||||
flags = 1
|
||||
data = length 384, hash 68AEB7CA
|
||||
sample 71:
|
||||
time = 4008000
|
||||
flags = 1
|
||||
data = length 384, hash AC2972C
|
||||
sample 72:
|
||||
time = 4032000
|
||||
flags = 1
|
||||
data = length 384, hash E2A06CB9
|
||||
tracksEnded = true
|
@ -0,0 +1,91 @@
|
||||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 4080000
|
||||
getPosition(0) = [[timeUs=0, position=9996]]
|
||||
getPosition(1) = [[timeUs=0, position=9996]]
|
||||
getPosition(2040000) = [[timeUs=1835152, position=160708], [timeUs=2335648, position=198850]]
|
||||
getPosition(4080000) = [[timeUs=3837136, position=308434]]
|
||||
numberOfTracks = 2
|
||||
track 0:
|
||||
total output bytes = 17833
|
||||
sample count = 6
|
||||
format 0:
|
||||
id = 0
|
||||
sampleMimeType = video/mp4v-es
|
||||
maxInputSize = 6761
|
||||
width = 960
|
||||
height = 400
|
||||
sample 0:
|
||||
time = 3837166
|
||||
flags = 1
|
||||
data = length 5356, hash 190A3CAE
|
||||
sample 1:
|
||||
time = 3878874
|
||||
flags = 0
|
||||
data = length 3172, hash 538FA2AE
|
||||
sample 2:
|
||||
time = 3920582
|
||||
flags = 0
|
||||
data = length 2393, hash 525B26D6
|
||||
sample 3:
|
||||
time = 3962291
|
||||
flags = 0
|
||||
data = length 2307, hash C894745F
|
||||
sample 4:
|
||||
time = 4003999
|
||||
flags = 0
|
||||
data = length 2490, hash 800FED70
|
||||
sample 5:
|
||||
time = 4045707
|
||||
flags = 0
|
||||
data = length 2115, hash A2512D3
|
||||
track 1:
|
||||
total output bytes = 3840
|
||||
sample count = 10
|
||||
format 0:
|
||||
id = 1
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 384
|
||||
channelCount = 2
|
||||
sampleRate = 48000
|
||||
sample 0:
|
||||
time = 3816000
|
||||
flags = 1
|
||||
data = length 384, hash 69E3E157
|
||||
sample 1:
|
||||
time = 3840000
|
||||
flags = 1
|
||||
data = length 384, hash AC90ADEC
|
||||
sample 2:
|
||||
time = 3864000
|
||||
flags = 1
|
||||
data = length 384, hash 6A333A56
|
||||
sample 3:
|
||||
time = 3888000
|
||||
flags = 1
|
||||
data = length 384, hash 493D75A3
|
||||
sample 4:
|
||||
time = 3912000
|
||||
flags = 1
|
||||
data = length 384, hash 53FE2A9E
|
||||
sample 5:
|
||||
time = 3936000
|
||||
flags = 1
|
||||
data = length 384, hash 65D6147C
|
||||
sample 6:
|
||||
time = 3960000
|
||||
flags = 1
|
||||
data = length 384, hash 5E744FB2
|
||||
sample 7:
|
||||
time = 3984000
|
||||
flags = 1
|
||||
data = length 384, hash 68AEB7CA
|
||||
sample 8:
|
||||
time = 4008000
|
||||
flags = 1
|
||||
data = length 384, hash AC2972C
|
||||
sample 9:
|
||||
time = 4032000
|
||||
flags = 1
|
||||
data = length 384, hash E2A06CB9
|
||||
tracksEnded = true
|
File diff suppressed because it is too large
Load Diff
BIN
libraries/test_data/src/test/assets/media/avi/sample.avi
Normal file
BIN
libraries/test_data/src/test/assets/media/avi/sample.avi
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user