From a46716c0e999873f1354847e852c2abbc3c4d5b6 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 12 Nov 2024 09:35:59 -0800 Subject: [PATCH] Handle `C.TIME_END_OF_SOURCE` buffer timestamps in `CeaDecoder` The behaviour was changed in 1.4.0 with https://github.com/androidx/media/commit/0f42dd47526c70e38b1048bb18c994c281800485, so that the buffer timestamp is compared to `outputStartTimeUs` when deciding whether to discard a "decode only" buffer before decoding (instead of the deprecated/removed `isDecodeOnly` property). This breaks when the buffer timestamp is `TIME_END_OF_SOURCE` (which is `Long.MIN_VALUE`), because `TIME_END_OF_SOURCE < outputStartTimeUs` is always true, so the end-of-stream buffer is never passed to the decoder and on to `TextRenderer` where it is used to [set `inputStreamEnded = true`](https://github.com/androidx/media/blob/40f187e4b47c445af5049a5a49ee4633ce4c1a76/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java#L434-L436) and so playback hangs. Issue: androidx/media#1863 PiperOrigin-RevId: 695767247 (cherry picked from commit 19b38c83b6ce1d13effc08da834cc8ff284cb969) --- RELEASENOTES.md | 2 ++ .../media3/extractor/text/cea/CeaDecoder.java | 4 +++- .../extractor/text/cea/Cea608DecoderTest.java | 20 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f83bff57efc..079d8d5fe80 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,6 +5,8 @@ * Text: * Fix garbled CEA-608 subtitles in content with more than one SEI message per sample. + * Fix playback hanging on DASH multi-period streams when CEA-608 subtitles + are enabled ([#1863](https://github.com/androidx/media/issues/1863)). ## 1.5 diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/CeaDecoder.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/CeaDecoder.java index 00c44551448..79472f58055 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/CeaDecoder.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/CeaDecoder.java @@ -84,7 +84,9 @@ public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException { Assertions.checkArgument(inputBuffer == dequeuedInputBuffer); CeaInputBuffer ceaInputBuffer = (CeaInputBuffer) inputBuffer; - if (outputStartTimeUs != C.TIME_UNSET && ceaInputBuffer.timeUs < outputStartTimeUs) { + if (ceaInputBuffer.timeUs != C.TIME_END_OF_SOURCE + && outputStartTimeUs != C.TIME_UNSET + && ceaInputBuffer.timeUs < outputStartTimeUs) { // We can start decoding anywhere in CEA formats, so discarding on the input side is fine. releaseInputBuffer(ceaInputBuffer); } else { diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea608DecoderTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea608DecoderTest.java index 718499a0434..e7c222e744b 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea608DecoderTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea608DecoderTest.java @@ -328,6 +328,26 @@ public void serviceSwitchOnField2Handled() throws Exception { assertThat(getOnlyCue(firstSubtitle).text.toString()).isEqualTo("test"); } + // https://github.com/androidx/media/issues/1863 + @Test + public void endOfStreamBuffer_flagPassedThrough() throws Exception { + Cea608Decoder decoder = + new Cea608Decoder( + MimeTypes.APPLICATION_CEA608, + /* accessibilityChannel= */ 1, + Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS); + + SubtitleInputBuffer inputBuffer = checkNotNull(decoder.dequeueInputBuffer()); + inputBuffer.timeUs = C.TIME_END_OF_SOURCE; + inputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); + decoder.setOutputStartTimeUs(0); + decoder.queueInputBuffer(inputBuffer); + decoder.setPositionUs(123); + SubtitleOutputBuffer outputBuffer = decoder.dequeueOutputBuffer(); + + assertThat(outputBuffer.isEndOfStream()).isTrue(); + } + private static byte[] createPacket(int header, int cc1, int cc2) { return new byte[] { UnsignedBytes.checkedCast(header),