Skip to content

Commit add9013

Browse files
icbakermicrokatz
authored andcommitted
Fix Cea608Decoder handling of service switch commands in field 2
From ANSI-CTA-608-E R-2014 section 8.4: > When closed captioning is used on line 21, field 2, it shall conform > to all of the applicable specifications and recommended practices as > defined for field 1 services with the following differences: > 1. The non-printing character of the miscellaneous control-character > pairs that fall in the range of 0x14, 0x20 to 0x14, 0x2F in field 1, > shall be replaced with 0x15, 0x20 to 0x15, 0x2F when used in field > 2. > 2. The non-printing character of the miscellaneous control-character > pairs that fall in the range of 0x1C, 0x20 to 0x1C, 0x2F in field > 1, shall be replaced with 0x1D, 0x20 to 0x1D, 0x2F when used in > field 2. This basically means that `cc1=0x15` in field 2 should be interpreted as `cc1=0x14` in field 1, and same for `0x1D -> 0x1C`. The `isMiscCode` method above already handles this by ignoring the LSB (the only difference between `0x14` and `0x15`, and `0x1C` and `0x1D`) by AND-ing with `0xF6` instead of `0xF7`. This change uses the same trick in `isServiceSwitchCommand`. Issue: google/ExoPlayer#10666 #minor-release PiperOrigin-RevId: 483927506 (cherry picked from commit 8c0f782)
1 parent 92dc1d3 commit add9013

File tree

3 files changed

+64
-2
lines changed

3 files changed

+64
-2
lines changed

RELEASENOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ Release notes
5454
a non-empty but invalid license URL.
5555
* Fix `setMediaDrmSession failed: session not opened` error when switching
5656
between DRM schemes in a playlist (e.g. Widevine to ClearKey).
57+
* Text:
58+
* CEA-608: Ensure service switch commands on field 2 are handled correctly
59+
([#10666](https://github.com/google/ExoPlayer/issues/10666)).
5760
* DASH:
5861
* Parse `EventStream.presentationTimeOffset` from manifests
5962
([#10460](https://github.com/google/ExoPlayer/issues/10460)).

libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea608Decoder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -874,8 +874,8 @@ private static boolean isXdsControlCode(byte cc1) {
874874
}
875875

876876
private static boolean isServiceSwitchCommand(byte cc1) {
877-
// cc1 - 0|0|0|1|C|1|0|0
878-
return (cc1 & 0xF7) == 0x14;
877+
// cc1 - 0|0|0|1|C|1|0|F
878+
return (cc1 & 0xF6) == 0x14;
879879
}
880880

881881
private static final class CueBuilder {

libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea608DecoderTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,65 @@ public void onlySelectedChannelIsUsed() throws Exception {
255255
assertThat(getOnlyCue(fifthSubtitle).text.toString()).isEqualTo("test subtitle");
256256
}
257257

258+
@Test
259+
public void serviceSwitchOnField1Handled() throws Exception {
260+
Cea608Decoder decoder =
261+
new Cea608Decoder(
262+
MimeTypes.APPLICATION_CEA608,
263+
/* accessibilityChannel= */ 1, // field 1, channel 1
264+
Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS);
265+
// field 1 (0xFC header): 'test' then service switch
266+
// field 2 (0xFD header): 'wrong!'
267+
byte[] sample1 =
268+
Bytes.concat(
269+
// 'paint on' control character
270+
createPacket(0xFC, 0x14, 0x29),
271+
createPacket(0xFD, 0x15, 0x29),
272+
createPacket(0xFC, 't', 'e'),
273+
createPacket(0xFD, 'w', 'r'),
274+
createPacket(0xFC, 's', 't'),
275+
createPacket(0xFD, 'o', 'n'),
276+
// Enter TEXT service
277+
createPacket(0xFC, 0x14, 0x2A),
278+
createPacket(0xFD, 'g', '!'),
279+
createPacket(0xFC, 'X', 'X'),
280+
createPacket(0xFD, 0x0, 0x0));
281+
282+
Subtitle firstSubtitle = checkNotNull(decodeSampleAndCopyResult(decoder, sample1));
283+
284+
assertThat(getOnlyCue(firstSubtitle).text.toString()).isEqualTo("test");
285+
}
286+
287+
// https://github.com/google/ExoPlayer/issues/10666
288+
@Test
289+
public void serviceSwitchOnField2Handled() throws Exception {
290+
Cea608Decoder decoder =
291+
new Cea608Decoder(
292+
MimeTypes.APPLICATION_CEA608,
293+
/* accessibilityChannel= */ 3, // field 2, channel 1
294+
Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS);
295+
// field 1 (0xFC header): 'wrong!'
296+
// field 2 (0xFD header): 'test' then service switch
297+
byte[] sample1 =
298+
Bytes.concat(
299+
// 'paint on' control character
300+
createPacket(0xFC, 0x14, 0x29),
301+
createPacket(0xFD, 0x15, 0x29),
302+
createPacket(0xFC, 'w', 'r'),
303+
createPacket(0xFD, 't', 'e'),
304+
createPacket(0xFC, 'o', 'n'),
305+
createPacket(0xFD, 's', 't'),
306+
createPacket(0xFC, 'g', '!'),
307+
// Enter TEXT service
308+
createPacket(0xFD, 0x15, 0x2A),
309+
createPacket(0xFC, 0x0, 0x0),
310+
createPacket(0xFD, 'X', 'X'));
311+
312+
Subtitle firstSubtitle = checkNotNull(decodeSampleAndCopyResult(decoder, sample1));
313+
314+
assertThat(getOnlyCue(firstSubtitle).text.toString()).isEqualTo("test");
315+
}
316+
258317
private static byte[] createPacket(int header, int cc1, int cc2) {
259318
return new byte[] {
260319
UnsignedBytes.checkedCast(header),

0 commit comments

Comments
 (0)