Fix capacity overflow logic in findSampleAfter

The method was recently introduced and only searched for matching
samples up to index==length. However, SampleQueue uses a circular
array to index its data and the search should continue until
relativeStartIndex+length, while also handling the overflow
in the circular array.

The tests for seeking didn't cover these overflow cases yet
because they always started writing data in an empty SampleQueue.
This can be fixed by prewarming the queue with placeholder data
to force using the overflow logic in the various seek methods.

PiperOrigin-RevId: 555963011
This commit is contained in:
tonihei 2023-08-11 15:31:55 +00:00 committed by Tianyi Feng
parent 2980c30ace
commit 418edda285
2 changed files with 49 additions and 17 deletions

View file

@ -1009,9 +1009,14 @@ public class SampleQueue implements TrackOutput {
*/
private int findSampleAfter(
int relativeStartIndex, int length, long timeUs, boolean allowTimeBeyondBuffer) {
for (int i = relativeStartIndex; i < length; i++) {
if (timesUs[i] >= timeUs) {
return i - relativeStartIndex;
int searchIndex = relativeStartIndex;
for (int i = 0; i < length; i++) {
if (timesUs[searchIndex] >= timeUs) {
return i;
}
searchIndex++;
if (searchIndex == capacity) {
searchIndex = 0;
}
}
return allowTimeBeyondBuffer ? length : -1;

View file

@ -37,6 +37,7 @@ import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.Util;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.exoplayer.FormatHolder;
import androidx.media3.exoplayer.analytics.PlayerId;
@ -159,6 +160,8 @@ public final class SampleQueueTest {
private static final TrackOutput.CryptoData CRYPTO_DATA =
new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0);
private static final int CLOSE_TO_CAPACITY_SIZE = SampleQueue.SAMPLE_CAPACITY_INCREMENT - 1;
private Allocator allocator;
private MockDrmSessionManager mockDrmSessionManager;
private DrmSession mockDrmSession;
@ -737,37 +740,40 @@ public final class SampleQueueTest {
@Test
public void seekToBeforeBuffer_notAllSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeTestData();
boolean success =
sampleQueue.seekTo(SAMPLE_TIMESTAMPS[0] - 1, /* allowTimeBeyondBuffer= */ false);
assertThat(success).isFalse();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(CLOSE_TO_CAPACITY_SIZE);
assertReadTestData();
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void seekToStartOfBuffer_notAllSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeTestData();
boolean success = sampleQueue.seekTo(SAMPLE_TIMESTAMPS[0], /* allowTimeBeyondBuffer= */ false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(CLOSE_TO_CAPACITY_SIZE);
assertReadTestData();
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void seekToEndOfBuffer_notAllSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP, /* allowTimeBeyondBuffer= */ false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(4);
assertThat(sampleQueue.getReadIndex()).isEqualTo(CLOSE_TO_CAPACITY_SIZE + 4);
assertReadTestData(
/* startFormat= */ null,
DATA_SECOND_KEYFRAME_INDEX,
@ -779,26 +785,28 @@ public final class SampleQueueTest {
@Test
public void seekToAfterBuffer_notAllSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeTestData();
boolean success =
sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP + 1, /* allowTimeBeyondBuffer= */ false);
assertThat(success).isFalse();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(CLOSE_TO_CAPACITY_SIZE);
assertReadTestData();
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void seekToAfterBufferAllowed_notAllSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeTestData();
boolean success =
sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP + 1, /* allowTimeBeyondBuffer= */ true);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(4);
assertThat(sampleQueue.getReadIndex()).isEqualTo(CLOSE_TO_CAPACITY_SIZE + 4);
assertReadTestData(
/* startFormat= */ null,
DATA_SECOND_KEYFRAME_INDEX,
@ -810,12 +818,13 @@ public final class SampleQueueTest {
@Test
public void seekToEndAndBackToStart_notAllSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP, /* allowTimeBeyondBuffer= */ false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(4);
assertThat(sampleQueue.getReadIndex()).isEqualTo(CLOSE_TO_CAPACITY_SIZE + 4);
assertReadTestData(
/* startFormat= */ null,
DATA_SECOND_KEYFRAME_INDEX,
@ -828,44 +837,48 @@ public final class SampleQueueTest {
success = sampleQueue.seekTo(SAMPLE_TIMESTAMPS[0], /* allowTimeBeyondBuffer= */ false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(CLOSE_TO_CAPACITY_SIZE);
assertReadTestData();
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void seekToBeforeBuffer_allSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeSyncSamplesOnlyTestData();
boolean success =
sampleQueue.seekTo(SAMPLE_TIMESTAMPS[0] - 1, /* allowTimeBeyondBuffer= */ false);
assertThat(success).isFalse();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(CLOSE_TO_CAPACITY_SIZE);
assertReadSyncSampleOnlyTestData();
assertNoSamplesToRead(FORMAT_SYNC_SAMPLE_ONLY_2);
}
@Test
public void seekToStartOfBuffer_allSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeSyncSamplesOnlyTestData();
boolean success = sampleQueue.seekTo(SAMPLE_TIMESTAMPS[0], /* allowTimeBeyondBuffer= */ false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(CLOSE_TO_CAPACITY_SIZE);
assertReadSyncSampleOnlyTestData();
assertNoSamplesToRead(FORMAT_SYNC_SAMPLE_ONLY_2);
}
@Test
public void seekToEndOfBuffer_allSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeSyncSamplesOnlyTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP, /* allowTimeBeyondBuffer= */ false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(SAMPLE_TIMESTAMPS.length - 1);
assertThat(sampleQueue.getReadIndex())
.isEqualTo(CLOSE_TO_CAPACITY_SIZE + SAMPLE_TIMESTAMPS.length - 1);
assertReadSyncSampleOnlyTestData(
/* firstSampleIndex= */ SAMPLE_TIMESTAMPS.length - 1, /* sampleCount= */ 1);
assertNoSamplesToRead(FORMAT_SYNC_SAMPLE_ONLY_2);
@ -873,38 +886,43 @@ public final class SampleQueueTest {
@Test
public void seekToAfterBuffer_allSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeSyncSamplesOnlyTestData();
boolean success =
sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP + 1, /* allowTimeBeyondBuffer= */ false);
assertThat(success).isFalse();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(CLOSE_TO_CAPACITY_SIZE);
assertReadSyncSampleOnlyTestData();
assertNoSamplesToRead(FORMAT_SYNC_SAMPLE_ONLY_2);
}
@Test
public void seekToAfterBufferAllowed_allSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeSyncSamplesOnlyTestData();
boolean success =
sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP + 1, /* allowTimeBeyondBuffer= */ true);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(SAMPLE_TIMESTAMPS.length);
assertThat(sampleQueue.getReadIndex())
.isEqualTo(CLOSE_TO_CAPACITY_SIZE + SAMPLE_TIMESTAMPS.length);
assertReadFormat(/* formatRequired= */ false, FORMAT_SYNC_SAMPLE_ONLY_2);
assertNoSamplesToRead(FORMAT_SYNC_SAMPLE_ONLY_2);
}
@Test
public void seekToEndAndBackToStart_allSamplesAreSyncSamples() {
writeAndDiscardPlaceholderSamples(CLOSE_TO_CAPACITY_SIZE);
writeSyncSamplesOnlyTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP, /* allowTimeBeyondBuffer= */ false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(SAMPLE_TIMESTAMPS.length - 1);
assertThat(sampleQueue.getReadIndex())
.isEqualTo(CLOSE_TO_CAPACITY_SIZE + SAMPLE_TIMESTAMPS.length - 1);
assertReadSyncSampleOnlyTestData(
/* firstSampleIndex= */ SAMPLE_TIMESTAMPS.length - 1, /* sampleCount= */ 1);
assertNoSamplesToRead(FORMAT_SYNC_SAMPLE_ONLY_2);
@ -913,7 +931,7 @@ public final class SampleQueueTest {
success = sampleQueue.seekTo(SAMPLE_TIMESTAMPS[0], /* allowTimeBeyondBuffer= */ false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(CLOSE_TO_CAPACITY_SIZE);
assertReadSyncSampleOnlyTestData();
assertNoSamplesToRead(FORMAT_SYNC_SAMPLE_ONLY_2);
}
@ -1574,6 +1592,15 @@ public final class SampleQueueTest {
(sampleFlags & C.BUFFER_FLAG_ENCRYPTED) != 0 ? CRYPTO_DATA : null);
}
private void writeAndDiscardPlaceholderSamples(int sampleCount) {
writeFormat(FORMAT_SYNC_SAMPLE_ONLY_1);
for (int i = 0; i < sampleCount; i++) {
writeSample(
Util.EMPTY_BYTE_ARRAY, /* timestampUs= */ 0, /* sampleFlags= */ C.BUFFER_FLAG_KEY_FRAME);
}
sampleQueue.discardToEnd();
}
/** Asserts correct reading of the sync-sample-only test data from {@code sampleQueue}. */
private void assertReadSyncSampleOnlyTestData() {
assertReadSyncSampleOnlyTestData(