mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
Merge pull request #7199 from TiVo:p-fix-stuckcaption
PiperOrigin-RevId: 308229206
This commit is contained in:
parent
bf5b52e288
commit
a03f8a1c95
5 changed files with 76 additions and 59 deletions
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue