Merge pull request #7370 from jruesga:embedded-cea-708-support

PiperOrigin-RevId: 312090461
This commit is contained in:
Oliver Woodman 2020-05-18 22:38:10 +01:00
commit 3db703a983
3 changed files with 132 additions and 37 deletions

View File

@ -156,6 +156,7 @@
`http://dashif.org/guidelines/trickmode`) into the same `TrackGroup` as
the main adaptation sets to which they refer. Trick play tracks are
marked with the `C.ROLE_FLAG_TRICK_PLAY` flag.
* Enable support for embedded CEA-708.
* Fix assertion failure in `SampleQueue` when playing DASH streams with
EMSG tracks ([#7273](https://github.com/google/ExoPlayer/issues/7273)).
* MP3:

View File

@ -69,7 +69,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
SequenceableLoader.Callback<ChunkSampleStream<DashChunkSource>>,
ChunkSampleStream.ReleaseCallback<DashChunkSource> {
// Defined by ANSI/SCTE 214-1 2016 7.2.3.
private static final Pattern CEA608_SERVICE_DESCRIPTOR_REGEX = Pattern.compile("CC([1-4])=(.+)");
// Defined by ANSI/SCTE 214-1 2016 7.2.2.
private static final Pattern CEA708_SERVICE_DESCRIPTOR_REGEX =
Pattern.compile("([1-4])=lang:(\\w+)(,.+)?");
/* package */ final int id;
private final DashChunkSource.Factory chunkSourceFactory;
@ -835,49 +839,52 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
for (int j = 0; j < descriptors.size(); j++) {
Descriptor descriptor = descriptors.get(j);
if ("urn:scte:dash:cc:cea-608:2015".equals(descriptor.schemeIdUri)) {
@Nullable String value = descriptor.value;
if (value == null) {
// There are embedded CEA-608 tracks, but service information is not declared.
return new Format[] {buildCea608TrackFormat(adaptationSet.id)};
}
String[] services = Util.split(value, ";");
Format[] formats = new Format[services.length];
for (int k = 0; k < services.length; k++) {
Matcher matcher = CEA608_SERVICE_DESCRIPTOR_REGEX.matcher(services[k]);
if (!matcher.matches()) {
// If we can't parse service information for all services, assume a single track.
return new Format[] {buildCea608TrackFormat(adaptationSet.id)};
}
formats[k] =
buildCea608TrackFormat(
adaptationSet.id,
/* language= */ matcher.group(2),
/* accessibilityChannel= */ Integer.parseInt(matcher.group(1)));
}
return formats;
Format cea608Format =
new Format.Builder()
.setSampleMimeType(MimeTypes.APPLICATION_CEA608)
.setId(adaptationSet.id + ":cea608")
.build();
return parseClosedCaptionDescriptor(
descriptor, CEA608_SERVICE_DESCRIPTOR_REGEX, cea608Format);
} else if ("urn:scte:dash:cc:cea-708:2015".equals(descriptor.schemeIdUri)) {
Format cea708Format =
new Format.Builder()
.setSampleMimeType(MimeTypes.APPLICATION_CEA708)
.setId(adaptationSet.id + ":cea708")
.build();
return parseClosedCaptionDescriptor(
descriptor, CEA708_SERVICE_DESCRIPTOR_REGEX, cea708Format);
}
}
}
return new Format[0];
}
private static Format buildCea608TrackFormat(int adaptationSetId) {
return buildCea608TrackFormat(
adaptationSetId, /* language= */ null, /* accessibilityChannel= */ Format.NO_VALUE);
}
private static Format buildCea608TrackFormat(
int adaptationSetId, @Nullable String language, int accessibilityChannel) {
String id =
adaptationSetId
+ ":cea608"
+ (accessibilityChannel != Format.NO_VALUE ? ":" + accessibilityChannel : "");
return new Format.Builder()
.setId(id)
.setSampleMimeType(MimeTypes.APPLICATION_CEA608)
.setLanguage(language)
.setAccessibilityChannel(accessibilityChannel)
.build();
private static Format[] parseClosedCaptionDescriptor(
Descriptor descriptor, Pattern serviceDescriptorRegex, Format baseFormat) {
@Nullable String value = descriptor.value;
if (value == null) {
// There are embedded closed caption tracks, but service information is not declared.
return new Format[] {baseFormat};
}
String[] services = Util.split(value, ";");
Format[] formats = new Format[services.length];
for (int i = 0; i < services.length; i++) {
Matcher matcher = serviceDescriptorRegex.matcher(services[i]);
if (!matcher.matches()) {
// If we can't parse service information for all services, assume a single track.
return new Format[] {baseFormat};
}
int accessibilityChannel = Integer.parseInt(matcher.group(1));
formats[i] =
baseFormat
.buildUpon()
.setId(baseFormat.id + ":" + accessibilityChannel)
.setAccessibilityChannel(accessibilityChannel)
.setLanguage(matcher.group(2))
.build();
}
return formats;
}
// We won't assign the array to a variable that erases the generic type, and then write into it.

View File

@ -256,6 +256,93 @@ public final class DashMediaPeriodTest {
MediaPeriodAsserts.assertTrackGroups(dashMediaPeriod, expectedTrackGroups);
}
@Test
public void cea608AccessibilityDescriptor_createsCea608TrackGroup() {
Descriptor descriptor =
new Descriptor("urn:scte:dash:cc:cea-608:2015", "CC1=eng;CC3=deu", /* id= */ null);
DashManifest manifest =
createDashManifest(
createPeriod(
new AdaptationSet(
/* id= */ 123,
C.TRACK_TYPE_VIDEO,
Arrays.asList(
createVideoRepresentation(/* bitrate= */ 0),
createVideoRepresentation(/* bitrate= */ 1)),
/* accessibilityDescriptors= */ Collections.singletonList(descriptor),
/* essentialProperties= */ Collections.emptyList(),
/* supplementalProperties= */ Collections.emptyList())));
DashMediaPeriod dashMediaPeriod = createDashMediaPeriod(manifest, 0);
List<AdaptationSet> adaptationSets = manifest.getPeriod(0).adaptationSets;
// We expect two adaptation sets. The first containing the video representations, and the second
// containing the embedded CEA-608 tracks.
Format.Builder cea608FormatBuilder =
new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_CEA608);
TrackGroupArray expectedTrackGroups =
new TrackGroupArray(
new TrackGroup(
adaptationSets.get(0).representations.get(0).format,
adaptationSets.get(0).representations.get(1).format),
new TrackGroup(
cea608FormatBuilder
.setId("123:cea608:1")
.setLanguage("eng")
.setAccessibilityChannel(1)
.build(),
cea608FormatBuilder
.setId("123:cea608:3")
.setLanguage("deu")
.setAccessibilityChannel(3)
.build()));
MediaPeriodAsserts.assertTrackGroups(dashMediaPeriod, expectedTrackGroups);
}
@Test
public void cea708AccessibilityDescriptor_createsCea708TrackGroup() {
Descriptor descriptor =
new Descriptor(
"urn:scte:dash:cc:cea-708:2015", "1=lang:eng;2=lang:deu,war:1,er:1", /* id= */ null);
DashManifest manifest =
createDashManifest(
createPeriod(
new AdaptationSet(
/* id= */ 123,
C.TRACK_TYPE_VIDEO,
Arrays.asList(
createVideoRepresentation(/* bitrate= */ 0),
createVideoRepresentation(/* bitrate= */ 1)),
/* accessibilityDescriptors= */ Collections.singletonList(descriptor),
/* essentialProperties= */ Collections.emptyList(),
/* supplementalProperties= */ Collections.emptyList())));
DashMediaPeriod dashMediaPeriod = createDashMediaPeriod(manifest, 0);
List<AdaptationSet> adaptationSets = manifest.getPeriod(0).adaptationSets;
// We expect two adaptation sets. The first containing the video representations, and the second
// containing the embedded CEA-708 tracks.
Format.Builder cea608FormatBuilder =
new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_CEA708);
TrackGroupArray expectedTrackGroups =
new TrackGroupArray(
new TrackGroup(
adaptationSets.get(0).representations.get(0).format,
adaptationSets.get(0).representations.get(1).format),
new TrackGroup(
cea608FormatBuilder
.setId("123:cea708:1")
.setLanguage("eng")
.setAccessibilityChannel(1)
.build(),
cea608FormatBuilder
.setId("123:cea708:2")
.setLanguage("deu")
.setAccessibilityChannel(2)
.build()));
MediaPeriodAsserts.assertTrackGroups(dashMediaPeriod, expectedTrackGroups);
}
private static DashMediaPeriod createDashMediaPeriod(DashManifest manifest, int periodIndex) {
return new DashMediaPeriod(
/* id= */ periodIndex,