Skip to content

Commit

Permalink
Fix Cea608Decoder handling of service switch commands in field 2
Browse files Browse the repository at this point in the history
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
  • Loading branch information
icbaker authored and microkatz committed Oct 31, 2022
1 parent 7fcb53d commit 8c0f782
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 2 deletions.
3 changes: 3 additions & 0 deletions RELEASENOTES.md
Expand Up @@ -54,6 +54,9 @@ Release notes
a non-empty but invalid license URL.
* Fix `setMediaDrmSession failed: session not opened` error when switching
between DRM schemes in a playlist (e.g. Widevine to ClearKey).
* Text:
* CEA-608: Ensure service switch commands on field 2 are handled correctly
([#10666](https://github.com/google/ExoPlayer/issues/10666)).
* DASH:
* Parse `EventStream.presentationTimeOffset` from manifests
([#10460](https://github.com/google/ExoPlayer/issues/10460)).
Expand Down
Expand Up @@ -874,8 +874,8 @@ private static boolean isXdsControlCode(byte cc1) {
}

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

private static final class CueBuilder {
Expand Down
Expand Up @@ -255,6 +255,65 @@ public void onlySelectedChannelIsUsed() throws Exception {
assertThat(getOnlyCue(fifthSubtitle).text.toString()).isEqualTo("test subtitle");
}

@Test
public void serviceSwitchOnField1Handled() throws Exception {
Cea608Decoder decoder =
new Cea608Decoder(
MimeTypes.APPLICATION_CEA608,
/* accessibilityChannel= */ 1, // field 1, channel 1
Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS);
// field 1 (0xFC header): 'test' then service switch
// field 2 (0xFD header): 'wrong!'
byte[] sample1 =
Bytes.concat(
// 'paint on' control character
createPacket(0xFC, 0x14, 0x29),
createPacket(0xFD, 0x15, 0x29),
createPacket(0xFC, 't', 'e'),
createPacket(0xFD, 'w', 'r'),
createPacket(0xFC, 's', 't'),
createPacket(0xFD, 'o', 'n'),
// Enter TEXT service
createPacket(0xFC, 0x14, 0x2A),
createPacket(0xFD, 'g', '!'),
createPacket(0xFC, 'X', 'X'),
createPacket(0xFD, 0x0, 0x0));

Subtitle firstSubtitle = checkNotNull(decodeSampleAndCopyResult(decoder, sample1));

assertThat(getOnlyCue(firstSubtitle).text.toString()).isEqualTo("test");
}

// https://github.com/google/ExoPlayer/issues/10666
@Test
public void serviceSwitchOnField2Handled() throws Exception {
Cea608Decoder decoder =
new Cea608Decoder(
MimeTypes.APPLICATION_CEA608,
/* accessibilityChannel= */ 3, // field 2, channel 1
Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS);
// field 1 (0xFC header): 'wrong!'
// field 2 (0xFD header): 'test' then service switch
byte[] sample1 =
Bytes.concat(
// 'paint on' control character
createPacket(0xFC, 0x14, 0x29),
createPacket(0xFD, 0x15, 0x29),
createPacket(0xFC, 'w', 'r'),
createPacket(0xFD, 't', 'e'),
createPacket(0xFC, 'o', 'n'),
createPacket(0xFD, 's', 't'),
createPacket(0xFC, 'g', '!'),
// Enter TEXT service
createPacket(0xFD, 0x15, 0x2A),
createPacket(0xFC, 0x0, 0x0),
createPacket(0xFD, 'X', 'X'));

Subtitle firstSubtitle = checkNotNull(decodeSampleAndCopyResult(decoder, sample1));

assertThat(getOnlyCue(firstSubtitle).text.toString()).isEqualTo("test");
}

private static byte[] createPacket(int header, int cc1, int cc2) {
return new byte[] {
UnsignedBytes.checkedCast(header),
Expand Down

0 comments on commit 8c0f782

Please sign in to comment.