Commit affbb7c5 by michaelkatz Committed by Rohit Singh

Render last frame even if have not read BUFFER_FLAG_END_OF_STREAM

If the limited number of input buffers causes reading of all samples except the last one conveying end of stream, then the last frame will not be rendered.

PiperOrigin-RevId: 525974445
parent 353523bb
...@@ -31,6 +31,11 @@ ...@@ -31,6 +31,11 @@
* Deprecate `Player.COMMAND_GET_MEDIA_ITEMS_METADATA` and * Deprecate `Player.COMMAND_GET_MEDIA_ITEMS_METADATA` and
`COMMAND_SET_MEDIA_ITEMS_METADATA`. Use `COMMAND_GET_METADATA` and `COMMAND_SET_MEDIA_ITEMS_METADATA`. Use `COMMAND_GET_METADATA` and
`COMMAND_SET_PLAYLIST_METADATA` instead. `COMMAND_SET_PLAYLIST_METADATA` instead.
* Add `Buffer.isLastSample()` that denotes if `Buffer` contains flag
`C.BUFFER_FLAG_LAST_SAMPLE`.
* Fix issue where last frame may not be rendered if the last sample with
frames is dequeued without reading the 'end of stream' sample.
([#11079](https://github.com/google/ExoPlayer/issues/11079)).
* Session: * Session:
* Deprecate 4 volume-controlling methods in `Player` and add overloaded * Deprecate 4 volume-controlling methods in `Player` and add overloaded
methods which allow users to specify volume flags: methods which allow users to specify volume flags:
......
...@@ -49,6 +49,11 @@ public abstract class Buffer { ...@@ -49,6 +49,11 @@ public abstract class Buffer {
return getFlag(C.BUFFER_FLAG_KEY_FRAME); return getFlag(C.BUFFER_FLAG_KEY_FRAME);
} }
/** Returns whether the {@link C#BUFFER_FLAG_LAST_SAMPLE} flag is set. */
public final boolean isLastSample() {
return getFlag(C.BUFFER_FLAG_LAST_SAMPLE);
}
/** Returns whether the {@link C#BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA} flag is set. */ /** Returns whether the {@link C#BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA} flag is set. */
public final boolean hasSupplementalData() { public final boolean hasSupplementalData() {
return getFlag(C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA); return getFlag(C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA);
......
...@@ -1249,7 +1249,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1249,7 +1249,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return true; return true;
} }
if (hasReadStreamToEnd()) { if (hasReadStreamToEnd() || buffer.isLastSample()) {
// Notify output queue of the last buffer's timestamp. // Notify output queue of the last buffer's timestamp.
lastBufferInStreamPresentationTimeUs = largestQueuedPresentationTimeUs; lastBufferInStreamPresentationTimeUs = largestQueuedPresentationTimeUs;
} }
......
...@@ -716,6 +716,9 @@ public class SampleQueue implements TrackOutput { ...@@ -716,6 +716,9 @@ public class SampleQueue implements TrackOutput {
} }
buffer.setFlags(flags[relativeReadIndex]); buffer.setFlags(flags[relativeReadIndex]);
if (readPosition == (length - 1) && (loadingFinished || isLastSampleQueued)) {
buffer.addFlag(C.BUFFER_FLAG_LAST_SAMPLE);
}
buffer.timeUs = timesUs[relativeReadIndex]; buffer.timeUs = timesUs[relativeReadIndex];
if (buffer.timeUs < startTimeUs) { if (buffer.timeUs < startTimeUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
......
...@@ -355,6 +355,32 @@ public final class SampleQueueTest { ...@@ -355,6 +355,32 @@ public final class SampleQueueTest {
} }
@Test @Test
public void readSingleSampleWithLoadingFinished() {
sampleQueue.sampleData(new ParsableByteArray(DATA), ALLOCATION_SIZE);
sampleQueue.format(FORMAT_1);
sampleQueue.sampleMetadata(1000, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
assertAllocationCount(1);
// If formatRequired, should read the format rather than the sample.
assertReadFormat(true, FORMAT_1);
// Otherwise should read the sample with loading finished.
assertReadLastSample(
1000,
/* isKeyFrame= */ true,
/* isDecodeOnly= */ false,
/* isEncrypted= */ false,
DATA,
/* offset= */ 0,
ALLOCATION_SIZE);
// Allocation should still be held.
assertAllocationCount(1);
sampleQueue.discardToRead();
// The allocation should have been released.
assertAllocationCount(0);
}
@Test
public void readMultiSamples() { public void readMultiSamples() {
writeTestData(); writeTestData();
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(LAST_SAMPLE_TIMESTAMP); assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(LAST_SAMPLE_TIMESTAMP);
...@@ -1642,13 +1668,27 @@ public final class SampleQueueTest { ...@@ -1642,13 +1668,27 @@ public final class SampleQueueTest {
FLAG_OMIT_SAMPLE_DATA | FLAG_PEEK, FLAG_OMIT_SAMPLE_DATA | FLAG_PEEK,
/* loadingFinished= */ false); /* loadingFinished= */ false);
assertSampleBufferReadResult( assertSampleBufferReadResult(
flagsOnlyBuffer, result, timeUs, isKeyFrame, isDecodeOnly, isEncrypted); flagsOnlyBuffer,
result,
timeUs,
isKeyFrame,
isDecodeOnly,
isEncrypted,
/* isLastSample= */ false);
// Check that peek yields the expected values. // Check that peek yields the expected values.
clearFormatHolderAndInputBuffer(); clearFormatHolderAndInputBuffer();
result = sampleQueue.read(formatHolder, inputBuffer, FLAG_PEEK, /* loadingFinished= */ false); result = sampleQueue.read(formatHolder, inputBuffer, FLAG_PEEK, /* loadingFinished= */ false);
assertSampleBufferReadResult( assertSampleBufferReadResult(
result, timeUs, isKeyFrame, isDecodeOnly, isEncrypted, sampleData, offset, length); result,
timeUs,
isKeyFrame,
isDecodeOnly,
isEncrypted,
/* isLastSample= */ false,
sampleData,
offset,
length);
// Check that read yields the expected values. // Check that read yields the expected values.
clearFormatHolderAndInputBuffer(); clearFormatHolderAndInputBuffer();
...@@ -1656,7 +1696,85 @@ public final class SampleQueueTest { ...@@ -1656,7 +1696,85 @@ public final class SampleQueueTest {
sampleQueue.read( sampleQueue.read(
formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ false); formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ false);
assertSampleBufferReadResult( assertSampleBufferReadResult(
result, timeUs, isKeyFrame, isDecodeOnly, isEncrypted, sampleData, offset, length); result,
timeUs,
isKeyFrame,
isDecodeOnly,
isEncrypted,
/* isLastSample= */ false,
sampleData,
offset,
length);
}
/**
* Asserts {@link SampleQueue#read} returns {@link C#RESULT_BUFFER_READ} and that the buffer is
* filled with the specified sample data. Also asserts that being the last sample and loading is
* finished, that the {@link C#BUFFER_FLAG_LAST_SAMPLE} flag is set.
*
* @param timeUs The expected buffer timestamp.
* @param isKeyFrame The expected keyframe flag.
* @param isDecodeOnly The expected decodeOnly flag.
* @param isEncrypted The expected encrypted flag.
* @param sampleData An array containing the expected sample data.
* @param offset The offset in {@code sampleData} of the expected sample data.
* @param length The length of the expected sample data.
*/
private void assertReadLastSample(
long timeUs,
boolean isKeyFrame,
boolean isDecodeOnly,
boolean isEncrypted,
byte[] sampleData,
int offset,
int length) {
// Check that peek whilst omitting data yields the expected values.
formatHolder.format = null;
DecoderInputBuffer flagsOnlyBuffer = DecoderInputBuffer.newNoDataInstance();
int result =
sampleQueue.read(
formatHolder,
flagsOnlyBuffer,
FLAG_OMIT_SAMPLE_DATA | FLAG_PEEK,
/* loadingFinished= */ true);
assertSampleBufferReadResult(
flagsOnlyBuffer,
result,
timeUs,
isKeyFrame,
isDecodeOnly,
isEncrypted,
/* isLastSample= */ true);
// Check that peek yields the expected values.
clearFormatHolderAndInputBuffer();
result = sampleQueue.read(formatHolder, inputBuffer, FLAG_PEEK, /* loadingFinished= */ true);
assertSampleBufferReadResult(
result,
timeUs,
isKeyFrame,
isDecodeOnly,
isEncrypted,
/* isLastSample= */ true,
sampleData,
offset,
length);
// Check that read yields the expected values.
clearFormatHolderAndInputBuffer();
result =
sampleQueue.read(
formatHolder, inputBuffer, /* readFlags= */ 0, /* loadingFinished= */ true);
assertSampleBufferReadResult(
result,
timeUs,
isKeyFrame,
isDecodeOnly,
isEncrypted,
/* isLastSample= */ true,
sampleData,
offset,
length);
} }
private void assertSampleBufferReadResult( private void assertSampleBufferReadResult(
...@@ -1665,7 +1783,8 @@ public final class SampleQueueTest { ...@@ -1665,7 +1783,8 @@ public final class SampleQueueTest {
long timeUs, long timeUs,
boolean isKeyFrame, boolean isKeyFrame,
boolean isDecodeOnly, boolean isDecodeOnly,
boolean isEncrypted) { boolean isEncrypted,
boolean isLastSample) {
assertThat(result).isEqualTo(RESULT_BUFFER_READ); assertThat(result).isEqualTo(RESULT_BUFFER_READ);
// formatHolder should not be populated. // formatHolder should not be populated.
assertThat(formatHolder.format).isNull(); assertThat(formatHolder.format).isNull();
...@@ -1674,6 +1793,7 @@ public final class SampleQueueTest { ...@@ -1674,6 +1793,7 @@ public final class SampleQueueTest {
assertThat(inputBuffer.isKeyFrame()).isEqualTo(isKeyFrame); assertThat(inputBuffer.isKeyFrame()).isEqualTo(isKeyFrame);
assertThat(inputBuffer.isDecodeOnly()).isEqualTo(isDecodeOnly); assertThat(inputBuffer.isDecodeOnly()).isEqualTo(isDecodeOnly);
assertThat(inputBuffer.isEncrypted()).isEqualTo(isEncrypted); assertThat(inputBuffer.isEncrypted()).isEqualTo(isEncrypted);
assertThat(inputBuffer.isLastSample()).isEqualTo(isLastSample);
} }
private void assertSampleBufferReadResult( private void assertSampleBufferReadResult(
...@@ -1682,11 +1802,12 @@ public final class SampleQueueTest { ...@@ -1682,11 +1802,12 @@ public final class SampleQueueTest {
boolean isKeyFrame, boolean isKeyFrame,
boolean isDecodeOnly, boolean isDecodeOnly,
boolean isEncrypted, boolean isEncrypted,
boolean isLastSample,
byte[] sampleData, byte[] sampleData,
int offset, int offset,
int length) { int length) {
assertSampleBufferReadResult( assertSampleBufferReadResult(
inputBuffer, result, timeUs, isKeyFrame, isDecodeOnly, isEncrypted); inputBuffer, result, timeUs, isKeyFrame, isDecodeOnly, isEncrypted, isLastSample);
// inputBuffer should be populated with data. // inputBuffer should be populated with data.
inputBuffer.flip(); inputBuffer.flip();
assertThat(inputBuffer.data.limit()).isEqualTo(length); assertThat(inputBuffer.data.limit()).isEqualTo(length);
......
...@@ -338,7 +338,8 @@ public class FakeMediaPeriod implements MediaPeriod { ...@@ -338,7 +338,8 @@ public class FakeMediaPeriod implements MediaPeriod {
lastSeekPositionUs = seekPositionUs; lastSeekPositionUs = seekPositionUs;
boolean seekedInsideStreams = true; boolean seekedInsideStreams = true;
for (FakeSampleStream sampleStream : sampleStreams) { for (FakeSampleStream sampleStream : sampleStreams) {
seekedInsideStreams &= sampleStream.seekToUs(seekPositionUs); seekedInsideStreams &=
sampleStream.seekToUs(seekPositionUs, /* allowTimeBeyondBuffer= */ false);
} }
if (!seekedInsideStreams) { if (!seekedInsideStreams) {
for (FakeSampleStream sampleStream : sampleStreams) { for (FakeSampleStream sampleStream : sampleStreams) {
......
...@@ -204,10 +204,12 @@ public class FakeSampleStream implements SampleStream { ...@@ -204,10 +204,12 @@ public class FakeSampleStream implements SampleStream {
* Seeks the stream to a new position using already available data in the queue. * Seeks the stream to a new position using already available data in the queue.
* *
* @param positionUs The new position, in microseconds. * @param positionUs The new position, in microseconds.
* @param allowTimeBeyondBuffer Whether the operation can succeed if timeUs is beyond the end of
* the queue, by seeking to the last sample (or keyframe).
* @return Whether seeking inside the available data was possible. * @return Whether seeking inside the available data was possible.
*/ */
public boolean seekToUs(long positionUs) { public boolean seekToUs(long positionUs, boolean allowTimeBeyondBuffer) {
return sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */ false); return sampleQueue.seekTo(positionUs, allowTimeBeyondBuffer);
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment