Fix DASH CEA-608 parsing during extraction
This is similar to the HLS fix in 770ca66fbc
Similar to HLS, the original problem here was **not** modifying the
`Format` for caption tracks
embedded into the video stream. I tried just updating the format in
both places, but that caused new failures because the new
('transcoded') format was then fed into `FragmentedMp4Extractor` as
part of `closedCaptionFormats`, which resulted in the CEA-608 data
being emitted from `FragmentedMp4Extractor` with the incorrect
`application/x-media3-cues` MIME type (but the bytes were actually
CEA-608), meaning the transcoding wrapper passed it through without
transcoding and decoding failed (because obviously CEA-608 bytes can't
be decoded by `CueDecoder` which is expecting a `Bundle` from
`CuesWithTiming.toBundle`.
To resolve this we keep track of the 'original' caption formats inside
`TrackGroupInfo`, so we can feed them into `FragmentedMp4Extractor`.
For all other usages in `DashMediaPeriod` we use the 'transcoded'
caption formats.
PiperOrigin-RevId: 592866262
This commit is contained in:
parent
60c8273521
commit
f36ab87b38
@ -59,6 +59,7 @@ import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
|||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||||
import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
|
import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
|
||||||
import androidx.media3.extractor.text.SubtitleParser;
|
import androidx.media3.extractor.text.SubtitleParser;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -689,18 +690,6 @@ import java.util.regex.Pattern;
|
|||||||
originalFormat
|
originalFormat
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
.setCryptoType(drmSessionManager.getCryptoType(originalFormat));
|
.setCryptoType(drmSessionManager.getCryptoType(originalFormat));
|
||||||
if (subtitleParserFactory != null && subtitleParserFactory.supportsFormat(originalFormat)) {
|
|
||||||
updatedFormat
|
|
||||||
.setSampleMimeType(MimeTypes.APPLICATION_MEDIA3_CUES)
|
|
||||||
.setCueReplacementBehavior(
|
|
||||||
subtitleParserFactory.getCueReplacementBehavior(originalFormat))
|
|
||||||
.setCodecs(
|
|
||||||
originalFormat.sampleMimeType
|
|
||||||
+ (originalFormat.codecs != null ? " " + originalFormat.codecs : ""))
|
|
||||||
// Reset this value to the default. All non-default timestamp adjustments are done
|
|
||||||
// by SubtitleTranscodingExtractor and there are no 'subsamples' after transcoding.
|
|
||||||
.setSubsampleOffsetUs(Format.OFFSET_SAMPLE_RELATIVE);
|
|
||||||
}
|
|
||||||
formats[j] = updatedFormat.build();
|
formats[j] = updatedFormat.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -715,6 +704,7 @@ import java.util.regex.Pattern;
|
|||||||
int closedCaptionTrackGroupIndex =
|
int closedCaptionTrackGroupIndex =
|
||||||
primaryGroupClosedCaptionTrackFormats[i].length != 0 ? trackGroupCount++ : C.INDEX_UNSET;
|
primaryGroupClosedCaptionTrackFormats[i].length != 0 ? trackGroupCount++ : C.INDEX_UNSET;
|
||||||
|
|
||||||
|
maybeUpdateFormatsForParsedText(subtitleParserFactory, formats);
|
||||||
trackGroups[primaryTrackGroupIndex] = new TrackGroup(trackGroupId, formats);
|
trackGroups[primaryTrackGroupIndex] = new TrackGroup(trackGroupId, formats);
|
||||||
trackGroupInfos[primaryTrackGroupIndex] =
|
trackGroupInfos[primaryTrackGroupIndex] =
|
||||||
TrackGroupInfo.primaryTrack(
|
TrackGroupInfo.primaryTrack(
|
||||||
@ -736,10 +726,15 @@ import java.util.regex.Pattern;
|
|||||||
}
|
}
|
||||||
if (closedCaptionTrackGroupIndex != C.INDEX_UNSET) {
|
if (closedCaptionTrackGroupIndex != C.INDEX_UNSET) {
|
||||||
String closedCaptionTrackGroupId = trackGroupId + ":cc";
|
String closedCaptionTrackGroupId = trackGroupId + ":cc";
|
||||||
|
trackGroupInfos[closedCaptionTrackGroupIndex] =
|
||||||
|
TrackGroupInfo.embeddedClosedCaptionTrack(
|
||||||
|
adaptationSetIndices,
|
||||||
|
primaryTrackGroupIndex,
|
||||||
|
ImmutableList.copyOf(primaryGroupClosedCaptionTrackFormats[i]));
|
||||||
|
maybeUpdateFormatsForParsedText(
|
||||||
|
subtitleParserFactory, primaryGroupClosedCaptionTrackFormats[i]);
|
||||||
trackGroups[closedCaptionTrackGroupIndex] =
|
trackGroups[closedCaptionTrackGroupIndex] =
|
||||||
new TrackGroup(closedCaptionTrackGroupId, primaryGroupClosedCaptionTrackFormats[i]);
|
new TrackGroup(closedCaptionTrackGroupId, primaryGroupClosedCaptionTrackFormats[i]);
|
||||||
trackGroupInfos[closedCaptionTrackGroupIndex] =
|
|
||||||
TrackGroupInfo.embeddedClosedCaptionTrack(adaptationSetIndices, primaryTrackGroupIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return trackGroupCount;
|
return trackGroupCount;
|
||||||
@ -774,14 +769,12 @@ import java.util.regex.Pattern;
|
|||||||
trackGroups.get(trackGroupInfo.embeddedEventMessageTrackGroupIndex);
|
trackGroups.get(trackGroupInfo.embeddedEventMessageTrackGroupIndex);
|
||||||
embeddedTrackCount++;
|
embeddedTrackCount++;
|
||||||
}
|
}
|
||||||
boolean enableClosedCaptionTrack =
|
ImmutableList<Format> embeddedClosedCaptionOriginalFormats =
|
||||||
trackGroupInfo.embeddedClosedCaptionTrackGroupIndex != C.INDEX_UNSET;
|
trackGroupInfo.embeddedClosedCaptionTrackGroupIndex != C.INDEX_UNSET
|
||||||
TrackGroup embeddedClosedCaptionTrackGroup = null;
|
? trackGroupInfos[trackGroupInfo.embeddedClosedCaptionTrackGroupIndex]
|
||||||
if (enableClosedCaptionTrack) {
|
.embeddedClosedCaptionTrackOriginalFormats
|
||||||
embeddedClosedCaptionTrackGroup =
|
: ImmutableList.of();
|
||||||
trackGroups.get(trackGroupInfo.embeddedClosedCaptionTrackGroupIndex);
|
embeddedTrackCount += embeddedClosedCaptionOriginalFormats.size();
|
||||||
embeddedTrackCount += embeddedClosedCaptionTrackGroup.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
Format[] embeddedTrackFormats = new Format[embeddedTrackCount];
|
Format[] embeddedTrackFormats = new Format[embeddedTrackCount];
|
||||||
int[] embeddedTrackTypes = new int[embeddedTrackCount];
|
int[] embeddedTrackTypes = new int[embeddedTrackCount];
|
||||||
@ -792,14 +785,12 @@ import java.util.regex.Pattern;
|
|||||||
embeddedTrackCount++;
|
embeddedTrackCount++;
|
||||||
}
|
}
|
||||||
List<Format> embeddedClosedCaptionTrackFormats = new ArrayList<>();
|
List<Format> embeddedClosedCaptionTrackFormats = new ArrayList<>();
|
||||||
if (enableClosedCaptionTrack) {
|
for (int i = 0; i < embeddedClosedCaptionOriginalFormats.size(); i++) {
|
||||||
for (int i = 0; i < embeddedClosedCaptionTrackGroup.length; i++) {
|
embeddedTrackFormats[embeddedTrackCount] = embeddedClosedCaptionOriginalFormats.get(i);
|
||||||
embeddedTrackFormats[embeddedTrackCount] = embeddedClosedCaptionTrackGroup.getFormat(i);
|
|
||||||
embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_TEXT;
|
embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_TEXT;
|
||||||
embeddedClosedCaptionTrackFormats.add(embeddedTrackFormats[embeddedTrackCount]);
|
embeddedClosedCaptionTrackFormats.add(embeddedTrackFormats[embeddedTrackCount]);
|
||||||
embeddedTrackCount++;
|
embeddedTrackCount++;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
PlayerTrackEmsgHandler trackPlayerEmsgHandler =
|
PlayerTrackEmsgHandler trackPlayerEmsgHandler =
|
||||||
manifest.dynamic && enableEventMessageTrack
|
manifest.dynamic && enableEventMessageTrack
|
||||||
@ -932,6 +923,30 @@ import java.util.regex.Pattern;
|
|||||||
return formats;
|
return formats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the provided {@link Format} array if subtitle/caption parsing is configured to happen
|
||||||
|
* during extraction.
|
||||||
|
*/
|
||||||
|
private static void maybeUpdateFormatsForParsedText(
|
||||||
|
SubtitleParser.Factory subtitleParserFactory, Format[] formats) {
|
||||||
|
for (int i = 0; i < formats.length; i++) {
|
||||||
|
if (subtitleParserFactory == null || !subtitleParserFactory.supportsFormat(formats[i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
formats[i] =
|
||||||
|
formats[i]
|
||||||
|
.buildUpon()
|
||||||
|
.setSampleMimeType(MimeTypes.APPLICATION_MEDIA3_CUES)
|
||||||
|
.setCueReplacementBehavior(
|
||||||
|
subtitleParserFactory.getCueReplacementBehavior(formats[i]))
|
||||||
|
.setCodecs(
|
||||||
|
formats[i].sampleMimeType
|
||||||
|
+ (formats[i].codecs != null ? " " + formats[i].codecs : ""))
|
||||||
|
.setSubsampleOffsetUs(Format.OFFSET_SAMPLE_RELATIVE)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We won't assign the array to a variable that erases the generic type, and then write into it.
|
// We won't assign the array to a variable that erases the generic type, and then write into it.
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
private static ChunkSampleStream<DashChunkSource>[] newSampleStreamArray(int length) {
|
private static ChunkSampleStream<DashChunkSource>[] newSampleStreamArray(int length) {
|
||||||
@ -973,6 +988,9 @@ import java.util.regex.Pattern;
|
|||||||
public final int embeddedEventMessageTrackGroupIndex;
|
public final int embeddedEventMessageTrackGroupIndex;
|
||||||
public final int embeddedClosedCaptionTrackGroupIndex;
|
public final int embeddedClosedCaptionTrackGroupIndex;
|
||||||
|
|
||||||
|
/** Only non-empty for track groups representing embedded caption tracks. */
|
||||||
|
public final ImmutableList<Format> embeddedClosedCaptionTrackOriginalFormats;
|
||||||
|
|
||||||
public static TrackGroupInfo primaryTrack(
|
public static TrackGroupInfo primaryTrack(
|
||||||
int trackType,
|
int trackType,
|
||||||
int[] adaptationSetIndices,
|
int[] adaptationSetIndices,
|
||||||
@ -986,7 +1004,8 @@ import java.util.regex.Pattern;
|
|||||||
primaryTrackGroupIndex,
|
primaryTrackGroupIndex,
|
||||||
embeddedEventMessageTrackGroupIndex,
|
embeddedEventMessageTrackGroupIndex,
|
||||||
embeddedClosedCaptionTrackGroupIndex,
|
embeddedClosedCaptionTrackGroupIndex,
|
||||||
/* eventStreamGroupIndex= */ -1);
|
/* eventStreamGroupIndex= */ -1,
|
||||||
|
/* embeddedClosedCaptionTrackOriginalFormats= */ ImmutableList.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TrackGroupInfo embeddedEmsgTrack(
|
public static TrackGroupInfo embeddedEmsgTrack(
|
||||||
@ -998,11 +1017,14 @@ import java.util.regex.Pattern;
|
|||||||
primaryTrackGroupIndex,
|
primaryTrackGroupIndex,
|
||||||
C.INDEX_UNSET,
|
C.INDEX_UNSET,
|
||||||
C.INDEX_UNSET,
|
C.INDEX_UNSET,
|
||||||
/* eventStreamGroupIndex= */ -1);
|
/* eventStreamGroupIndex= */ -1,
|
||||||
|
/* embeddedClosedCaptionTrackOriginalFormats= */ ImmutableList.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TrackGroupInfo embeddedClosedCaptionTrack(
|
public static TrackGroupInfo embeddedClosedCaptionTrack(
|
||||||
int[] adaptationSetIndices, int primaryTrackGroupIndex) {
|
int[] adaptationSetIndices,
|
||||||
|
int primaryTrackGroupIndex,
|
||||||
|
ImmutableList<Format> originalFormats) {
|
||||||
return new TrackGroupInfo(
|
return new TrackGroupInfo(
|
||||||
C.TRACK_TYPE_TEXT,
|
C.TRACK_TYPE_TEXT,
|
||||||
CATEGORY_EMBEDDED,
|
CATEGORY_EMBEDDED,
|
||||||
@ -1010,7 +1032,8 @@ import java.util.regex.Pattern;
|
|||||||
primaryTrackGroupIndex,
|
primaryTrackGroupIndex,
|
||||||
C.INDEX_UNSET,
|
C.INDEX_UNSET,
|
||||||
C.INDEX_UNSET,
|
C.INDEX_UNSET,
|
||||||
/* eventStreamGroupIndex= */ -1);
|
/* eventStreamGroupIndex= */ -1,
|
||||||
|
originalFormats);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TrackGroupInfo mpdEventTrack(int eventStreamIndex) {
|
public static TrackGroupInfo mpdEventTrack(int eventStreamIndex) {
|
||||||
@ -1021,7 +1044,8 @@ import java.util.regex.Pattern;
|
|||||||
/* primaryTrackGroupIndex= */ -1,
|
/* primaryTrackGroupIndex= */ -1,
|
||||||
C.INDEX_UNSET,
|
C.INDEX_UNSET,
|
||||||
C.INDEX_UNSET,
|
C.INDEX_UNSET,
|
||||||
eventStreamIndex);
|
eventStreamIndex,
|
||||||
|
/* embeddedClosedCaptionTrackOriginalFormats= */ ImmutableList.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrackGroupInfo(
|
private TrackGroupInfo(
|
||||||
@ -1031,7 +1055,8 @@ import java.util.regex.Pattern;
|
|||||||
int primaryTrackGroupIndex,
|
int primaryTrackGroupIndex,
|
||||||
int embeddedEventMessageTrackGroupIndex,
|
int embeddedEventMessageTrackGroupIndex,
|
||||||
int embeddedClosedCaptionTrackGroupIndex,
|
int embeddedClosedCaptionTrackGroupIndex,
|
||||||
int eventStreamGroupIndex) {
|
int eventStreamGroupIndex,
|
||||||
|
ImmutableList<Format> embeddedClosedCaptionTrackOriginalFormats) {
|
||||||
this.trackType = trackType;
|
this.trackType = trackType;
|
||||||
this.adaptationSetIndices = adaptationSetIndices;
|
this.adaptationSetIndices = adaptationSetIndices;
|
||||||
this.trackGroupCategory = trackGroupCategory;
|
this.trackGroupCategory = trackGroupCategory;
|
||||||
@ -1039,6 +1064,7 @@ import java.util.regex.Pattern;
|
|||||||
this.embeddedEventMessageTrackGroupIndex = embeddedEventMessageTrackGroupIndex;
|
this.embeddedEventMessageTrackGroupIndex = embeddedEventMessageTrackGroupIndex;
|
||||||
this.embeddedClosedCaptionTrackGroupIndex = embeddedClosedCaptionTrackGroupIndex;
|
this.embeddedClosedCaptionTrackGroupIndex = embeddedClosedCaptionTrackGroupIndex;
|
||||||
this.eventStreamGroupIndex = eventStreamGroupIndex;
|
this.eventStreamGroupIndex = eventStreamGroupIndex;
|
||||||
|
this.embeddedClosedCaptionTrackOriginalFormats = embeddedClosedCaptionTrackOriginalFormats;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,14 +167,53 @@ public final class DashPlaybackTest {
|
|||||||
applicationContext, playbackOutput, "playbackdumps/dash/ttml-in-mp4.dump");
|
applicationContext, playbackOutput, "playbackdumps/dash/ttml-in-mp4.dump");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test and {@link #cea608_parseDuringExtraction()} use the same output dump file, to
|
||||||
|
* demonstrate the flag has no effect on the resulting subtitles.
|
||||||
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void cea608() throws Exception {
|
public void cea608_parseDuringRendering() throws Exception {
|
||||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||||
CapturingRenderersFactory capturingRenderersFactory =
|
CapturingRenderersFactory capturingRenderersFactory =
|
||||||
new CapturingRenderersFactory(applicationContext);
|
new CapturingRenderersFactory(applicationContext);
|
||||||
// TODO(b/181312195): Opt this test into the new subtitle parsing when it works.
|
|
||||||
ExoPlayer player =
|
ExoPlayer player =
|
||||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||||
|
.setMediaSourceFactory(
|
||||||
|
new DashMediaSource.Factory(new DefaultDataSource.Factory(applicationContext))
|
||||||
|
.experimentalParseSubtitlesDuringExtraction(false))
|
||||||
|
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||||
|
.build();
|
||||||
|
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||||
|
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
|
||||||
|
|
||||||
|
// Ensure the subtitle track is selected.
|
||||||
|
DefaultTrackSelector trackSelector =
|
||||||
|
checkNotNull((DefaultTrackSelector) player.getTrackSelector());
|
||||||
|
trackSelector.setParameters(trackSelector.buildUponParameters().setPreferredTextLanguage("en"));
|
||||||
|
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/cea608/manifest.mpd"));
|
||||||
|
player.prepare();
|
||||||
|
player.play();
|
||||||
|
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||||
|
player.release();
|
||||||
|
|
||||||
|
DumpFileAsserts.assertOutput(
|
||||||
|
applicationContext, playbackOutput, "playbackdumps/dash/cea608.dump");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test and {@link #cea608_parseDuringRendering()} use the same output dump file, to
|
||||||
|
* demonstrate the flag has no effect on the resulting subtitles.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void cea608_parseDuringExtraction() throws Exception {
|
||||||
|
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||||
|
CapturingRenderersFactory capturingRenderersFactory =
|
||||||
|
new CapturingRenderersFactory(applicationContext);
|
||||||
|
ExoPlayer player =
|
||||||
|
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||||
|
.setMediaSourceFactory(
|
||||||
|
new DashMediaSource.Factory(new DefaultDataSource.Factory(applicationContext))
|
||||||
|
.experimentalParseSubtitlesDuringExtraction(true))
|
||||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||||
.build();
|
.build();
|
||||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user