diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6dc200967b..0debedc1db 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -91,6 +91,13 @@ * PGS: Fix run-length decoding to resolve `0` as a color index, instead of a literal color value ([#1367](https://github.com/androidx/media/pull/1367)). + * CEA-708: Ignore `rowLock` value. The CEA-708-E S-2023 spec states that + `rowLock` and `columnLock` should both be assumed to be true, regardless + of the values present in the stream (`columnLock` support is not + implemented, so it's effectively assumed to always be false). + * This was originally included in the `1.3.0-alpha01` release notes, + but the change was accidentally reverted before the `1.3.0-rc01` + release. This is now fixed, so the change is present again. * Metadata: * Fix mapping of MP4 to ID3 sort tags. Previously the 'album sort' (`soal`), 'artist sort' (`soar`) and 'album artist sort' (`soaa`) MP4 diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Decoder.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Decoder.java index 6d2102be73..077d1e2210 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Decoder.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Decoder.java @@ -778,8 +778,11 @@ public final class Cea708Decoder extends CeaDecoder { // first byte captionChannelPacketData.skipBits(2); // null padding boolean visible = captionChannelPacketData.readBit(); - boolean rowLock = captionChannelPacketData.readBit(); - boolean columnLock = captionChannelPacketData.readBit(); + + // ANSI/CTA-708-E S-2023 spec (Section 8.4.7) indicates that rowLock and columnLock values in + // the media should be ignored and assumed to be true. + captionChannelPacketData.skipBits(2); + int priority = captionChannelPacketData.readBits(3); // second byte boolean relativePositioning = captionChannelPacketData.readBit(); @@ -791,7 +794,8 @@ public final class Cea708Decoder extends CeaDecoder { int rowCount = captionChannelPacketData.readBits(4); // fifth byte captionChannelPacketData.skipBits(2); // null padding - int columnCount = captionChannelPacketData.readBits(6); + // TODO: Add support for column count. + captionChannelPacketData.skipBits(6); // column count // sixth byte captionChannelPacketData.skipBits(2); // null padding int windowStyle = captionChannelPacketData.readBits(3); @@ -799,14 +803,11 @@ public final class Cea708Decoder extends CeaDecoder { cueInfoBuilder.defineWindow( visible, - rowLock, - columnLock, priority, relativePositioning, verticalAnchor, horizontalAnchor, rowCount, - columnCount, anchorId, windowStyle, penStyle); @@ -967,7 +968,6 @@ public final class Cea708Decoder extends CeaDecoder { private int horizontalAnchor; private int anchorId; private int rowCount; - private boolean rowLock; private int justification; private int windowStyleId; private int penStyleId; @@ -1003,7 +1003,6 @@ public final class Cea708Decoder extends CeaDecoder { horizontalAnchor = 0; anchorId = 0; rowCount = MAXIMUM_ROW_COUNT; - rowLock = true; justification = JUSTIFICATION_LEFT; windowStyleId = 0; penStyleId = 0; @@ -1037,20 +1036,16 @@ public final class Cea708Decoder extends CeaDecoder { public void defineWindow( boolean visible, - boolean rowLock, - boolean columnLock, int priority, boolean relativePositioning, int verticalAnchor, int horizontalAnchor, int rowCount, - int columnCount, int anchorId, int windowStyleId, int penStyleId) { this.defined = true; this.visible = visible; - this.rowLock = rowLock; this.priority = priority; this.relativePositioning = relativePositioning; this.verticalAnchor = verticalAnchor; @@ -1062,14 +1057,12 @@ public final class Cea708Decoder extends CeaDecoder { this.rowCount = rowCount + 1; // Trim any rolled up captions that are no longer valid, if applicable. - while ((rowLock && (rolledUpCaptions.size() >= this.rowCount)) - || (rolledUpCaptions.size() >= MAXIMUM_ROW_COUNT)) { + while (rolledUpCaptions.size() >= this.rowCount + || rolledUpCaptions.size() >= MAXIMUM_ROW_COUNT) { rolledUpCaptions.remove(0); } } - // TODO: Add support for column lock and count. - if (windowStyleId != 0 && this.windowStyleId != windowStyleId) { this.windowStyleId = windowStyleId; // windowStyleId is 1-based. @@ -1231,8 +1224,8 @@ public final class Cea708Decoder extends CeaDecoder { backgroundColorStartPosition = 0; } - while ((rowLock && (rolledUpCaptions.size() >= rowCount)) - || (rolledUpCaptions.size() >= MAXIMUM_ROW_COUNT)) { + while (rolledUpCaptions.size() >= rowCount + || rolledUpCaptions.size() >= MAXIMUM_ROW_COUNT) { rolledUpCaptions.remove(0); } } else { diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea708DecoderTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea708DecoderTest.java index 2653eae16e..f4b196c6ec 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea708DecoderTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea708DecoderTest.java @@ -60,7 +60,6 @@ public class Cea708DecoderTest { public void singleServiceAndWindowDefinition() throws Exception { Cea708Decoder cea708Decoder = new Cea708Decoder( - /* accessibilityChannel= */ Format.NO_VALUE, /* initializationData= */ null); byte[] windowDefinition = TestUtil.createByteArray( @@ -87,6 +86,38 @@ public class Cea708DecoderTest { assertThat(getOnlyCue(firstSubtitle).text.toString()).isEqualTo("test subtitle"); } + @Test + public void singleServiceAndWindowDefinition_ignoresRowLock() throws Exception { + Cea708Decoder cea708Decoder = + new Cea708Decoder( + /* accessibilityChannel= */ Format.NO_VALUE, /* initializationData= */ null); + byte[] windowDefinition = + TestUtil.createByteArray( + 0x98, // DF0 command (define window 0) + 0b0010_0000, // visible=true, row lock and column lock disabled, priority=0 + 0xF0 | 50, // relative positioning, anchor vertical + 50, // anchor horizontal + 1, // anchor point = 0, row count = 1 + 30, // column count = 30 + 0b0000_1001); // window style = 1, pen style = 1 + byte[] setCurrentWindow = TestUtil.createByteArray(0x80); // CW0 (set current window to 0) + byte[] subtitleData = + encodePacketIntoBytePairs( + createPacket( + /* sequenceNumber= */ 0, + createServiceBlock( + Bytes.concat( + windowDefinition, + setCurrentWindow, + "row1\r\nrow2\r\nrow3\r\nrow4".getBytes(Charsets.UTF_8))))); + + Subtitle result = decodeSampleAndCopyResult(cea708Decoder, subtitleData); + + // Row count is 1 (which means 2 rows should be kept). Row lock is disabled in the media, + // but this is ignored and the result is still truncated to only the last two rows. + assertThat(getOnlyCue(result).text.toString()).isEqualTo("row3\nrow4"); + } + /** * Queues {@code sample} to {@code decoder} and dequeues the result, then copies and returns it if * it's non-null.