Merge pull request #7199 from TiVo:p-fix-stuckcaption

PiperOrigin-RevId: 308229206
This commit is contained in:
Ian Baker 2020-04-27 11:18:56 +01:00
parent bf5b52e288
commit a03f8a1c95
5 changed files with 76 additions and 59 deletions

View file

@ -101,6 +101,9 @@
used `start`, `middle` and `end`).
* Use anti-aliasing and bitmap filtering when displaying bitmap subtitles
([#6950](https://github.com/google/ExoPlayer/pull/6950)).
* Implement timing-out of stuck CEA-608 captions (as permitted by
ANSI/CTA-608-E R-2014 Annex C.9) and set the default timeout to 16
seconds ([#7181](https://github.com/google/ExoPlayer/issues/7181)).
* DRM:
* Add support for attaching DRM sessions to clear content in the demo app.
* Remove `DrmSessionManager` references from all renderers.
@ -111,8 +114,8 @@
([#7078](https://github.com/google/ExoPlayer/issues/7078)).
* Remove generics from DRM components.
* Downloads and caching:
* Merge downloads in `SegmentDownloader` to improve overall download
speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)).
* Merge downloads in `SegmentDownloader` to improve overall download speed
([#5978](https://github.com/google/ExoPlayer/issues/5978)).
* Replace `CacheDataSinkFactory` and `CacheDataSourceFactory` with
`CacheDataSink.Factory` and `CacheDataSource.Factory` respectively.
* Remove `DownloadConstructorHelper` and use `CacheDataSource.Factory`

View file

@ -27,7 +27,6 @@ import com.google.android.exoplayer2.text.ttml.TtmlDecoder;
import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder;
import com.google.android.exoplayer2.text.webvtt.Mp4WebvttDecoder;
import com.google.android.exoplayer2.text.webvtt.WebvttDecoder;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.MimeTypes;
/**
@ -109,8 +108,10 @@ public interface SubtitleDecoderFactory {
return new Tx3gDecoder(format.initializationData);
case MimeTypes.APPLICATION_CEA608:
case MimeTypes.APPLICATION_MP4CEA608:
return new Cea608Decoder(mimeType, format.accessibilityChannel,
16000L, Clock.DEFAULT);
return new Cea608Decoder(
mimeType,
format.accessibilityChannel,
Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS);
case MimeTypes.APPLICATION_CEA708:
return new Cea708Decoder(format.accessibilityChannel, format.initializationData);
case MimeTypes.APPLICATION_DVBSUBS:

View file

@ -26,12 +26,14 @@ import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.Subtitle;
import com.google.android.exoplayer2.text.SubtitleDecoder;
import com.google.android.exoplayer2.text.SubtitleDecoderException;
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
@ -41,11 +43,15 @@ import java.util.Collections;
import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
* A {@link SubtitleDecoder} for CEA-608 (also known as "line 21 captions" and "EIA-608").
*/
/** A {@link SubtitleDecoder} for CEA-608 (also known as "line 21 captions" and "EIA-608"). */
public final class Cea608Decoder extends CeaDecoder {
/**
* The minimum value for the {@code validDataChannelTimeoutMs} constructor parameter permitted by
* ANSI/CTA-608-E R-2014 Annex C.9.
*/
public static final long MIN_DATA_CHANNEL_TIMEOUT_MS = 16_000;
private static final String TAG = "Cea608Decoder";
private static final int CC_VALID_FLAG = 0x04;
@ -238,6 +244,7 @@ public final class Cea608Decoder extends CeaDecoder {
private final int packetLength;
private final int selectedField;
private final int selectedChannel;
private final long validDataChannelTimeoutUs;
private final ArrayList<CueBuilder> cueBuilders;
private CueBuilder currentCueBuilder;
@ -258,26 +265,26 @@ 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 lastCueUpdateMs = 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
private long lastCueUpdateUs;
// This value is set in the constructor. The automatic erasure is disabled when this value is 0
private long validDataChannelTimeoutMs = 0;
private Clock clock;
public Cea608Decoder(String mimeType, int accessibilityChannel, long timeoutMs, Clock clock) {
/**
* Constructs an instance.
*
* @param mimeType The MIME type of the CEA-608 data.
* @param accessibilityChannel The Accessibility channel, or {@link
* com.google.android.exoplayer2.Format#NO_VALUE} if unknown.
* @param validDataChannelTimeoutMs The timeout (in milliseconds) permitted by ANSI/CTA-608-E
* R-2014 Annex C.9 to clear "stuck" captions where no removal control code is received. The
* timeout should be at least {@link #MIN_DATA_CHANNEL_TIMEOUT_MS} or {@link C#TIME_UNSET} for
* no timeout.
*/
public Cea608Decoder(String mimeType, int accessibilityChannel, long validDataChannelTimeoutMs) {
ccData = new ParsableByteArray();
cueBuilders = new ArrayList<>();
currentCueBuilder = new CueBuilder(CC_MODE_UNKNOWN, DEFAULT_CAPTIONS_ROW_COUNT);
currentChannel = NTSC_CC_CHANNEL_1;
validDataChannelTimeoutMs = timeoutMs;
this.clock = clock;
this.validDataChannelTimeoutUs =
validDataChannelTimeoutMs > 0 ? validDataChannelTimeoutMs * 1000 : C.TIME_UNSET;
packetLength = MimeTypes.APPLICATION_MP4CEA608.equals(mimeType) ? 2 : 3;
switch (accessibilityChannel) {
case 1:
@ -305,6 +312,7 @@ public final class Cea608Decoder extends CeaDecoder {
setCaptionMode(CC_MODE_UNKNOWN);
resetCueBuilders();
isInCaptionService = true;
lastCueUpdateUs = C.TIME_UNSET;
}
@Override
@ -326,7 +334,7 @@ public final class Cea608Decoder extends CeaDecoder {
repeatableControlCc2 = 0;
currentChannel = NTSC_CC_CHANNEL_1;
isInCaptionService = true;
lastCueUpdateMs = C.TIME_UNSET;
lastCueUpdateUs = C.TIME_UNSET;
}
@Override
@ -334,6 +342,26 @@ public final class Cea608Decoder extends CeaDecoder {
// Do nothing
}
@Nullable
@Override
public SubtitleOutputBuffer dequeueOutputBuffer() throws SubtitleDecoderException {
SubtitleOutputBuffer outputBuffer = super.dequeueOutputBuffer();
if (outputBuffer != null) {
return outputBuffer;
}
if (shouldClearStuckCaptions()) {
outputBuffer = getAvailableOutputBuffer();
if (outputBuffer != null) {
cues = Collections.emptyList();
lastCueUpdateUs = C.TIME_UNSET;
Subtitle subtitle = createSubtitle();
outputBuffer.setContent(getPositionUs(), subtitle, Format.OFFSET_SAMPLE_RELATIVE);
return outputBuffer;
}
}
return null;
}
@Override
protected boolean isNewSubtitleDataAvailable() {
return cues != lastCues;
@ -351,7 +379,6 @@ 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();
@ -361,6 +388,7 @@ 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;
@ -370,6 +398,7 @@ 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);
@ -439,9 +468,7 @@ public final class Cea608Decoder extends CeaDecoder {
if (captionDataProcessed) {
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
cues = getDisplayCues();
if ((validDataChannelTimeoutMs != 0) && !captionEraseCommandSeen) {
lastCueUpdateMs = clock.elapsedRealtime();
}
lastCueUpdateUs = getPositionUs();
}
}
}
@ -560,17 +587,14 @@ 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
@ -1040,17 +1064,12 @@ public final class Cea608Decoder extends CeaDecoder {
}
protected void clearStuckCaptions()
{
if ((validDataChannelTimeoutMs != 0) &&
(lastCueUpdateMs != C.TIME_UNSET)) {
long timeElapsed = clock.elapsedRealtime() - lastCueUpdateMs;
if (timeElapsed >= validDataChannelTimeoutMs) {
// Force erase captions. There might be stale captions stuck on screen.
// (CEA-608 Annex C.9)
cues = Collections.emptyList();
lastCueUpdateMs = C.TIME_UNSET;
}
}
/** See ANSI/CTA-608-E R-2014 Annex C.9 for Caption Erase Logic. */
private boolean shouldClearStuckCaptions() {
if (validDataChannelTimeoutUs == C.TIME_UNSET || lastCueUpdateUs == C.TIME_UNSET) {
return false;
}
long elapsedUs = getPositionUs() - lastCueUpdateUs;
return elapsedUs >= validDataChannelTimeoutUs;
}
}

View file

@ -1296,14 +1296,6 @@ 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 {

View file

@ -97,8 +97,6 @@ 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
@ -181,6 +179,15 @@ import java.util.PriorityQueue;
*/
protected abstract void decode(SubtitleInputBuffer inputBuffer);
@Nullable
protected final SubtitleOutputBuffer getAvailableOutputBuffer() {
return availableOutputBuffers.pollFirst();
}
protected final long getPositionUs() {
return playbackPositionUs;
}
private static final class CeaInputBuffer extends SubtitleInputBuffer
implements Comparable<CeaInputBuffer> {
@ -215,9 +222,4 @@ import java.util.PriorityQueue;
owner.releaseOutputBuffer(this);
}
}
/**
* Implements CEA-608 Annex C.9 automatic Caption Erase Logic
*/
protected abstract void clearStuckCaptions();
}