Expect PresentationTime Discontinuity During Stream Transitions
PiperOrigin-RevId: 440378974
This commit is contained in:
parent
20547c3392
commit
c007068ddb
@ -585,8 +585,9 @@ public final class C {
|
||||
|
||||
/**
|
||||
* Flags which can apply to a buffer containing a media sample. Possible flag values are {@link
|
||||
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_LAST_SAMPLE},
|
||||
* {@link #BUFFER_FLAG_ENCRYPTED} and {@link #BUFFER_FLAG_DECODE_ONLY}.
|
||||
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_FIRST_SAMPLE},
|
||||
* {@link #BUFFER_FLAG_LAST_SAMPLE}, {@link #BUFFER_FLAG_ENCRYPTED} and {@link
|
||||
* #BUFFER_FLAG_DECODE_ONLY}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@ -597,6 +598,7 @@ public final class C {
|
||||
value = {
|
||||
BUFFER_FLAG_KEY_FRAME,
|
||||
BUFFER_FLAG_END_OF_STREAM,
|
||||
BUFFER_FLAG_FIRST_SAMPLE,
|
||||
BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA,
|
||||
BUFFER_FLAG_LAST_SAMPLE,
|
||||
BUFFER_FLAG_ENCRYPTED,
|
||||
@ -608,6 +610,8 @@ public final class C {
|
||||
/** Flag for empty buffers that signal that the end of the stream was reached. */
|
||||
@UnstableApi
|
||||
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
|
||||
/** Indicates that a buffer is known to contain the first media sample of the stream. */
|
||||
@UnstableApi public static final int BUFFER_FLAG_FIRST_SAMPLE = 1 << 27; // 0x08000000
|
||||
/** Indicates that a buffer has supplemental data. */
|
||||
@UnstableApi public static final int BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA = 1 << 28; // 0x10000000
|
||||
/** Indicates that a buffer is known to contain the last media sample of the stream. */
|
||||
|
@ -34,6 +34,11 @@ public abstract class Buffer {
|
||||
return getFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
||||
}
|
||||
|
||||
/** Returns whether the {@link C#BUFFER_FLAG_FIRST_SAMPLE} flag is set. */
|
||||
public final boolean isFirstSample() {
|
||||
return getFlag(C.BUFFER_FLAG_FIRST_SAMPLE);
|
||||
}
|
||||
|
||||
/** Returns whether the {@link C#BUFFER_FLAG_END_OF_STREAM} flag is set. */
|
||||
public final boolean isEndOfStream() {
|
||||
return getFlag(C.BUFFER_FLAG_END_OF_STREAM);
|
||||
|
@ -235,6 +235,9 @@ public abstract class SimpleDecoder<
|
||||
if (inputBuffer.isDecodeOnly()) {
|
||||
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
||||
}
|
||||
if (inputBuffer.isFirstSample()) {
|
||||
outputBuffer.addFlag(C.BUFFER_FLAG_FIRST_SAMPLE);
|
||||
}
|
||||
@Nullable E exception;
|
||||
try {
|
||||
exception = decode(inputBuffer, outputBuffer, resetDecoder);
|
||||
|
@ -130,6 +130,7 @@ public abstract class DecoderAudioRenderer<
|
||||
private int encoderPadding;
|
||||
|
||||
private boolean experimentalKeepAudioTrackOnSeek;
|
||||
private boolean firstStreamSampleRead;
|
||||
|
||||
@Nullable private T decoder;
|
||||
|
||||
@ -389,6 +390,9 @@ public abstract class DecoderAudioRenderer<
|
||||
decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount;
|
||||
audioSink.handleDiscontinuity();
|
||||
}
|
||||
if (outputBuffer.isFirstSample()) {
|
||||
audioSink.handleDiscontinuity();
|
||||
}
|
||||
}
|
||||
|
||||
if (outputBuffer.isEndOfStream()) {
|
||||
@ -470,6 +474,10 @@ public abstract class DecoderAudioRenderer<
|
||||
inputBuffer = null;
|
||||
return false;
|
||||
}
|
||||
if (!firstStreamSampleRead) {
|
||||
firstStreamSampleRead = true;
|
||||
inputBuffer.addFlag(C.BUFFER_FLAG_FIRST_SAMPLE);
|
||||
}
|
||||
inputBuffer.flip();
|
||||
inputBuffer.format = inputFormat;
|
||||
onQueueInputBuffer(inputBuffer);
|
||||
@ -587,6 +595,13 @@ public abstract class DecoderAudioRenderer<
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs)
|
||||
throws ExoPlaybackException {
|
||||
super.onStreamChanged(formats, startPositionUs, offsetUs);
|
||||
firstStreamSampleRead = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(@MessageType int messageType, @Nullable Object message)
|
||||
throws ExoPlaybackException {
|
||||
|
@ -887,7 +887,6 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
|
||||
@Override
|
||||
public void handleDiscontinuity() {
|
||||
// Force resynchronization after a skipped buffer.
|
||||
startMediaTimeUsNeedsSync = true;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,12 @@ import static androidx.media3.exoplayer.RendererCapabilities.DECODER_SUPPORT_PRI
|
||||
import static androidx.media3.exoplayer.RendererCapabilities.TUNNELING_NOT_SUPPORTED;
|
||||
import static androidx.media3.exoplayer.RendererCapabilities.TUNNELING_SUPPORTED;
|
||||
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM;
|
||||
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@ -46,6 +51,7 @@ import com.google.common.collect.ImmutableList;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.annotation.Config;
|
||||
@ -139,6 +145,100 @@ public class DecoderAudioRendererTest {
|
||||
verify(mockAudioSink, times(1)).reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void firstSampleOfStreamSignalsDiscontinuityToAudioSink() throws Exception {
|
||||
when(mockAudioSink.handleBuffer(any(), anyLong(), anyInt())).thenReturn(true);
|
||||
when(mockAudioSink.isEnded()).thenReturn(true);
|
||||
InOrder inOrderAudioSink = inOrder(mockAudioSink);
|
||||
FakeSampleStream fakeSampleStream =
|
||||
new FakeSampleStream(
|
||||
new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024),
|
||||
/* mediaSourceEventDispatcher= */ null,
|
||||
DrmSessionManager.DRM_UNSUPPORTED,
|
||||
new DrmSessionEventListener.EventDispatcher(),
|
||||
FORMAT,
|
||||
ImmutableList.of(
|
||||
oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME),
|
||||
oneByteSample(/* timeUs= */ 1_000),
|
||||
END_OF_STREAM_ITEM));
|
||||
fakeSampleStream.writeData(/* startPositionUs= */ 0);
|
||||
audioRenderer.enable(
|
||||
RendererConfiguration.DEFAULT,
|
||||
new Format[] {FORMAT},
|
||||
fakeSampleStream,
|
||||
/* positionUs= */ 0,
|
||||
/* joining= */ false,
|
||||
/* mayRenderStartOfStream= */ true,
|
||||
/* startPositionUs= */ 0,
|
||||
/* offsetUs= */ 0);
|
||||
|
||||
audioRenderer.setCurrentStreamFinal();
|
||||
while (!audioRenderer.isEnded()) {
|
||||
audioRenderer.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0);
|
||||
}
|
||||
|
||||
inOrderAudioSink.verify(mockAudioSink, times(1)).handleDiscontinuity();
|
||||
inOrderAudioSink.verify(mockAudioSink, times(2)).handleBuffer(any(), anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void firstSampleOfReplacementStreamSignalsDiscontinuityToAudioSink() throws Exception {
|
||||
when(mockAudioSink.handleBuffer(any(), anyLong(), anyInt())).thenReturn(true);
|
||||
when(mockAudioSink.isEnded()).thenReturn(true);
|
||||
InOrder inOrderAudioSink = inOrder(mockAudioSink);
|
||||
FakeSampleStream fakeSampleStream1 =
|
||||
new FakeSampleStream(
|
||||
new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024),
|
||||
/* mediaSourceEventDispatcher= */ null,
|
||||
DrmSessionManager.DRM_UNSUPPORTED,
|
||||
new DrmSessionEventListener.EventDispatcher(),
|
||||
FORMAT,
|
||||
ImmutableList.of(
|
||||
oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME),
|
||||
oneByteSample(/* timeUs= */ 1_000),
|
||||
END_OF_STREAM_ITEM));
|
||||
fakeSampleStream1.writeData(/* startPositionUs= */ 0);
|
||||
FakeSampleStream fakeSampleStream2 =
|
||||
new FakeSampleStream(
|
||||
new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024),
|
||||
/* mediaSourceEventDispatcher= */ null,
|
||||
DrmSessionManager.DRM_UNSUPPORTED,
|
||||
new DrmSessionEventListener.EventDispatcher(),
|
||||
FORMAT,
|
||||
ImmutableList.of(
|
||||
oneByteSample(/* timeUs= */ 1_000_000, C.BUFFER_FLAG_KEY_FRAME),
|
||||
oneByteSample(/* timeUs= */ 1_001_000),
|
||||
END_OF_STREAM_ITEM));
|
||||
fakeSampleStream2.writeData(/* startPositionUs= */ 0);
|
||||
audioRenderer.enable(
|
||||
RendererConfiguration.DEFAULT,
|
||||
new Format[] {FORMAT},
|
||||
fakeSampleStream1,
|
||||
/* positionUs= */ 0,
|
||||
/* joining= */ false,
|
||||
/* mayRenderStartOfStream= */ true,
|
||||
/* startPositionUs= */ 0,
|
||||
/* offsetUs= */ 0);
|
||||
|
||||
while (!audioRenderer.hasReadStreamToEnd()) {
|
||||
audioRenderer.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0);
|
||||
}
|
||||
audioRenderer.replaceStream(
|
||||
new Format[] {FORMAT},
|
||||
fakeSampleStream2,
|
||||
/* startPositionUs= */ 1_000_000,
|
||||
/* offsetUs= */ 1_000_000);
|
||||
audioRenderer.setCurrentStreamFinal();
|
||||
while (!audioRenderer.isEnded()) {
|
||||
audioRenderer.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0);
|
||||
}
|
||||
|
||||
inOrderAudioSink.verify(mockAudioSink, times(1)).handleDiscontinuity();
|
||||
inOrderAudioSink.verify(mockAudioSink, times(2)).handleBuffer(any(), anyLong(), anyInt());
|
||||
inOrderAudioSink.verify(mockAudioSink, times(1)).handleDiscontinuity();
|
||||
inOrderAudioSink.verify(mockAudioSink, times(2)).handleBuffer(any(), anyLong(), anyInt());
|
||||
}
|
||||
|
||||
private static final class FakeDecoder
|
||||
extends SimpleDecoder<DecoderInputBuffer, SimpleDecoderOutputBuffer, DecoderException> {
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
discontinuity:
|
||||
config:
|
||||
pcmEncoding = 2
|
||||
channelCount = 2
|
||||
|
@ -1,3 +1,4 @@
|
||||
discontinuity:
|
||||
config:
|
||||
pcmEncoding = 536870912
|
||||
channelCount = 2
|
||||
|
Loading…
x
Reference in New Issue
Block a user