Add support for H.263 and MPEG-4 Part 2 in TS

The new reader is named H263Reader as it handles H.263 streams, but
MPEG-4 Part 2 streams are also intended to be handled. The reader's
output format MIME type is video/mp4v as the H.263 streams can be
decoded by decoders supporting this MIME type.

The implementation is based on the framework implementation for
extracting MPEG-4 video in MPEG-TS
(https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libstagefright/mpeg2ts/ESQueue.cpp;l=1825;drc=86e363c1fac27302ca4ae33e73296f7797672995)
and is similar to the existing H262Reader.

Issue: #1603
Issue: #5107
PiperOrigin-RevId: 320565337
This commit is contained in:
andrewlewis 2020-07-10 09:40:24 +01:00 committed by kim-vde
parent a8f1cdcfd7
commit 1e2ed51f25
12 changed files with 913 additions and 5 deletions

View File

@ -212,6 +212,9 @@
headers mime type in `DefaultExtractorsFactory`.
* Add support for partially fragmented MP4s
([#7308](https://github.com/google/ExoPlayer/issues/7308)).
* Add support for MPEG-4 Part 2 and H.263 in MPEG-TS
([#1603](https://github.com/google/ExoPlayer/issues/1603),
[#5107](https://github.com/google/ExoPlayer/issues/5107)).
* Testing
* Add `TestExoPlayer`, a utility class with APIs to create
`SimpleExoPlayer` instances with fake components for testing.

View File

@ -165,6 +165,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
return new PesReader(new DtsReader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_H262:
return new PesReader(new H262Reader(buildUserDataReader(esInfo)));
case TsExtractor.TS_STREAM_TYPE_H263:
return new PesReader(new H263Reader(buildUserDataReader(esInfo)));
case TsExtractor.TS_STREAM_TYPE_H264:
return isSet(FLAG_IGNORE_H264_STREAM) ? null
: new PesReader(new H264Reader(buildSeiReader(esInfo),

View File

@ -15,6 +15,9 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
@ -22,7 +25,6 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
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;
@ -118,7 +120,7 @@ public final class H262Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
checkStateNotNull(output); // Asserts that createTracks has been called.
int offset = data.getPosition();
int limit = data.limit();
byte[] dataArray = data.data;
@ -156,7 +158,7 @@ public final class H262Reader implements ElementaryStreamReader {
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
// The csd data is complete, so we can decode and output the media format.
Pair<Format, Long> result = parseCsdBuffer(csdBuffer, formatId);
Pair<Format, Long> result = parseCsdBuffer(csdBuffer, checkNotNull(formatId));
output.format(result.first);
frameDurationUs = result.second;
hasOutputFormat = true;
@ -215,11 +217,11 @@ public final class H262Reader implements ElementaryStreamReader {
* Parses the {@link Format} and frame duration from a csd buffer.
*
* @param csdBuffer The csd buffer.
* @param formatId The id for the generated format. May be null.
* @param formatId The id for the generated format.
* @return A pair consisting of the {@link Format} and the frame duration in microseconds, or 0 if
* the duration could not be determined.
*/
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, @Nullable String formatId) {
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, String formatId) {
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
int firstByte = csdData[4] & 0xFF;

View File

@ -0,0 +1,477 @@
/*
* Copyright 2020 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.ts;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import static com.google.android.exoplayer2.util.Util.castNonNull;
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.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Parses an ISO/IEC 14496-2 (MPEG-4 Part 2) or ITU-T Recommendation H.263 byte stream and extracts
* individual frames.
*/
public final class H263Reader implements ElementaryStreamReader {
private static final String TAG = "H263Reader";
private static final int START_CODE_VALUE_VISUAL_OBJECT_SEQUENCE = 0xB0;
private static final int START_CODE_VALUE_USER_DATA = 0xB2;
private static final int START_CODE_VALUE_GROUP_OF_VOP = 0xB3;
private static final int START_CODE_VALUE_VISUAL_OBJECT = 0xB5;
private static final int START_CODE_VALUE_VOP = 0xB6;
private static final int START_CODE_VALUE_MAX_VIDEO_OBJECT = 0x1F;
private static final int START_CODE_VALUE_UNSET = -1;
// See ISO 14496-2 (2001) table 6-12 for the mapping from aspect_ratio_info to pixel aspect ratio.
private static final float[] PIXEL_WIDTH_HEIGHT_RATIO_BY_ASPECT_RATIO_INFO =
new float[] {1f, 1f, 12 / 11f, 10 / 11f, 16 / 11f, 40 / 33f, 1f};
private static final int VIDEO_OBJECT_LAYER_SHAPE_RECTANGULAR = 0;
@Nullable private final UserDataReader userDataReader;
@Nullable private final ParsableByteArray userDataParsable;
// State that should be reset on seek.
private final boolean[] prefixFlags;
private final CsdBuffer csdBuffer;
@Nullable private final NalUnitTargetBuffer userData;
private H263Reader.@MonotonicNonNull SampleReader sampleReader;
private long totalBytesWritten;
// State initialized once when tracks are created.
private @MonotonicNonNull String formatId;
private @MonotonicNonNull TrackOutput output;
// State that should not be reset on seek.
private boolean hasOutputFormat;
// Per packet state that gets reset at the start of each packet.
private long pesTimeUs;
/** Creates a new reader. */
public H263Reader() {
this(null);
}
/* package */ H263Reader(@Nullable UserDataReader userDataReader) {
this.userDataReader = userDataReader;
prefixFlags = new boolean[4];
csdBuffer = new CsdBuffer(128);
if (userDataReader != null) {
userData = new NalUnitTargetBuffer(START_CODE_VALUE_USER_DATA, 128);
userDataParsable = new ParsableByteArray();
} else {
userData = null;
userDataParsable = null;
}
}
@Override
public void seek() {
NalUnitUtil.clearPrefixFlags(prefixFlags);
csdBuffer.reset();
if (sampleReader != null) {
sampleReader.reset();
}
if (userData != null) {
userData.reset();
}
totalBytesWritten = 0;
}
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_VIDEO);
sampleReader = new SampleReader(output);
if (userDataReader != null) {
userDataReader.createTracks(extractorOutput, idGenerator);
}
}
@Override
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
// TODO (Internal b/32267012): Consider using random access indicator.
this.pesTimeUs = pesTimeUs;
}
@Override
public void consume(ParsableByteArray data) {
// Assert that createTracks has been called.
checkStateNotNull(sampleReader);
checkStateNotNull(output);
int offset = data.getPosition();
int limit = data.limit();
byte[] dataArray = data.data;
// Append the data to the buffer.
totalBytesWritten += data.bytesLeft();
output.sampleData(data, data.bytesLeft());
while (true) {
int startCodeOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags);
if (startCodeOffset == limit) {
// We've scanned to the end of the data without finding another start code.
if (!hasOutputFormat) {
csdBuffer.onData(dataArray, offset, limit);
}
sampleReader.onData(dataArray, offset, limit);
if (userData != null) {
userData.appendToNalUnit(dataArray, offset, limit);
}
return;
}
// We've found a start code with the following value.
int startCodeValue = data.data[startCodeOffset + 3] & 0xFF;
// This is the number of bytes from the current offset to the start of the next start
// code. It may be negative if the start code started in the previously consumed data.
int lengthToStartCode = startCodeOffset - offset;
if (!hasOutputFormat) {
if (lengthToStartCode > 0) {
csdBuffer.onData(dataArray, offset, /* limit= */ startCodeOffset);
}
// This is the number of bytes belonging to the next start code that have already been
// passed to csdBuffer.
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
// The csd data is complete, so we can decode and output the media format.
output.format(
parseCsdBuffer(csdBuffer, csdBuffer.volStartPosition, checkNotNull(formatId)));
hasOutputFormat = true;
}
}
sampleReader.onData(dataArray, offset, /* limit= */ startCodeOffset);
if (userData != null) {
int bytesAlreadyPassed = 0;
if (lengthToStartCode > 0) {
userData.appendToNalUnit(dataArray, offset, /* limit= */ startCodeOffset);
} else {
bytesAlreadyPassed = -lengthToStartCode;
}
if (userData.endNalUnit(bytesAlreadyPassed)) {
int unescapedLength = NalUnitUtil.unescapeStream(userData.nalData, userData.nalLength);
castNonNull(userDataParsable).reset(userData.nalData, unescapedLength);
castNonNull(userDataReader).consume(pesTimeUs, userDataParsable);
}
if (startCodeValue == START_CODE_VALUE_USER_DATA && data.data[startCodeOffset + 2] == 0x1) {
userData.startNalUnit(startCodeValue);
}
}
int bytesWrittenPastPosition = limit - startCodeOffset;
long absolutePosition = totalBytesWritten - bytesWrittenPastPosition;
sampleReader.onDataEnd(absolutePosition, bytesWrittenPastPosition, hasOutputFormat);
// Indicate the start of the next chunk.
sampleReader.onStartCode(startCodeValue, pesTimeUs);
// Continue scanning the data.
offset = startCodeOffset + 3;
}
}
@Override
public void packetFinished() {
// Do nothing.
}
/**
* Parses a codec-specific data buffer, returning the {@link Format} of the media.
*
* @param csdBuffer The buffer to parse.
* @param volStartPosition The byte offset of the start of the video object layer in the buffer.
* @param formatId The ID for the generated format.
* @return The {@link Format} of the media represented in the buffer.
*/
private static Format parseCsdBuffer(CsdBuffer csdBuffer, int volStartPosition, String formatId) {
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
ParsableBitArray buffer = new ParsableBitArray(csdData);
buffer.skipBytes(volStartPosition);
// Parse the video object layer defined in ISO 14496-2 (2001) subsection 6.2.3.
buffer.skipBytes(4); // video_object_layer_start_code
buffer.skipBit(); // random_accessible_vol
buffer.skipBits(8); // video_object_type_indication
if (buffer.readBit()) { // is_object_layer_identifier
buffer.skipBits(4); // video_object_layer_verid
buffer.skipBits(3); // video_object_layer_priority
}
float pixelWidthHeightRatio;
int aspectRatioInfo = buffer.readBits(4);
if (aspectRatioInfo == 0x0F) { // extended_PAR
int parWidth = buffer.readBits(8);
int parHeight = buffer.readBits(8);
if (parHeight == 0) {
Log.w(TAG, "Invalid aspect ratio");
pixelWidthHeightRatio = 1f;
} else {
pixelWidthHeightRatio = (float) parWidth / parHeight;
}
} else if (aspectRatioInfo < PIXEL_WIDTH_HEIGHT_RATIO_BY_ASPECT_RATIO_INFO.length) {
pixelWidthHeightRatio = PIXEL_WIDTH_HEIGHT_RATIO_BY_ASPECT_RATIO_INFO[aspectRatioInfo];
} else {
Log.w(TAG, "Invalid aspect ratio");
pixelWidthHeightRatio = 1f;
}
if (buffer.readBit()) { // vol_control_parameters
buffer.skipBits(2); // chroma_format
buffer.skipBits(1); // low_delay
if (buffer.readBit()) { // vbv_parameters
buffer.skipBits(15); // first_half_bit_rate
buffer.skipBit(); // marker_bit
buffer.skipBits(15); // latter_half_bit_rate
buffer.skipBit(); // marker_bit
buffer.skipBits(15); // first_half_vbv_buffer_size
buffer.skipBit(); // marker_bit
buffer.skipBits(3); // latter_half_vbv_buffer_size
buffer.skipBits(11); // first_half_vbv_occupancy
buffer.skipBit(); // marker_bit
buffer.skipBits(15); // latter_half_vbv_occupancy
buffer.skipBit(); // marker_bit
}
}
int videoObjectLayerShape = buffer.readBits(2);
if (videoObjectLayerShape != VIDEO_OBJECT_LAYER_SHAPE_RECTANGULAR) {
Log.w(TAG, "Unhandled video object layer shape");
}
buffer.skipBit(); // marker_bit
int vopTimeIncrementResolution = buffer.readBits(16);
buffer.skipBit(); // marker_bit
if (buffer.readBit()) { // fixed_vop_rate
if (vopTimeIncrementResolution == 0) {
Log.w(TAG, "Invalid vop_increment_time_resolution");
} else {
vopTimeIncrementResolution--;
int numBits = 0;
while (vopTimeIncrementResolution > 0) {
++numBits;
vopTimeIncrementResolution >>= 1;
}
buffer.skipBits(numBits); // fixed_vop_time_increment
}
}
buffer.skipBit(); // marker_bit
int videoObjectLayerWidth = buffer.readBits(13);
buffer.skipBit(); // marker_bit
int videoObjectLayerHeight = buffer.readBits(13);
buffer.skipBit(); // marker_bit
buffer.skipBit(); // interlaced
return new Format.Builder()
.setId(formatId)
.setSampleMimeType(MimeTypes.VIDEO_MP4V)
.setWidth(videoObjectLayerWidth)
.setHeight(videoObjectLayerHeight)
.setPixelWidthHeightRatio(pixelWidthHeightRatio)
.setInitializationData(Collections.singletonList(csdData))
.build();
}
private static final class CsdBuffer {
private static final byte[] START_CODE = new byte[] {0, 0, 1};
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STATE_SKIP_TO_VISUAL_OBJECT_SEQUENCE_START,
STATE_EXPECT_VISUAL_OBJECT_START,
STATE_EXPECT_VIDEO_OBJECT_START,
STATE_EXPECT_VIDEO_OBJECT_LAYER_START,
STATE_WAIT_FOR_VOP_START
})
private @interface State {}
private static final int STATE_SKIP_TO_VISUAL_OBJECT_SEQUENCE_START = 0;
private static final int STATE_EXPECT_VISUAL_OBJECT_START = 1;
private static final int STATE_EXPECT_VIDEO_OBJECT_START = 2;
private static final int STATE_EXPECT_VIDEO_OBJECT_LAYER_START = 3;
private static final int STATE_WAIT_FOR_VOP_START = 4;
private boolean isFilling;
@State private int state;
public int length;
public int volStartPosition;
public byte[] data;
public CsdBuffer(int initialCapacity) {
data = new byte[initialCapacity];
}
public void reset() {
isFilling = false;
length = 0;
state = STATE_SKIP_TO_VISUAL_OBJECT_SEQUENCE_START;
}
/**
* Called when a start code is encountered in the stream.
*
* @param startCodeValue The start code value.
* @param bytesAlreadyPassed The number of bytes of the start code that have been passed to
* {@link #onData(byte[], int, int)}, or 0.
* @return Whether the csd data is now complete. If true is returned, neither this method nor
* {@link #onData(byte[], int, int)} should be called again without an interleaving call to
* {@link #reset()}.
*/
public boolean onStartCode(int startCodeValue, int bytesAlreadyPassed) {
switch (state) {
case STATE_SKIP_TO_VISUAL_OBJECT_SEQUENCE_START:
if (startCodeValue == START_CODE_VALUE_VISUAL_OBJECT_SEQUENCE) {
state = STATE_EXPECT_VISUAL_OBJECT_START;
isFilling = true;
}
break;
case STATE_EXPECT_VISUAL_OBJECT_START:
if (startCodeValue != START_CODE_VALUE_VISUAL_OBJECT) {
Log.w(TAG, "Unexpected start code value");
reset();
} else {
state = STATE_EXPECT_VIDEO_OBJECT_START;
}
break;
case STATE_EXPECT_VIDEO_OBJECT_START:
if (startCodeValue > START_CODE_VALUE_MAX_VIDEO_OBJECT) {
Log.w(TAG, "Unexpected start code value");
reset();
} else {
state = STATE_EXPECT_VIDEO_OBJECT_LAYER_START;
}
break;
case STATE_EXPECT_VIDEO_OBJECT_LAYER_START:
if ((startCodeValue & 0xF0) != 0x20) {
Log.w(TAG, "Unexpected start code value");
reset();
} else {
volStartPosition = length;
state = STATE_WAIT_FOR_VOP_START;
}
break;
case STATE_WAIT_FOR_VOP_START:
if (startCodeValue == START_CODE_VALUE_GROUP_OF_VOP
|| startCodeValue == START_CODE_VALUE_VISUAL_OBJECT) {
length -= bytesAlreadyPassed;
isFilling = false;
return true;
}
break;
default:
throw new IllegalStateException();
}
onData(START_CODE, /* offset= */ 0, /* limit= */ START_CODE.length);
return false;
}
public void onData(byte[] newData, int offset, int limit) {
if (!isFilling) {
return;
}
int readLength = limit - offset;
if (data.length < length + readLength) {
data = Arrays.copyOf(data, (length + readLength) * 2);
}
System.arraycopy(newData, offset, data, length, readLength);
length += readLength;
}
}
private static final class SampleReader {
/** Byte offset of vop_coding_type after the start code value. */
private static final int OFFSET_VOP_CODING_TYPE = 1;
/** Value of vop_coding_type for intra video object planes. */
private static final int VOP_CODING_TYPE_INTRA = 0;
private final TrackOutput output;
private boolean readingSample;
private boolean lookingForVopCodingType;
private boolean sampleIsKeyframe;
private int startCodeValue;
private int vopBytesRead;
private long samplePosition;
private long sampleTimeUs;
public SampleReader(TrackOutput output) {
this.output = output;
}
public void reset() {
readingSample = false;
lookingForVopCodingType = false;
sampleIsKeyframe = false;
startCodeValue = START_CODE_VALUE_UNSET;
}
public void onStartCode(int startCodeValue, long pesTimeUs) {
this.startCodeValue = startCodeValue;
sampleIsKeyframe = false;
readingSample =
startCodeValue == START_CODE_VALUE_VOP || startCodeValue == START_CODE_VALUE_GROUP_OF_VOP;
lookingForVopCodingType = startCodeValue == START_CODE_VALUE_VOP;
vopBytesRead = 0;
sampleTimeUs = pesTimeUs;
}
public void onData(byte[] data, int offset, int limit) {
if (lookingForVopCodingType) {
int headerOffset = offset + OFFSET_VOP_CODING_TYPE - vopBytesRead;
if (headerOffset < limit) {
sampleIsKeyframe = ((data[headerOffset] & 0xC0) >> 6) == VOP_CODING_TYPE_INTRA;
lookingForVopCodingType = false;
} else {
vopBytesRead += limit - offset;
}
}
}
public void onDataEnd(long position, int bytesWrittenPastPosition, boolean hasOutputFormat) {
if (startCodeValue == START_CODE_VALUE_VOP && hasOutputFormat && readingSample) {
int size = (int) (position - samplePosition);
@C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
output.sampleMetadata(
sampleTimeUs, flags, size, bytesWrittenPastPosition, /* encryptionData= */ null);
}
// Start a new sample, unless this is a 'group of video object plane' in which case we
// include the data at the start of a 'video object plane' coming next.
if (startCodeValue != START_CODE_VALUE_GROUP_OF_VOP) {
samplePosition = position;
}
}
}
}

View File

@ -90,6 +90,7 @@ public final class TsExtractor implements Extractor {
public static final int TS_STREAM_TYPE_E_AC3 = 0x87;
public static final int TS_STREAM_TYPE_AC4 = 0xAC; // DVB/ATSC AC-4 Descriptor
public static final int TS_STREAM_TYPE_H262 = 0x02;
public static final int TS_STREAM_TYPE_H263 = 0x10; // MPEG-4 Part 2 and H.263
public static final int TS_STREAM_TYPE_H264 = 0x1B;
public static final int TS_STREAM_TYPE_H265 = 0x24;
public static final int TS_STREAM_TYPE_ID3 = 0x15;

View File

@ -60,6 +60,11 @@ public final class TsExtractorTest {
TsExtractor::new, "ts/sample_h262_mpeg_audio.ts", simulationConfig);
}
@Test
public void sampleWithH263() throws Exception {
ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_h263.ts", simulationConfig);
}
@Test
public void sampleWithH264AndMpegAudio() throws Exception {
ExtractorAsserts.assertBehavior(

Binary file not shown.

View File

@ -0,0 +1,121 @@
seekMap:
isSeekable = true
duration = 960000
getPosition(0) = [[timeUs=0, position=0]]
getPosition(1) = [[timeUs=1, position=0]]
getPosition(480000) = [[timeUs=480000, position=21958]]
getPosition(960000) = [[timeUs=960000, position=44104]]
numberOfTracks = 2
track 256:
total output bytes = 39002
sample count = 24
format 0:
id = 1/256
sampleMimeType = video/mp4v-es
width = 640
height = 360
initializationData:
data = length 47, hash 7696BF67
sample 0:
time = 0
flags = 1
data = length 8408, hash 718A7985
sample 1:
time = 40000
flags = 0
data = length 2018, hash 7BC8193F
sample 2:
time = 80000
flags = 0
data = length 480, hash C244FFAF
sample 3:
time = 120000
flags = 0
data = length 256, hash 56D68D82
sample 4:
time = 160000
flags = 0
data = length 222, hash FADF6CA9
sample 5:
time = 200000
flags = 0
data = length 217, hash 161BB856
sample 6:
time = 240000
flags = 0
data = length 212, hash 835B0727
sample 7:
time = 280000
flags = 0
data = length 212, hash E9AF0AB7
sample 8:
time = 320000
flags = 0
data = length 212, hash E9517D06
sample 9:
time = 360000
flags = 0
data = length 212, hash 4FA58096
sample 10:
time = 400000
flags = 0
data = length 212, hash 4F47F2E5
sample 11:
time = 440000
flags = 0
data = length 212, hash B59BF675
sample 12:
time = 480000
flags = 1
data = length 11769, hash 3ED9DF06
sample 13:
time = 520000
flags = 0
data = length 230, hash 2AF3505D
sample 14:
time = 560000
flags = 0
data = length 222, hash F4E7436D
sample 15:
time = 600000
flags = 0
data = length 222, hash F0F812FD
sample 16:
time = 640000
flags = 0
data = length 222, hash 18472E8C
sample 17:
time = 680000
flags = 0
data = length 222, hash 1457FE1C
sample 18:
time = 720000
flags = 0
data = length 222, hash 3BA719AB
sample 19:
time = 760000
flags = 0
data = length 222, hash 37B7E93B
sample 20:
time = 800000
flags = 0
data = length 222, hash 5F0704CA
sample 21:
time = 840000
flags = 0
data = length 222, hash 5B17D45A
sample 22:
time = 880000
flags = 0
data = length 222, hash 8266EFE9
sample 23:
time = 920000
flags = 0
data = length 222, hash 7E77BF79
track 8448:
total output bytes = 0
sample count = 0
format 0:
id = 1/8448
sampleMimeType = application/cea-608
tracksEnded = true

View File

@ -0,0 +1,97 @@
seekMap:
isSeekable = true
duration = 960000
getPosition(0) = [[timeUs=0, position=0]]
getPosition(1) = [[timeUs=1, position=0]]
getPosition(480000) = [[timeUs=480000, position=21958]]
getPosition(960000) = [[timeUs=960000, position=44104]]
numberOfTracks = 2
track 256:
total output bytes = 27354
sample count = 18
format 0:
id = 1/256
sampleMimeType = video/mp4v-es
width = 640
height = 360
initializationData:
data = length 47, hash 7696BF67
sample 0:
time = 320000
flags = 0
data = length 212, hash 835B0727
sample 1:
time = 360000
flags = 0
data = length 212, hash E9AF0AB7
sample 2:
time = 400000
flags = 0
data = length 212, hash E9517D06
sample 3:
time = 440000
flags = 0
data = length 212, hash 4FA58096
sample 4:
time = 480000
flags = 0
data = length 212, hash 4F47F2E5
sample 5:
time = 520000
flags = 0
data = length 212, hash B59BF675
sample 6:
time = 560000
flags = 1
data = length 11769, hash 3ED9DF06
sample 7:
time = 600000
flags = 0
data = length 230, hash 2AF3505D
sample 8:
time = 640000
flags = 0
data = length 222, hash F4E7436D
sample 9:
time = 680000
flags = 0
data = length 222, hash F0F812FD
sample 10:
time = 720000
flags = 0
data = length 222, hash 18472E8C
sample 11:
time = 760000
flags = 0
data = length 222, hash 1457FE1C
sample 12:
time = 800000
flags = 0
data = length 222, hash 3BA719AB
sample 13:
time = 840000
flags = 0
data = length 222, hash 37B7E93B
sample 14:
time = 880000
flags = 0
data = length 222, hash 5F0704CA
sample 15:
time = 920000
flags = 0
data = length 222, hash 5B17D45A
sample 16:
time = 960000
flags = 0
data = length 222, hash 8266EFE9
sample 17:
time = 1000000
flags = 0
data = length 222, hash 7E77BF79
track 8448:
total output bytes = 0
sample count = 0
format 0:
id = 1/8448
sampleMimeType = application/cea-608
tracksEnded = true

View File

@ -0,0 +1,57 @@
seekMap:
isSeekable = true
duration = 960000
getPosition(0) = [[timeUs=0, position=0]]
getPosition(1) = [[timeUs=1, position=0]]
getPosition(480000) = [[timeUs=480000, position=21958]]
getPosition(960000) = [[timeUs=960000, position=44104]]
numberOfTracks = 2
track 256:
total output bytes = 13592
sample count = 8
format 0:
id = 1/256
sampleMimeType = video/mp4v-es
width = 640
height = 360
initializationData:
data = length 47, hash 7696BF67
sample 0:
time = 640000
flags = 0
data = length 222, hash 18472E8C
sample 1:
time = 680000
flags = 0
data = length 222, hash 1457FE1C
sample 2:
time = 720000
flags = 0
data = length 222, hash 3BA719AB
sample 3:
time = 760000
flags = 0
data = length 222, hash 37B7E93B
sample 4:
time = 800000
flags = 0
data = length 222, hash 5F0704CA
sample 5:
time = 840000
flags = 0
data = length 222, hash 5B17D45A
sample 6:
time = 880000
flags = 0
data = length 222, hash 8266EFE9
sample 7:
time = 920000
flags = 0
data = length 222, hash 7E77BF79
track 8448:
total output bytes = 0
sample count = 0
format 0:
id = 1/8448
sampleMimeType = application/cea-608
tracksEnded = true

View File

@ -0,0 +1,25 @@
seekMap:
isSeekable = true
duration = 960000
getPosition(0) = [[timeUs=0, position=0]]
getPosition(1) = [[timeUs=1, position=0]]
getPosition(480000) = [[timeUs=480000, position=21958]]
getPosition(960000) = [[timeUs=960000, position=44104]]
numberOfTracks = 2
track 256:
total output bytes = 0
sample count = 0
format 0:
id = 1/256
sampleMimeType = video/mp4v-es
width = 640
height = 360
initializationData:
data = length 47, hash 7696BF67
track 8448:
total output bytes = 0
sample count = 0
format 0:
id = 1/8448
sampleMimeType = application/cea-608
tracksEnded = true

View File

@ -0,0 +1,118 @@
seekMap:
isSeekable = false
duration = UNSET TIME
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 2
track 256:
total output bytes = 39002
sample count = 24
format 0:
id = 1/256
sampleMimeType = video/mp4v-es
width = 640
height = 360
initializationData:
data = length 47, hash 7696BF67
sample 0:
time = 0
flags = 1
data = length 8408, hash 718A7985
sample 1:
time = 40000
flags = 0
data = length 2018, hash 7BC8193F
sample 2:
time = 80000
flags = 0
data = length 480, hash C244FFAF
sample 3:
time = 120000
flags = 0
data = length 256, hash 56D68D82
sample 4:
time = 160000
flags = 0
data = length 222, hash FADF6CA9
sample 5:
time = 200000
flags = 0
data = length 217, hash 161BB856
sample 6:
time = 240000
flags = 0
data = length 212, hash 835B0727
sample 7:
time = 280000
flags = 0
data = length 212, hash E9AF0AB7
sample 8:
time = 320000
flags = 0
data = length 212, hash E9517D06
sample 9:
time = 360000
flags = 0
data = length 212, hash 4FA58096
sample 10:
time = 400000
flags = 0
data = length 212, hash 4F47F2E5
sample 11:
time = 440000
flags = 0
data = length 212, hash B59BF675
sample 12:
time = 480000
flags = 1
data = length 11769, hash 3ED9DF06
sample 13:
time = 520000
flags = 0
data = length 230, hash 2AF3505D
sample 14:
time = 560000
flags = 0
data = length 222, hash F4E7436D
sample 15:
time = 600000
flags = 0
data = length 222, hash F0F812FD
sample 16:
time = 640000
flags = 0
data = length 222, hash 18472E8C
sample 17:
time = 680000
flags = 0
data = length 222, hash 1457FE1C
sample 18:
time = 720000
flags = 0
data = length 222, hash 3BA719AB
sample 19:
time = 760000
flags = 0
data = length 222, hash 37B7E93B
sample 20:
time = 800000
flags = 0
data = length 222, hash 5F0704CA
sample 21:
time = 840000
flags = 0
data = length 222, hash 5B17D45A
sample 22:
time = 880000
flags = 0
data = length 222, hash 8266EFE9
sample 23:
time = 920000
flags = 0
data = length 222, hash 7E77BF79
track 8448:
total output bytes = 0
sample count = 0
format 0:
id = 1/8448
sampleMimeType = application/cea-608
tracksEnded = true