diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/DefaultHlsExtractorFactory.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/DefaultHlsExtractorFactory.java index 8ecd1d667e..742fdcd543 100644 --- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/DefaultHlsExtractorFactory.java +++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/DefaultHlsExtractorFactory.java @@ -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 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( diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/DefaultExtractorsFactory.java b/libraries/extractor/src/main/java/androidx/media3/extractor/DefaultExtractorsFactory.java index 73b0ea9a2d..9cc511e097 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/DefaultExtractorsFactory.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/DefaultExtractorsFactory.java @@ -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)); diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/TsExtractor.java b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/TsExtractor.java index d29c77fc84..7ff5fa4a60 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/TsExtractor.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/TsExtractor.java @@ -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 timestampAdjusters; private final ParsableByteArray tsPacketBuffer; private final SparseIntArray continuityCounters; private final TsPayloadReader.Factory payloadReaderFactory; + private final SubtitleParser.Factory subtitleParserFactory; private final SparseArray 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 diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/DefaultExtractorsFactoryTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/DefaultExtractorsFactoryTest.java index d6a9d82951..ddc9ff3d27 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/DefaultExtractorsFactoryTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/DefaultExtractorsFactoryTest.java @@ -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> getUnderlyingExtractorClasses( diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/ts/TsExtractorSeekTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/ts/TsExtractorSeekTest.java index 014013376a..03cd6b9d26 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/ts/TsExtractorSeekTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/ts/TsExtractorSeekTest.java @@ -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(); diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/ts/TsExtractorTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/ts/TsExtractorTest.java index 4ee83354a3..61e2633446 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/ts/TsExtractorTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/ts/TsExtractorTest.java @@ -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 params() { - return ExtractorAsserts.configs(); + @Parameters(name = "{0},subtitlesParsedDuringExtraction={1}") + public static List params() { + List 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;