From 59b8552ac0b038efc84eb060edb3b4fad810bcfd Mon Sep 17 00:00:00 2001 From: Sadashiva Neelavara Date: Wed, 1 Apr 2020 18:34:37 -0700 Subject: [PATCH] Fix to remove CEA-608 caption stuck on screen with timeout This changes fixes issue #7181. Removing CEA-608 captions that timeout after 16 seconds without a clear. --- .../exoplayer2/text/cea/Cea608Decoder.java | 31 +++++++++++++++++-- .../exoplayer2/text/cea/Cea708Decoder.java | 8 +++++ .../exoplayer2/text/cea/CeaDecoder.java | 7 +++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java index cce1bf6270..964b5ba0f8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java @@ -257,6 +257,16 @@ public final class Cea608Decoder extends CeaDecoder { // service bytes and drops the rest. private boolean isInCaptionService; + // Static counter to keep track of last CC rendered. This is used to force erase the caption when + // the stream does not explicitly send control codes to remove caption as specified by + // CEA-608 Annex C.9 + private long ccTimeOutCounter = C.TIME_UNSET; + private boolean captionEraseCommandSeen = false; + // CEA-608 Annex C.9 propose that if no data are received for the selected caption channel within + // a given time, the decoder should automatically erase the caption. The time limit should be no + // less than 16 seconds + public static final int VALID_DATA_CHANNEL_TIMEOUT_MS = 16000; + public Cea608Decoder(String mimeType, int accessibilityChannel) { ccData = new ParsableByteArray(); cueBuilders = new ArrayList<>(); @@ -310,6 +320,7 @@ public final class Cea608Decoder extends CeaDecoder { repeatableControlCc2 = 0; currentChannel = NTSC_CC_CHANNEL_1; isInCaptionService = true; + ccTimeOutCounter = C.TIME_UNSET; } @Override @@ -334,6 +345,7 @@ public final class Cea608Decoder extends CeaDecoder { ByteBuffer subtitleData = Assertions.checkNotNull(inputBuffer.data); ccData.reset(subtitleData.array(), subtitleData.limit()); boolean captionDataProcessed = false; + captionEraseCommandSeen = false; while (ccData.bytesLeft() >= packetLength) { byte ccHeader = packetLength == 2 ? CC_IMPLICIT_DATA_HEADER : (byte) ccData.readUnsignedByte(); @@ -343,7 +355,6 @@ public final class Cea608Decoder extends CeaDecoder { // TODO: We're currently ignoring the top 5 marker bits, which should all be 1s according // to the CEA-608 specification. We need to determine if the data should be handled // differently when that is not the case. - if ((ccHeader & CC_TYPE_FLAG) != 0) { // Do not process anything that is not part of the 608 byte stream. continue; @@ -353,7 +364,6 @@ public final class Cea608Decoder extends CeaDecoder { // Do not process packets not within the selected field. continue; } - // Strip the parity bit from each byte to get CC data. byte ccData1 = (byte) (ccByte1 & 0x7F); byte ccData2 = (byte) (ccByte2 & 0x7F); @@ -423,6 +433,9 @@ public final class Cea608Decoder extends CeaDecoder { if (captionDataProcessed) { if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { cues = getDisplayCues(); + if (!captionEraseCommandSeen) { + ccTimeOutCounter = System.currentTimeMillis(); + } } } } @@ -541,14 +554,17 @@ public final class Cea608Decoder extends CeaDecoder { cues = Collections.emptyList(); if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { resetCueBuilders(); + captionEraseCommandSeen = true; } break; case CTRL_ERASE_NON_DISPLAYED_MEMORY: resetCueBuilders(); + captionEraseCommandSeen = true; break; case CTRL_END_OF_CAPTION: cues = getDisplayCues(); resetCueBuilders(); + captionEraseCommandSeen = true; break; case CTRL_CARRIAGE_RETURN: // carriage returns only apply to rollup captions; don't bother if we don't have anything @@ -1018,4 +1034,15 @@ public final class Cea608Decoder extends CeaDecoder { } + protected void clearStuckCaptions() + { + if (ccTimeOutCounter != C.TIME_UNSET) { + long timeElapsed = System.currentTimeMillis() - ccTimeOutCounter; + if (timeElapsed >= VALID_DATA_CHANNEL_TIMEOUT_MS) { + // Force erase captions. There might be stale captions stuck on screen. + // (CEA-608 Annex C.9) + flush(); + } + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java index 182fe7a2fe..53d930c948 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java @@ -1296,6 +1296,14 @@ public final class Cea708Decoder extends CeaDecoder { } } + protected void clearStuckCaptions() + { + // Do nothing for CEA-708. + // As per spec CEA-708 Caption text sequences shall be terminated by either the start of a new + // DTVCC Command, or with an ASCII ETX (End of Text) (0x03) character when no other DTVCC + // Commands follow. + } + /** A {@link Cue} for CEA-708. */ private static final class Cea708CueInfo { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java index 03a7255538..8be6ff1312 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java @@ -97,6 +97,8 @@ import java.util.PriorityQueue; if (availableOutputBuffers.isEmpty()) { return null; } + // check if 608 decoder needs to clean up the stale caption + clearStuckCaptions(); // iterate through all available input buffers whose timestamps are less than or equal // to the current playback position; processing input buffers for future content should // be deferred until they would be applicable @@ -213,4 +215,9 @@ import java.util.PriorityQueue; owner.releaseOutputBuffer(this); } } + + /** + * Implements CEA-608 Annex C.9 automatic Caption Erase Logic + */ + protected abstract void clearStuckCaptions(); }