Plumb SubtitleParser.Factory into TsExtractor
PiperOrigin-RevId: 597578122
This commit is contained in:
parent
c59711f592
commit
a728ec8e67
@ -16,6 +16,7 @@
|
||||
package androidx.media3.exoplayer.hls;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.extractor.ts.TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.net.Uri;
|
||||
@ -205,16 +206,13 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
||||
return createFragmentedMp4Extractor(
|
||||
subtitleParserFactory, timestampAdjuster, format, muxedCaptionFormats);
|
||||
case FileTypes.TS:
|
||||
Extractor tsExtractor =
|
||||
createTsExtractor(
|
||||
payloadReaderFactoryFlags,
|
||||
exposeCea608WhenMissingDeclarations,
|
||||
format,
|
||||
muxedCaptionFormats,
|
||||
timestampAdjuster);
|
||||
return subtitleParserFactory != null
|
||||
? new SubtitleTranscodingExtractor(tsExtractor, subtitleParserFactory)
|
||||
: tsExtractor;
|
||||
return createTsExtractor(
|
||||
payloadReaderFactoryFlags,
|
||||
exposeCea608WhenMissingDeclarations,
|
||||
format,
|
||||
muxedCaptionFormats,
|
||||
timestampAdjuster,
|
||||
subtitleParserFactory);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -225,7 +223,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
||||
boolean exposeCea608WhenMissingDeclarations,
|
||||
Format format,
|
||||
@Nullable List<Format> muxedCaptionFormats,
|
||||
TimestampAdjuster timestampAdjuster) {
|
||||
TimestampAdjuster timestampAdjuster,
|
||||
@Nullable SubtitleParser.Factory subtitleParserFactory) {
|
||||
@DefaultTsPayloadReaderFactory.Flags
|
||||
int payloadReaderFactoryFlags =
|
||||
DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM
|
||||
@ -254,11 +253,18 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
||||
payloadReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM;
|
||||
}
|
||||
}
|
||||
|
||||
@TsExtractor.Flags int extractorFlags = 0;
|
||||
if (subtitleParserFactory == null) {
|
||||
subtitleParserFactory = SubtitleParser.Factory.UNSUPPORTED;
|
||||
extractorFlags |= TsExtractor.FLAG_EMIT_RAW_SUBTITLE_DATA;
|
||||
}
|
||||
return new TsExtractor(
|
||||
TsExtractor.MODE_HLS,
|
||||
extractorFlags,
|
||||
subtitleParserFactory,
|
||||
timestampAdjuster,
|
||||
new DefaultTsPayloadReaderFactory(payloadReaderFactoryFlags, muxedCaptionFormats));
|
||||
new DefaultTsPayloadReaderFactory(payloadReaderFactoryFlags, muxedCaptionFormats),
|
||||
DEFAULT_TIMESTAMP_SEARCH_BYTES);
|
||||
}
|
||||
|
||||
private static FragmentedMp4Extractor createFragmentedMp4Extractor(
|
||||
|
@ -440,6 +440,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
textTrackTranscodingEnabled
|
||||
&& !(extractor.getUnderlyingImplementation() instanceof FragmentedMp4Extractor)
|
||||
&& !(extractor.getUnderlyingImplementation() instanceof Mp4Extractor)
|
||||
&& !(extractor.getUnderlyingImplementation() instanceof TsExtractor)
|
||||
? new SubtitleTranscodingExtractor(extractor, subtitleParserFactory)
|
||||
: extractor;
|
||||
}
|
||||
@ -530,6 +531,8 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
extractors.add(
|
||||
new TsExtractor(
|
||||
tsMode,
|
||||
(textTrackTranscodingEnabled ? 0 : TsExtractor.FLAG_EMIT_RAW_SUBTITLE_DATA),
|
||||
subtitleParserFactory,
|
||||
new TimestampAdjuster(0),
|
||||
new DefaultTsPayloadReaderFactory(tsFlags, tsSubtitleFormats),
|
||||
tsTimestampSearchBytes));
|
||||
|
@ -24,6 +24,7 @@ import android.util.SparseIntArray;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.ParserException;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.NullableType;
|
||||
@ -39,7 +40,8 @@ import androidx.media3.extractor.ExtractorsFactory;
|
||||
import androidx.media3.extractor.PositionHolder;
|
||||
import androidx.media3.extractor.SeekMap;
|
||||
import androidx.media3.extractor.TrackOutput;
|
||||
import androidx.media3.extractor.ts.DefaultTsPayloadReaderFactory.Flags;
|
||||
import androidx.media3.extractor.text.SubtitleParser;
|
||||
import androidx.media3.extractor.text.SubtitleTranscodingExtractorOutput;
|
||||
import androidx.media3.extractor.ts.TsPayloadReader.DvbSubtitleInfo;
|
||||
import androidx.media3.extractor.ts.TsPayloadReader.EsInfo;
|
||||
import androidx.media3.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||
@ -58,9 +60,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@UnstableApi
|
||||
public final class TsExtractor implements Extractor {
|
||||
|
||||
/** Factory for {@link TsExtractor} instances. */
|
||||
/**
|
||||
* @deprecated Use {@link #newFactory(SubtitleParser.Factory)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new TsExtractor()};
|
||||
|
||||
/**
|
||||
* Creates a factory for {@link TsExtractor} instances with the provided {@link
|
||||
* SubtitleParser.Factory}.
|
||||
*/
|
||||
public static ExtractorsFactory newFactory(SubtitleParser.Factory subtitleParserFactory) {
|
||||
return () -> new Extractor[] {new TsExtractor(subtitleParserFactory)};
|
||||
}
|
||||
|
||||
/**
|
||||
* Modes for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} or {@link
|
||||
* #MODE_HLS}.
|
||||
@ -83,6 +96,24 @@ public final class TsExtractor implements Extractor {
|
||||
*/
|
||||
public static final int MODE_HLS = 2;
|
||||
|
||||
/**
|
||||
* Flags controlling the behavior of the extractor. Possible flag value is {@link
|
||||
* #FLAG_EMIT_RAW_SUBTITLE_DATA}.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef(
|
||||
flag = true,
|
||||
value = {FLAG_EMIT_RAW_SUBTITLE_DATA})
|
||||
public @interface Flags {}
|
||||
|
||||
/**
|
||||
* Flag to use the source subtitle formats without modification. If unset, subtitles will be
|
||||
* transcoded to {@link MimeTypes#APPLICATION_MEDIA3_CUES} during extraction.
|
||||
*/
|
||||
public static final int FLAG_EMIT_RAW_SUBTITLE_DATA = 1;
|
||||
|
||||
public static final int TS_PACKET_SIZE = 188;
|
||||
public static final int DEFAULT_TIMESTAMP_SEARCH_BYTES = 600 * TS_PACKET_SIZE;
|
||||
|
||||
@ -121,11 +152,13 @@ public final class TsExtractor implements Extractor {
|
||||
private static final int SNIFF_TS_PACKET_COUNT = 5;
|
||||
|
||||
private final @Mode int mode;
|
||||
private final @Flags int extractorFlags;
|
||||
private final int timestampSearchBytes;
|
||||
private final List<TimestampAdjuster> timestampAdjusters;
|
||||
private final ParsableByteArray tsPacketBuffer;
|
||||
private final SparseIntArray continuityCounters;
|
||||
private final TsPayloadReader.Factory payloadReaderFactory;
|
||||
private final SubtitleParser.Factory subtitleParserFactory;
|
||||
private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
|
||||
private final SparseBooleanArray trackIds;
|
||||
private final SparseBooleanArray trackPids;
|
||||
@ -142,57 +175,131 @@ public final class TsExtractor implements Extractor {
|
||||
private int bytesSinceLastSync;
|
||||
private int pcrPid;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #TsExtractor(SubtitleParser.Factory)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public TsExtractor() {
|
||||
this(/* defaultTsPayloadReaderFlags= */ 0);
|
||||
this(
|
||||
MODE_SINGLE_PMT,
|
||||
/* extractorFlags= */ FLAG_EMIT_RAW_SUBTITLE_DATA,
|
||||
SubtitleParser.Factory.UNSUPPORTED,
|
||||
new TimestampAdjuster(0),
|
||||
new DefaultTsPayloadReaderFactory(/* defaultTsPayloadReaderFlags= */ 0),
|
||||
DEFAULT_TIMESTAMP_SEARCH_BYTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory}
|
||||
* {@code FLAG_*} values that control the behavior of the payload readers.
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during
|
||||
* extraction.
|
||||
*/
|
||||
public TsExtractor(@Flags int defaultTsPayloadReaderFlags) {
|
||||
this(MODE_SINGLE_PMT, defaultTsPayloadReaderFlags, DEFAULT_TIMESTAMP_SEARCH_BYTES);
|
||||
public TsExtractor(SubtitleParser.Factory subtitleParserFactory) {
|
||||
this(
|
||||
MODE_SINGLE_PMT,
|
||||
/* extractorFlags= */ 0,
|
||||
subtitleParserFactory,
|
||||
new TimestampAdjuster(0),
|
||||
new DefaultTsPayloadReaderFactory(/* defaultTsPayloadReaderFlags= */ 0),
|
||||
DEFAULT_TIMESTAMP_SEARCH_BYTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT}
|
||||
* and {@link #MODE_HLS}.
|
||||
* @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory}
|
||||
* {@code FLAG_*} values that control the behavior of the payload readers.
|
||||
* @param timestampSearchBytes The number of bytes searched from a given position in the stream to
|
||||
* find a PCR timestamp. If this value is too small, the duration might be unknown and seeking
|
||||
* might not be supported for high bitrate progressive streams. Setting a large value for this
|
||||
* field might be inefficient though because the extractor stores a buffer of {@code
|
||||
* timestampSearchBytes} bytes when determining the duration or when performing a seek
|
||||
* operation. The default value is {@link #DEFAULT_TIMESTAMP_SEARCH_BYTES}. If the number of
|
||||
* bytes left in the stream from the current position is less than {@code
|
||||
* timestampSearchBytes}, the search is performed on the bytes left.
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param extractorFlags Flags that control the extractor's behavior.
|
||||
* @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during
|
||||
* extraction.
|
||||
*/
|
||||
public TsExtractor(@Flags int extractorFlags, SubtitleParser.Factory subtitleParserFactory) {
|
||||
this(
|
||||
MODE_SINGLE_PMT,
|
||||
extractorFlags,
|
||||
subtitleParserFactory,
|
||||
new TimestampAdjuster(0),
|
||||
new DefaultTsPayloadReaderFactory(/* defaultTsPayloadReaderFlags= */ 0),
|
||||
DEFAULT_TIMESTAMP_SEARCH_BYTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #TsExtractor(int, int, SubtitleParser.Factory, TimestampAdjuster,
|
||||
* TsPayloadReader.Factory, int)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public TsExtractor(@DefaultTsPayloadReaderFactory.Flags int defaultTsPayloadReaderFlags) {
|
||||
this(
|
||||
MODE_SINGLE_PMT,
|
||||
FLAG_EMIT_RAW_SUBTITLE_DATA,
|
||||
SubtitleParser.Factory.UNSUPPORTED,
|
||||
new TimestampAdjuster(0),
|
||||
new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags),
|
||||
DEFAULT_TIMESTAMP_SEARCH_BYTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #TsExtractor(int, int, SubtitleParser.Factory, TimestampAdjuster,
|
||||
* TsPayloadReader.Factory, int)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public TsExtractor(
|
||||
@Mode int mode, @Flags int defaultTsPayloadReaderFlags, int timestampSearchBytes) {
|
||||
@Mode int mode,
|
||||
@DefaultTsPayloadReaderFactory.Flags int defaultTsPayloadReaderFlags,
|
||||
int timestampSearchBytes) {
|
||||
this(
|
||||
mode,
|
||||
FLAG_EMIT_RAW_SUBTITLE_DATA,
|
||||
SubtitleParser.Factory.UNSUPPORTED,
|
||||
new TimestampAdjuster(0),
|
||||
new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags),
|
||||
timestampSearchBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT}
|
||||
* and {@link #MODE_HLS}.
|
||||
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
||||
* @param payloadReaderFactory Factory for injecting a custom set of payload readers.
|
||||
* @deprecated Use {@link #TsExtractor(int, int, SubtitleParser.Factory, TimestampAdjuster,
|
||||
* TsPayloadReader.Factory, int)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public TsExtractor(
|
||||
@Mode int mode,
|
||||
TimestampAdjuster timestampAdjuster,
|
||||
TsPayloadReader.Factory payloadReaderFactory) {
|
||||
this(mode, timestampAdjuster, payloadReaderFactory, DEFAULT_TIMESTAMP_SEARCH_BYTES);
|
||||
this(
|
||||
mode,
|
||||
FLAG_EMIT_RAW_SUBTITLE_DATA,
|
||||
SubtitleParser.Factory.UNSUPPORTED,
|
||||
timestampAdjuster,
|
||||
payloadReaderFactory,
|
||||
DEFAULT_TIMESTAMP_SEARCH_BYTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #TsExtractor(int, int, SubtitleParser.Factory, TimestampAdjuster,
|
||||
* TsPayloadReader.Factory, int)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public TsExtractor(
|
||||
@Mode int mode,
|
||||
TimestampAdjuster timestampAdjuster,
|
||||
TsPayloadReader.Factory payloadReaderFactory,
|
||||
int timestampSearchBytes) {
|
||||
this(
|
||||
mode,
|
||||
FLAG_EMIT_RAW_SUBTITLE_DATA,
|
||||
SubtitleParser.Factory.UNSUPPORTED,
|
||||
timestampAdjuster,
|
||||
payloadReaderFactory,
|
||||
timestampSearchBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT}
|
||||
* and {@link #MODE_HLS}.
|
||||
* @param extractorFlags Flags that control the extractor's behavior.
|
||||
* @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during
|
||||
* extraction.
|
||||
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
||||
* @param payloadReaderFactory Factory for injecting a custom set of payload readers.
|
||||
* @param timestampSearchBytes The number of bytes searched from a given position in the stream to
|
||||
@ -206,12 +313,16 @@ public final class TsExtractor implements Extractor {
|
||||
*/
|
||||
public TsExtractor(
|
||||
@Mode int mode,
|
||||
@Flags int extractorFlags,
|
||||
SubtitleParser.Factory subtitleParserFactory,
|
||||
TimestampAdjuster timestampAdjuster,
|
||||
TsPayloadReader.Factory payloadReaderFactory,
|
||||
int timestampSearchBytes) {
|
||||
this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory);
|
||||
this.timestampSearchBytes = timestampSearchBytes;
|
||||
this.mode = mode;
|
||||
this.extractorFlags = extractorFlags;
|
||||
this.subtitleParserFactory = subtitleParserFactory;
|
||||
if (mode == MODE_SINGLE_PMT || mode == MODE_HLS) {
|
||||
timestampAdjusters = Collections.singletonList(timestampAdjuster);
|
||||
} else {
|
||||
@ -254,7 +365,10 @@ public final class TsExtractor implements Extractor {
|
||||
|
||||
@Override
|
||||
public void init(ExtractorOutput output) {
|
||||
this.output = output;
|
||||
this.output =
|
||||
(extractorFlags & FLAG_EMIT_RAW_SUBTITLE_DATA) == 0
|
||||
? new SubtitleTranscodingExtractorOutput(output, subtitleParserFactory)
|
||||
: output;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -179,7 +179,7 @@ public final class DefaultExtractorsFactoryTest {
|
||||
assertThat(matroskaExtractor).isInstanceOf(SubtitleTranscodingExtractor.class);
|
||||
assertThat(mp4Extractor).isNotInstanceOf(SubtitleTranscodingExtractor.class);
|
||||
assertThat(fragmentedMp4Extractor).isNotInstanceOf(SubtitleTranscodingExtractor.class);
|
||||
assertThat(tsExtractor).isInstanceOf(SubtitleTranscodingExtractor.class);
|
||||
assertThat(tsExtractor).isNotInstanceOf(SubtitleTranscodingExtractor.class);
|
||||
}
|
||||
|
||||
private static List<Class<? extends Extractor>> getUnderlyingExtractorClasses(
|
||||
|
@ -24,6 +24,7 @@ import androidx.media3.extractor.Extractor;
|
||||
import androidx.media3.extractor.ExtractorInput;
|
||||
import androidx.media3.extractor.PositionHolder;
|
||||
import androidx.media3.extractor.SeekMap;
|
||||
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
|
||||
import androidx.media3.test.utils.FakeExtractorOutput;
|
||||
import androidx.media3.test.utils.FakeTrackOutput;
|
||||
import androidx.media3.test.utils.TestUtil;
|
||||
@ -56,7 +57,9 @@ public final class TsExtractorSeekTest {
|
||||
positionHolder = new PositionHolder();
|
||||
expectedTrackOutput =
|
||||
TestUtil.extractAllSamplesFromFile(
|
||||
new TsExtractor(), ApplicationProvider.getApplicationContext(), TEST_FILE)
|
||||
new TsExtractor(new DefaultSubtitleParserFactory()),
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
TEST_FILE)
|
||||
.trackOutputs
|
||||
.get(AUDIO_TRACK_ID);
|
||||
|
||||
@ -68,7 +71,7 @@ public final class TsExtractorSeekTest {
|
||||
@Test
|
||||
public void tsExtractorReads_nonSeekTableFile_returnSeekableSeekMap() throws IOException {
|
||||
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
|
||||
TsExtractor extractor = new TsExtractor();
|
||||
TsExtractor extractor = new TsExtractor(new DefaultSubtitleParserFactory());
|
||||
|
||||
SeekMap seekMap =
|
||||
TestUtil.extractSeekMap(extractor, new FakeExtractorOutput(), dataSource, fileUri);
|
||||
@ -81,7 +84,7 @@ public final class TsExtractorSeekTest {
|
||||
@Test
|
||||
public void handlePendingSeek_handlesSeekingToPositionInFile_extractsCorrectFrame()
|
||||
throws IOException {
|
||||
TsExtractor extractor = new TsExtractor();
|
||||
TsExtractor extractor = new TsExtractor(new DefaultSubtitleParserFactory());
|
||||
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
|
||||
|
||||
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
|
||||
@ -100,7 +103,7 @@ public final class TsExtractorSeekTest {
|
||||
|
||||
@Test
|
||||
public void handlePendingSeek_handlesSeekToEoF_extractsLastFrame() throws IOException {
|
||||
TsExtractor extractor = new TsExtractor();
|
||||
TsExtractor extractor = new TsExtractor(new DefaultSubtitleParserFactory());
|
||||
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
|
||||
|
||||
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
|
||||
@ -120,7 +123,7 @@ public final class TsExtractorSeekTest {
|
||||
|
||||
@Test
|
||||
public void handlePendingSeek_handlesSeekingBackward_extractsCorrectFrame() throws IOException {
|
||||
TsExtractor extractor = new TsExtractor();
|
||||
TsExtractor extractor = new TsExtractor(new DefaultSubtitleParserFactory());
|
||||
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
|
||||
|
||||
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
|
||||
@ -142,7 +145,7 @@ public final class TsExtractorSeekTest {
|
||||
|
||||
@Test
|
||||
public void handlePendingSeek_handlesSeekingForward_extractsCorrectFrame() throws IOException {
|
||||
TsExtractor extractor = new TsExtractor();
|
||||
TsExtractor extractor = new TsExtractor(new DefaultSubtitleParserFactory());
|
||||
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
|
||||
|
||||
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
|
||||
@ -164,7 +167,7 @@ public final class TsExtractorSeekTest {
|
||||
|
||||
@Test
|
||||
public void handlePendingSeek_handlesRandomSeeks_extractsCorrectFrame() throws IOException {
|
||||
TsExtractor extractor = new TsExtractor();
|
||||
TsExtractor extractor = new TsExtractor(new DefaultSubtitleParserFactory());
|
||||
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
|
||||
|
||||
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
|
||||
@ -187,7 +190,7 @@ public final class TsExtractorSeekTest {
|
||||
@Test
|
||||
public void handlePendingSeek_handlesRandomSeeksAfterReadingFileOnce_extractsCorrectFrame()
|
||||
throws IOException {
|
||||
TsExtractor extractor = new TsExtractor();
|
||||
TsExtractor extractor = new TsExtractor(new DefaultSubtitleParserFactory());
|
||||
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
|
||||
|
||||
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
|
||||
|
@ -15,7 +15,12 @@
|
||||
*/
|
||||
package androidx.media3.extractor.ts;
|
||||
|
||||
import static androidx.media3.extractor.mp4.FragmentedMp4Extractor.FLAG_EMIT_RAW_SUBTITLE_DATA;
|
||||
import static androidx.media3.extractor.ts.DefaultTsPayloadReaderFactory.FLAG_DETECT_ACCESS_UNITS;
|
||||
import static androidx.media3.extractor.ts.DefaultTsPayloadReaderFactory.FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS;
|
||||
import static androidx.media3.extractor.ts.TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES;
|
||||
import static androidx.media3.extractor.ts.TsExtractor.MODE_MULTI_PMT;
|
||||
import static androidx.media3.extractor.ts.TsExtractor.MODE_SINGLE_PMT;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.util.SparseArray;
|
||||
@ -28,6 +33,8 @@ import androidx.media3.extractor.Extractor;
|
||||
import androidx.media3.extractor.ExtractorOutput;
|
||||
import androidx.media3.extractor.PositionHolder;
|
||||
import androidx.media3.extractor.TrackOutput;
|
||||
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
|
||||
import androidx.media3.extractor.text.SubtitleParser;
|
||||
import androidx.media3.extractor.ts.TsPayloadReader.EsInfo;
|
||||
import androidx.media3.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||
import androidx.media3.test.utils.ExtractorAsserts;
|
||||
@ -36,7 +43,8 @@ import androidx.media3.test.utils.FakeExtractorOutput;
|
||||
import androidx.media3.test.utils.FakeTrackOutput;
|
||||
import androidx.media3.test.utils.TestUtil;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||
@ -47,39 +55,62 @@ import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
public final class TsExtractorTest {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static ImmutableList<ExtractorAsserts.SimulationConfig> params() {
|
||||
return ExtractorAsserts.configs();
|
||||
@Parameters(name = "{0},subtitlesParsedDuringExtraction={1}")
|
||||
public static List<Object[]> params() {
|
||||
List<Object[]> parameterList = new ArrayList<>();
|
||||
for (ExtractorAsserts.SimulationConfig config : ExtractorAsserts.configs()) {
|
||||
parameterList.add(new Object[] {config, /* subtitlesParsedDuringExtraction */ true});
|
||||
parameterList.add(new Object[] {config, /* subtitlesParsedDuringExtraction */ false});
|
||||
}
|
||||
return parameterList;
|
||||
}
|
||||
|
||||
@Parameter public ExtractorAsserts.SimulationConfig simulationConfig;
|
||||
@Parameter(0)
|
||||
public ExtractorAsserts.SimulationConfig simulationConfig;
|
||||
|
||||
@Parameter(1)
|
||||
public boolean subtitlesParsedDuringExtraction;
|
||||
|
||||
@Test
|
||||
public void sampleWithH262AndMpegAudio() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(
|
||||
TsExtractor::new, "media/ts/sample_h262_mpeg_audio.ts", simulationConfig);
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_h262_mpeg_audio.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sampleWithH263() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(TsExtractor::new, "media/ts/sample_h263.ts", simulationConfig);
|
||||
ExtractorAsserts.assertBehavior(
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_h263.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sampleWithH264() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(TsExtractor::new, "media/ts/sample_h264.ts", simulationConfig);
|
||||
ExtractorAsserts.assertBehavior(
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_h264.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sampleWithH264AndMpegAudio() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(
|
||||
TsExtractor::new, "media/ts/sample_h264_mpeg_audio.ts", simulationConfig);
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_h264_mpeg_audio.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sampleWithH264NoAccessUnitDelimiters() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(
|
||||
() -> new TsExtractor(FLAG_DETECT_ACCESS_UNITS),
|
||||
getExtractorFactory(
|
||||
subtitlesParsedDuringExtraction,
|
||||
MODE_SINGLE_PMT,
|
||||
new TimestampAdjuster(0),
|
||||
new DefaultTsPayloadReaderFactory(FLAG_DETECT_ACCESS_UNITS)),
|
||||
"media/ts/sample_h264_no_access_unit_delimiters.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
@ -87,26 +118,35 @@ public final class TsExtractorTest {
|
||||
@Test
|
||||
public void sampleWithH264AndDtsAudio() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(
|
||||
() -> new TsExtractor(DefaultTsPayloadReaderFactory.FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS),
|
||||
getExtractorFactory(
|
||||
subtitlesParsedDuringExtraction,
|
||||
MODE_SINGLE_PMT,
|
||||
new TimestampAdjuster(0),
|
||||
new DefaultTsPayloadReaderFactory(FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS)),
|
||||
"media/ts/sample_h264_dts_audio.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sampleWithH265() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(TsExtractor::new, "media/ts/sample_h265.ts", simulationConfig);
|
||||
ExtractorAsserts.assertBehavior(
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_h265.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sampleWithH265RpsPred() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(
|
||||
TsExtractor::new, "media/ts/sample_h265_rps_pred.ts", simulationConfig);
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_h265_rps_pred.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sampleWithScte35() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(
|
||||
TsExtractor::new,
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_scte35.ts",
|
||||
new ExtractorAsserts.AssertionConfig.Builder()
|
||||
.setDeduplicateConsecutiveFormats(true)
|
||||
@ -117,7 +157,7 @@ public final class TsExtractorTest {
|
||||
@Test
|
||||
public void sampleWithAit() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(
|
||||
TsExtractor::new,
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_ait.ts",
|
||||
new ExtractorAsserts.AssertionConfig.Builder()
|
||||
.setDeduplicateConsecutiveFormats(true)
|
||||
@ -127,41 +167,63 @@ public final class TsExtractorTest {
|
||||
|
||||
@Test
|
||||
public void sampleWithAc3() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(TsExtractor::new, "media/ts/sample_ac3.ts", simulationConfig);
|
||||
ExtractorAsserts.assertBehavior(
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_ac3.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sampleWithAc4() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(TsExtractor::new, "media/ts/sample_ac4.ts", simulationConfig);
|
||||
ExtractorAsserts.assertBehavior(
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_ac4.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sampleWithEac3() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(TsExtractor::new, "media/ts/sample_eac3.ts", simulationConfig);
|
||||
ExtractorAsserts.assertBehavior(
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_eac3.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sampleWithEac3joc() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(
|
||||
TsExtractor::new, "media/ts/sample_eac3joc.ts", simulationConfig);
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_eac3joc.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sampleWithLatm() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(TsExtractor::new, "media/ts/sample_latm.ts", simulationConfig);
|
||||
ExtractorAsserts.assertBehavior(
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_latm.ts",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void streamWithJunkData() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(
|
||||
TsExtractor::new, "media/ts/sample_with_junk", simulationConfig);
|
||||
getExtractorFactory(subtitlesParsedDuringExtraction),
|
||||
"media/ts/sample_with_junk",
|
||||
simulationConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customPesReader() throws Exception {
|
||||
CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(true, false);
|
||||
TsExtractor tsExtractor =
|
||||
new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory);
|
||||
(TsExtractor)
|
||||
getExtractorFactory(
|
||||
subtitlesParsedDuringExtraction,
|
||||
MODE_MULTI_PMT,
|
||||
new TimestampAdjuster(0),
|
||||
factory)
|
||||
.create();
|
||||
FakeExtractorInput input =
|
||||
new FakeExtractorInput.Builder()
|
||||
.setData(
|
||||
@ -199,7 +261,13 @@ public final class TsExtractorTest {
|
||||
public void customInitialSectionReader() throws Exception {
|
||||
CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true);
|
||||
TsExtractor tsExtractor =
|
||||
new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory);
|
||||
(TsExtractor)
|
||||
getExtractorFactory(
|
||||
subtitlesParsedDuringExtraction,
|
||||
MODE_MULTI_PMT,
|
||||
new TimestampAdjuster(0),
|
||||
factory)
|
||||
.create();
|
||||
FakeExtractorInput input =
|
||||
new FakeExtractorInput.Builder()
|
||||
.setData(
|
||||
@ -221,6 +289,39 @@ public final class TsExtractorTest {
|
||||
assertThat(factory.sdtReader.consumedSdts).isEqualTo(2);
|
||||
}
|
||||
|
||||
private static ExtractorAsserts.ExtractorFactory getExtractorFactory(
|
||||
boolean subtitlesParsedDuringExtraction) {
|
||||
return getExtractorFactory(
|
||||
subtitlesParsedDuringExtraction,
|
||||
MODE_SINGLE_PMT,
|
||||
new TimestampAdjuster(0),
|
||||
new DefaultTsPayloadReaderFactory(0));
|
||||
}
|
||||
|
||||
private static ExtractorAsserts.ExtractorFactory getExtractorFactory(
|
||||
boolean subtitlesParsedDuringExtraction,
|
||||
@TsExtractor.Mode int mode,
|
||||
TimestampAdjuster timestampAdjuster,
|
||||
TsPayloadReader.Factory payloadReaderFactory) {
|
||||
SubtitleParser.Factory subtitleParserFactory;
|
||||
@TsExtractor.Flags int flags;
|
||||
if (subtitlesParsedDuringExtraction) {
|
||||
subtitleParserFactory = new DefaultSubtitleParserFactory();
|
||||
flags = 0;
|
||||
} else {
|
||||
subtitleParserFactory = SubtitleParser.Factory.UNSUPPORTED;
|
||||
flags = FLAG_EMIT_RAW_SUBTITLE_DATA;
|
||||
}
|
||||
return () ->
|
||||
new TsExtractor(
|
||||
mode,
|
||||
flags,
|
||||
subtitleParserFactory,
|
||||
timestampAdjuster,
|
||||
payloadReaderFactory,
|
||||
DEFAULT_TIMESTAMP_SEARCH_BYTES);
|
||||
}
|
||||
|
||||
private static final class CustomTsPayloadReaderFactory implements TsPayloadReader.Factory {
|
||||
|
||||
private final boolean provideSdtReader;
|
||||
|
Loading…
x
Reference in New Issue
Block a user