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:
parent
a8f1cdcfd7
commit
1e2ed51f25
@ -212,6 +212,9 @@
|
|||||||
headers mime type in `DefaultExtractorsFactory`.
|
headers mime type in `DefaultExtractorsFactory`.
|
||||||
* Add support for partially fragmented MP4s
|
* Add support for partially fragmented MP4s
|
||||||
([#7308](https://github.com/google/ExoPlayer/issues/7308)).
|
([#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
|
* Testing
|
||||||
* Add `TestExoPlayer`, a utility class with APIs to create
|
* Add `TestExoPlayer`, a utility class with APIs to create
|
||||||
`SimpleExoPlayer` instances with fake components for testing.
|
`SimpleExoPlayer` instances with fake components for testing.
|
||||||
|
@ -165,6 +165,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
|
|||||||
return new PesReader(new DtsReader(esInfo.language));
|
return new PesReader(new DtsReader(esInfo.language));
|
||||||
case TsExtractor.TS_STREAM_TYPE_H262:
|
case TsExtractor.TS_STREAM_TYPE_H262:
|
||||||
return new PesReader(new H262Reader(buildUserDataReader(esInfo)));
|
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:
|
case TsExtractor.TS_STREAM_TYPE_H264:
|
||||||
return isSet(FLAG_IGNORE_H264_STREAM) ? null
|
return isSet(FLAG_IGNORE_H264_STREAM) ? null
|
||||||
: new PesReader(new H264Reader(buildSeiReader(esInfo),
|
: new PesReader(new H264Reader(buildSeiReader(esInfo),
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
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 android.util.Pair;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
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.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
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.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.NalUnitUtil;
|
import com.google.android.exoplayer2.util.NalUnitUtil;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
@ -118,7 +120,7 @@ public final class H262Reader implements ElementaryStreamReader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data) {
|
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 offset = data.getPosition();
|
||||||
int limit = data.limit();
|
int limit = data.limit();
|
||||||
byte[] dataArray = data.data;
|
byte[] dataArray = data.data;
|
||||||
@ -156,7 +158,7 @@ public final class H262Reader implements ElementaryStreamReader {
|
|||||||
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
|
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
|
||||||
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
|
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
|
||||||
// The csd data is complete, so we can decode and output the media format.
|
// 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);
|
output.format(result.first);
|
||||||
frameDurationUs = result.second;
|
frameDurationUs = result.second;
|
||||||
hasOutputFormat = true;
|
hasOutputFormat = true;
|
||||||
@ -215,11 +217,11 @@ public final class H262Reader implements ElementaryStreamReader {
|
|||||||
* Parses the {@link Format} and frame duration from a csd buffer.
|
* Parses the {@link Format} and frame duration from a csd buffer.
|
||||||
*
|
*
|
||||||
* @param csdBuffer The 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
|
* @return A pair consisting of the {@link Format} and the frame duration in microseconds, or 0 if
|
||||||
* the duration could not be determined.
|
* 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);
|
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
|
||||||
|
|
||||||
int firstByte = csdData[4] & 0xFF;
|
int firstByte = csdData[4] & 0xFF;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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_E_AC3 = 0x87;
|
||||||
public static final int TS_STREAM_TYPE_AC4 = 0xAC; // DVB/ATSC AC-4 Descriptor
|
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_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_H264 = 0x1B;
|
||||||
public static final int TS_STREAM_TYPE_H265 = 0x24;
|
public static final int TS_STREAM_TYPE_H265 = 0x24;
|
||||||
public static final int TS_STREAM_TYPE_ID3 = 0x15;
|
public static final int TS_STREAM_TYPE_ID3 = 0x15;
|
||||||
|
@ -60,6 +60,11 @@ public final class TsExtractorTest {
|
|||||||
TsExtractor::new, "ts/sample_h262_mpeg_audio.ts", simulationConfig);
|
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
|
@Test
|
||||||
public void sampleWithH264AndMpegAudio() throws Exception {
|
public void sampleWithH264AndMpegAudio() throws Exception {
|
||||||
ExtractorAsserts.assertBehavior(
|
ExtractorAsserts.assertBehavior(
|
||||||
|
BIN
testdata/src/test/assets/ts/sample_h263.ts
vendored
Normal file
BIN
testdata/src/test/assets/ts/sample_h263.ts
vendored
Normal file
Binary file not shown.
121
testdata/src/test/assets/ts/sample_h263.ts.0.dump
vendored
Normal file
121
testdata/src/test/assets/ts/sample_h263.ts.0.dump
vendored
Normal 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
|
97
testdata/src/test/assets/ts/sample_h263.ts.1.dump
vendored
Normal file
97
testdata/src/test/assets/ts/sample_h263.ts.1.dump
vendored
Normal 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
|
57
testdata/src/test/assets/ts/sample_h263.ts.2.dump
vendored
Normal file
57
testdata/src/test/assets/ts/sample_h263.ts.2.dump
vendored
Normal 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
|
25
testdata/src/test/assets/ts/sample_h263.ts.3.dump
vendored
Normal file
25
testdata/src/test/assets/ts/sample_h263.ts.3.dump
vendored
Normal 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
|
118
testdata/src/test/assets/ts/sample_h263.ts.unknown_length.dump
vendored
Normal file
118
testdata/src/test/assets/ts/sample_h263.ts.unknown_length.dump
vendored
Normal 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
|
Loading…
x
Reference in New Issue
Block a user