-
Notifications
You must be signed in to change notification settings - Fork 6k
Enable embedded CEA-708 support #7370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,7 @@ | |
| ChunkSampleStream.ReleaseCallback<DashChunkSource> { | ||
|
|
||
| private static final Pattern CEA608_SERVICE_DESCRIPTOR_REGEX = Pattern.compile("CC([1-4])=(.+)"); | ||
| private static final Pattern CEA708_SERVICE_DESCRIPTOR_REGEX = Pattern.compile("([1-4])=lang:(\\w+)(,.+)?"); | ||
|
|
||
| /* package */ final int id; | ||
| private final DashChunkSource.Factory chunkSourceFactory; | ||
|
|
@@ -488,14 +489,14 @@ private static Pair<TrackGroupArray, TrackGroupInfo[]> buildTrackGroups( | |
|
|
||
| int primaryGroupCount = groupedAdaptationSetIndices.length; | ||
| boolean[] primaryGroupHasEventMessageTrackFlags = new boolean[primaryGroupCount]; | ||
| Format[][] primaryGroupCea608TrackFormats = new Format[primaryGroupCount][]; | ||
| Format[][] primaryGroupClosedCaptionsTrackFormats = new Format[primaryGroupCount][]; | ||
| int totalEmbeddedTrackGroupCount = | ||
| identifyEmbeddedTracks( | ||
| primaryGroupCount, | ||
| adaptationSets, | ||
| groupedAdaptationSetIndices, | ||
| primaryGroupHasEventMessageTrackFlags, | ||
| primaryGroupCea608TrackFormats); | ||
| primaryGroupClosedCaptionsTrackFormats); | ||
|
|
||
| int totalGroupCount = primaryGroupCount + totalEmbeddedTrackGroupCount + eventStreams.size(); | ||
| TrackGroup[] trackGroups = new TrackGroup[totalGroupCount]; | ||
|
|
@@ -508,7 +509,7 @@ private static Pair<TrackGroupArray, TrackGroupInfo[]> buildTrackGroups( | |
| groupedAdaptationSetIndices, | ||
| primaryGroupCount, | ||
| primaryGroupHasEventMessageTrackFlags, | ||
| primaryGroupCea608TrackFormats, | ||
| primaryGroupClosedCaptionsTrackFormats, | ||
| trackGroups, | ||
| trackGroupInfos); | ||
|
|
||
|
|
@@ -616,25 +617,25 @@ private static int[][] getGroupedAdaptationSetIndices(List<AdaptationSet> adapta | |
| * same primary group, grouped in primary track groups order. | ||
| * @param primaryGroupHasEventMessageTrackFlags An output array to be filled with flags indicating | ||
| * whether each of the primary track groups contains an embedded event message track. | ||
| * @param primaryGroupCea608TrackFormats An output array to be filled with track formats for | ||
| * CEA-608 tracks embedded in each of the primary track groups. | ||
| * @param primaryGroupClosedCaptionsTrackFormats An output array to be filled with track formats for | ||
| * closed captions tracks embedded in each of the primary track groups. | ||
| * @return Total number of embedded track groups. | ||
| */ | ||
| private static int identifyEmbeddedTracks( | ||
| int primaryGroupCount, | ||
| List<AdaptationSet> adaptationSets, | ||
| int[][] groupedAdaptationSetIndices, | ||
| boolean[] primaryGroupHasEventMessageTrackFlags, | ||
| Format[][] primaryGroupCea608TrackFormats) { | ||
| Format[][] primaryGroupClosedCaptionsTrackFormats) { | ||
| int numEmbeddedTrackGroups = 0; | ||
| for (int i = 0; i < primaryGroupCount; i++) { | ||
| if (hasEventMessageTrack(adaptationSets, groupedAdaptationSetIndices[i])) { | ||
| primaryGroupHasEventMessageTrackFlags[i] = true; | ||
| numEmbeddedTrackGroups++; | ||
| } | ||
| primaryGroupCea608TrackFormats[i] = | ||
| getCea608TrackFormats(adaptationSets, groupedAdaptationSetIndices[i]); | ||
| if (primaryGroupCea608TrackFormats[i].length != 0) { | ||
| primaryGroupClosedCaptionsTrackFormats[i] = | ||
| getClosedCaptionsTrackFormats(adaptationSets, groupedAdaptationSetIndices[i]); | ||
| if (primaryGroupClosedCaptionsTrackFormats[i].length != 0) { | ||
| numEmbeddedTrackGroups++; | ||
| } | ||
| } | ||
|
|
@@ -647,7 +648,7 @@ private static int buildPrimaryAndEmbeddedTrackGroupInfos( | |
| int[][] groupedAdaptationSetIndices, | ||
| int primaryGroupCount, | ||
| boolean[] primaryGroupHasEventMessageTrackFlags, | ||
| Format[][] primaryGroupCea608TrackFormats, | ||
| Format[][] primaryGroupClosedCaptionsTrackFormats, | ||
| TrackGroup[] trackGroups, | ||
| TrackGroupInfo[] trackGroupInfos) { | ||
| int trackGroupCount = 0; | ||
|
|
@@ -673,8 +674,8 @@ private static int buildPrimaryAndEmbeddedTrackGroupInfos( | |
| int primaryTrackGroupIndex = trackGroupCount++; | ||
| int eventMessageTrackGroupIndex = | ||
| primaryGroupHasEventMessageTrackFlags[i] ? trackGroupCount++ : C.INDEX_UNSET; | ||
| int cea608TrackGroupIndex = | ||
| primaryGroupCea608TrackFormats[i].length != 0 ? trackGroupCount++ : C.INDEX_UNSET; | ||
| int closedCaptionsTrackGroupIndex = | ||
| primaryGroupClosedCaptionsTrackFormats[i].length != 0 ? trackGroupCount++ : C.INDEX_UNSET; | ||
|
|
||
| trackGroups[primaryTrackGroupIndex] = new TrackGroup(formats); | ||
| trackGroupInfos[primaryTrackGroupIndex] = | ||
|
|
@@ -683,7 +684,7 @@ private static int buildPrimaryAndEmbeddedTrackGroupInfos( | |
| adaptationSetIndices, | ||
| primaryTrackGroupIndex, | ||
| eventMessageTrackGroupIndex, | ||
| cea608TrackGroupIndex); | ||
| closedCaptionsTrackGroupIndex); | ||
| if (eventMessageTrackGroupIndex != C.INDEX_UNSET) { | ||
| Format format = | ||
| new Format.Builder() | ||
|
|
@@ -694,10 +695,11 @@ private static int buildPrimaryAndEmbeddedTrackGroupInfos( | |
| trackGroupInfos[eventMessageTrackGroupIndex] = | ||
| TrackGroupInfo.embeddedEmsgTrack(adaptationSetIndices, primaryTrackGroupIndex); | ||
| } | ||
| if (cea608TrackGroupIndex != C.INDEX_UNSET) { | ||
| trackGroups[cea608TrackGroupIndex] = new TrackGroup(primaryGroupCea608TrackFormats[i]); | ||
| trackGroupInfos[cea608TrackGroupIndex] = | ||
| TrackGroupInfo.embeddedCea608Track(adaptationSetIndices, primaryTrackGroupIndex); | ||
| if (closedCaptionsTrackGroupIndex != C.INDEX_UNSET) { | ||
| trackGroups[closedCaptionsTrackGroupIndex] = | ||
| new TrackGroup(primaryGroupClosedCaptionsTrackFormats[i]); | ||
| trackGroupInfos[closedCaptionsTrackGroupIndex] = | ||
| TrackGroupInfo.embeddedClosedCaptionsTrack(adaptationSetIndices, primaryTrackGroupIndex); | ||
| } | ||
| } | ||
| return trackGroupCount; | ||
|
|
@@ -728,11 +730,13 @@ private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackGroupInfo trac | |
| trackGroups.get(trackGroupInfo.embeddedEventMessageTrackGroupIndex); | ||
| embeddedTrackCount++; | ||
| } | ||
| boolean enableCea608Tracks = trackGroupInfo.embeddedCea608TrackGroupIndex != C.INDEX_UNSET; | ||
| TrackGroup embeddedCea608TrackGroup = null; | ||
| if (enableCea608Tracks) { | ||
| embeddedCea608TrackGroup = trackGroups.get(trackGroupInfo.embeddedCea608TrackGroupIndex); | ||
| embeddedTrackCount += embeddedCea608TrackGroup.length; | ||
| boolean enableClosedCaptionsTracks = | ||
| trackGroupInfo.embeddedClosedCaptionsTrackGroupIndex != C.INDEX_UNSET; | ||
| TrackGroup embeddedClosedCaptionsTrackGroup = null; | ||
| if (enableClosedCaptionsTracks) { | ||
| embeddedClosedCaptionsTrackGroup = | ||
| trackGroups.get(trackGroupInfo.embeddedClosedCaptionsTrackGroupIndex); | ||
| embeddedTrackCount += embeddedClosedCaptionsTrackGroup.length; | ||
| } | ||
|
|
||
| Format[] embeddedTrackFormats = new Format[embeddedTrackCount]; | ||
|
|
@@ -743,12 +747,12 @@ private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackGroupInfo trac | |
| embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_METADATA; | ||
| embeddedTrackCount++; | ||
| } | ||
| List<Format> embeddedCea608TrackFormats = new ArrayList<>(); | ||
| if (enableCea608Tracks) { | ||
| for (int i = 0; i < embeddedCea608TrackGroup.length; i++) { | ||
| embeddedTrackFormats[embeddedTrackCount] = embeddedCea608TrackGroup.getFormat(i); | ||
| List<Format> embeddedClosedCaptionsTrackFormats = new ArrayList<>(); | ||
| if (enableClosedCaptionsTracks) { | ||
| for (int i = 0; i < embeddedClosedCaptionsTrackGroup.length; i++) { | ||
| embeddedTrackFormats[embeddedTrackCount] = embeddedClosedCaptionsTrackGroup.getFormat(i); | ||
| embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_TEXT; | ||
| embeddedCea608TrackFormats.add(embeddedTrackFormats[embeddedTrackCount]); | ||
| embeddedClosedCaptionsTrackFormats.add(embeddedTrackFormats[embeddedTrackCount]); | ||
| embeddedTrackCount++; | ||
| } | ||
| } | ||
|
|
@@ -767,7 +771,7 @@ private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackGroupInfo trac | |
| trackGroupInfo.trackType, | ||
| elapsedRealtimeOffsetMs, | ||
| enableEventMessageTrack, | ||
| embeddedCea608TrackFormats, | ||
| embeddedClosedCaptionsTrackFormats, | ||
| trackPlayerEmsgHandler, | ||
| transferListener); | ||
| ChunkSampleStream<DashChunkSource> stream = | ||
|
|
@@ -824,57 +828,61 @@ private static boolean hasEventMessageTrack(List<AdaptationSet> adaptationSets, | |
| return false; | ||
| } | ||
|
|
||
| private static Format[] getCea608TrackFormats( | ||
| private static Format[] getClosedCaptionsTrackFormats( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of the enum through this code is IMO making it quite a bit harder to read than it needs to be. Can't you just do something like (warning, untested): and get rid of the enum and all the little helper methods.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| List<AdaptationSet> adaptationSets, int[] adaptationSetIndices) { | ||
| for (int i : adaptationSetIndices) { | ||
| AdaptationSet adaptationSet = adaptationSets.get(i); | ||
| List<Descriptor> descriptors = adaptationSets.get(i).accessibilityDescriptors; | ||
| 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 parseClosedCaptionsDescriptor( | ||
| 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 parseClosedCaptionsDescriptor( | ||
| 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(); | ||
| @SuppressWarnings("ConstantConditions") | ||
| private static Format[] parseClosedCaptionsDescriptor( | ||
| 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. | ||
|
|
@@ -916,21 +924,21 @@ private static final class TrackGroupInfo { | |
| public final int eventStreamGroupIndex; | ||
| public final int primaryTrackGroupIndex; | ||
| public final int embeddedEventMessageTrackGroupIndex; | ||
| public final int embeddedCea608TrackGroupIndex; | ||
| public final int embeddedClosedCaptionsTrackGroupIndex; | ||
|
|
||
| public static TrackGroupInfo primaryTrack( | ||
| int trackType, | ||
| int[] adaptationSetIndices, | ||
| int primaryTrackGroupIndex, | ||
| int embeddedEventMessageTrackGroupIndex, | ||
| int embeddedCea608TrackGroupIndex) { | ||
| int embeddedClosedCaptionsTrackGroupIndex) { | ||
| return new TrackGroupInfo( | ||
| trackType, | ||
| CATEGORY_PRIMARY, | ||
| adaptationSetIndices, | ||
| primaryTrackGroupIndex, | ||
| embeddedEventMessageTrackGroupIndex, | ||
| embeddedCea608TrackGroupIndex, | ||
| embeddedClosedCaptionsTrackGroupIndex, | ||
| /* eventStreamGroupIndex= */ -1); | ||
| } | ||
|
|
||
|
|
@@ -946,7 +954,7 @@ public static TrackGroupInfo embeddedEmsgTrack(int[] adaptationSetIndices, | |
| /* eventStreamGroupIndex= */ -1); | ||
| } | ||
|
|
||
| public static TrackGroupInfo embeddedCea608Track(int[] adaptationSetIndices, | ||
| public static TrackGroupInfo embeddedClosedCaptionsTrack(int[] adaptationSetIndices, | ||
| int primaryTrackGroupIndex) { | ||
| return new TrackGroupInfo( | ||
| C.TRACK_TYPE_TEXT, | ||
|
|
@@ -975,14 +983,14 @@ private TrackGroupInfo( | |
| int[] adaptationSetIndices, | ||
| int primaryTrackGroupIndex, | ||
| int embeddedEventMessageTrackGroupIndex, | ||
| int embeddedCea608TrackGroupIndex, | ||
| int embeddedClosedCaptionsTrackGroupIndex, | ||
| int eventStreamGroupIndex) { | ||
| this.trackType = trackType; | ||
| this.adaptationSetIndices = adaptationSetIndices; | ||
| this.trackGroupCategory = trackGroupCategory; | ||
| this.primaryTrackGroupIndex = primaryTrackGroupIndex; | ||
| this.embeddedEventMessageTrackGroupIndex = embeddedEventMessageTrackGroupIndex; | ||
| this.embeddedCea608TrackGroupIndex = embeddedCea608TrackGroupIndex; | ||
| this.embeddedClosedCaptionsTrackGroupIndex = embeddedClosedCaptionsTrackGroupIndex; | ||
| this.eventStreamGroupIndex = eventStreamGroupIndex; | ||
| } | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.