Commit 81b0b53a by samrobinson Committed by Christos Tsilopoulos

Propagate gapless audio delay & padding.

MediaCodec does not need to be re-created in the
event of gapless metadata.

PiperOrigin-RevId: 318439694
parent 5a72b945
...@@ -170,6 +170,8 @@ ...@@ -170,6 +170,8 @@
([#7404](https://github.com/google/ExoPlayer/issues/7404)). ([#7404](https://github.com/google/ExoPlayer/issues/7404)).
* Adjust input timestamps in `MediaCodecRenderer` to account for the * Adjust input timestamps in `MediaCodecRenderer` to account for the
Codec2 MP3 decoder having lower timestamps on the output side. Codec2 MP3 decoder having lower timestamps on the output side.
* Propagate gapless audio metadata without the need to recreate the audio
decoders.
* DASH: * DASH:
* Enable support for embedded CEA-708. * Enable support for embedded CEA-708.
* HLS: * HLS:
......
...@@ -310,16 +310,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -310,16 +310,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override @Override
protected @KeepCodecResult int canKeepCodec( protected @KeepCodecResult int canKeepCodec(
MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
// TODO: We currently rely on recreating the codec when encoder delay or padding is non-zero. if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize) {
// Re-creating the codec is necessary to guarantee that onOutputMediaFormatChanged is called,
// which is where encoder delay and padding are propagated to the sink. We should find a better
// way to propagate these values, and then allow the codec to be re-used in cases where this
// would otherwise be possible.
if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize
|| oldFormat.encoderDelay != 0
|| oldFormat.encoderPadding != 0
|| newFormat.encoderDelay != 0
|| newFormat.encoderPadding != 0) {
return KEEP_CODEC_RESULT_NO; return KEEP_CODEC_RESULT_NO;
} else if (codecInfo.isSeamlessAdaptationSupported( } else if (codecInfo.isSeamlessAdaptationSupported(
oldFormat, newFormat, /* isNewFormatComplete= */ true)) { oldFormat, newFormat, /* isNewFormatComplete= */ true)) {
...@@ -388,9 +379,14 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -388,9 +379,14 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
protected void onOutputMediaFormatChanged(MediaCodec codec, MediaFormat outputMediaFormat) protected void onOutputFormatChanged(Format outputFormat) throws ExoPlaybackException {
throws ExoPlaybackException { configureOutput(outputFormat);
}
@Override
protected void configureOutput(Format outputFormat) throws ExoPlaybackException {
@C.Encoding int encoding; @C.Encoding int encoding;
MediaFormat mediaFormat;
int channelCount; int channelCount;
int sampleRate; int sampleRate;
if (passthroughFormat != null) { if (passthroughFormat != null) {
...@@ -398,18 +394,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -398,18 +394,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
channelCount = passthroughFormat.channelCount; channelCount = passthroughFormat.channelCount;
sampleRate = passthroughFormat.sampleRate; sampleRate = passthroughFormat.sampleRate;
} else { } else {
if (outputMediaFormat.containsKey(VIVO_BITS_PER_SAMPLE_KEY)) { mediaFormat = getCodec().getOutputFormat();
encoding = Util.getPcmEncoding(outputMediaFormat.getInteger(VIVO_BITS_PER_SAMPLE_KEY)); if (mediaFormat.containsKey(VIVO_BITS_PER_SAMPLE_KEY)) {
encoding = Util.getPcmEncoding(mediaFormat.getInteger(VIVO_BITS_PER_SAMPLE_KEY));
} else { } else {
encoding = getPcmEncoding(inputFormat); encoding = getPcmEncoding(outputFormat);
} }
channelCount = outputMediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); channelCount = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
sampleRate = outputMediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE); sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
} }
@Nullable int[] channelMap = null; @Nullable int[] channelMap = null;
if (codecNeedsDiscardChannelsWorkaround && channelCount == 6 && inputFormat.channelCount < 6) { if (codecNeedsDiscardChannelsWorkaround && channelCount == 6 && outputFormat.channelCount < 6) {
channelMap = new int[inputFormat.channelCount]; channelMap = new int[outputFormat.channelCount];
for (int i = 0; i < inputFormat.channelCount; i++) { for (int i = 0; i < outputFormat.channelCount; i++) {
channelMap[i] = i; channelMap[i] = i;
} }
} }
...@@ -420,11 +417,10 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -420,11 +417,10 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
sampleRate, sampleRate,
/* specifiedBufferSize= */ 0, /* specifiedBufferSize= */ 0,
channelMap, channelMap,
inputFormat.encoderDelay, outputFormat.encoderDelay,
inputFormat.encoderPadding); outputFormat.encoderPadding);
} catch (AudioSink.ConfigurationException e) { } catch (AudioSink.ConfigurationException e) {
// TODO(internal: b/145658993) Use outputFormat instead. throw createRendererException(e, outputFormat);
throw createRendererException(e, inputFormat);
} }
} }
......
...@@ -405,6 +405,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -405,6 +405,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean waitingForFirstSampleInFormat; private boolean waitingForFirstSampleInFormat;
private boolean pendingOutputEndOfStream; private boolean pendingOutputEndOfStream;
@MediaCodecOperationMode private int mediaCodecOperationMode; @MediaCodecOperationMode private int mediaCodecOperationMode;
@Nullable private ExoPlaybackException pendingPlaybackException;
protected DecoderCounters decoderCounters; protected DecoderCounters decoderCounters;
private long outputStreamOffsetUs; private long outputStreamOffsetUs;
private int pendingOutputStreamOffsetCount; private int pendingOutputStreamOffsetCount;
...@@ -623,12 +624,24 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -623,12 +624,24 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
/** /**
* Sets an exception to be re-thrown by render.
*
* @param exception The exception.
*/
protected void setPendingPlaybackException(ExoPlaybackException exception) {
pendingPlaybackException = exception;
}
/**
* Polls the pending output format queue for a given buffer timestamp. If a format is present, it * Polls the pending output format queue for a given buffer timestamp. If a format is present, it
* is removed and returned. Otherwise returns {@code null}. Subclasses should only call this * is removed and returned. Otherwise returns {@code null}. Subclasses should only call this
* method if they are taking over responsibility for output format propagation (e.g., when using * method if they are taking over responsibility for output format propagation (e.g., when using
* video tunneling). * video tunneling).
*
* @throws ExoPlaybackException Thrown if an error occurs as a result of the output format change.
*/ */
protected final void updateOutputFormatForTime(long presentationTimeUs) { protected final void updateOutputFormatForTime(long presentationTimeUs)
throws ExoPlaybackException {
@Nullable Format format = formatQueue.pollFloor(presentationTimeUs); @Nullable Format format = formatQueue.pollFloor(presentationTimeUs);
if (format != null) { if (format != null) {
outputFormat = format; outputFormat = format;
...@@ -784,6 +797,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -784,6 +797,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
pendingOutputEndOfStream = false; pendingOutputEndOfStream = false;
processEndOfStream(); processEndOfStream();
} }
if (pendingPlaybackException != null) {
ExoPlaybackException playbackException = pendingPlaybackException;
pendingPlaybackException = null;
throw playbackException;
}
try { try {
if (outputStreamEnded) { if (outputStreamEnded) {
renderToEndOfStream(); renderToEndOfStream();
...@@ -908,6 +927,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -908,6 +927,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecInfo = null; codecInfo = null;
codecFormat = null; codecFormat = null;
codecHasOutputMediaFormat = false; codecHasOutputMediaFormat = false;
pendingPlaybackException = null;
codecOperatingRate = CODEC_OPERATING_RATE_UNSET; codecOperatingRate = CODEC_OPERATING_RATE_UNSET;
codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER; codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER;
codecNeedsReconfigureWorkaround = false; codecNeedsReconfigureWorkaround = false;
...@@ -1490,8 +1510,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1490,8 +1510,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* <p>The default implementation is a no-op. * <p>The default implementation is a no-op.
* *
* @param outputFormat The new output {@link Format}. * @param outputFormat The new output {@link Format}.
* @throws ExoPlaybackException Thrown if an error occurs handling the new output format.
*/ */
protected void onOutputFormatChanged(Format outputFormat) { protected void onOutputFormatChanged(Format outputFormat) throws ExoPlaybackException {
// Do nothing. // Do nothing.
} }
...@@ -1501,8 +1522,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1501,8 +1522,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* <p>The default implementation is a no-op. * <p>The default implementation is a no-op.
* *
* @param outputFormat The format to configure the output with. * @param outputFormat The format to configure the output with.
* @throws ExoPlaybackException Thrown if an error occurs configuring the output.
*/ */
protected void configureOutput(Format outputFormat) { protected void configureOutput(Format outputFormat) throws ExoPlaybackException {
// Do nothing. // Do nothing.
} }
...@@ -1538,8 +1560,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1538,8 +1560,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* <p>The default implementation is a no-op. * <p>The default implementation is a no-op.
* *
* @param buffer The buffer to be queued. * @param buffer The buffer to be queued.
* @throws ExoPlaybackException Thrown if an error occurs handling the input buffer.
*/ */
protected void onQueueInputBuffer(DecoderInputBuffer buffer) { protected void onQueueInputBuffer(DecoderInputBuffer buffer) throws ExoPlaybackException {
// Do nothing. // Do nothing.
} }
......
...@@ -643,11 +643,14 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -643,11 +643,14 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
/** /**
* Called immediately before an input buffer is queued into the codec. * Called immediately before an input buffer is queued into the codec.
* *
* <p>In tunneling mode for pre Marshmallow, the buffer is treated as if immediately output.
*
* @param buffer The buffer to be queued. * @param buffer The buffer to be queued.
* @throws ExoPlaybackException Thrown if an error occurs handling the input buffer.
*/ */
@CallSuper @CallSuper
@Override @Override
protected void onQueueInputBuffer(DecoderInputBuffer buffer) { protected void onQueueInputBuffer(DecoderInputBuffer buffer) throws ExoPlaybackException {
// In tunneling mode the device may do frame rate conversion, so in general we can't keep track // In tunneling mode the device may do frame rate conversion, so in general we can't keep track
// of the number of buffers in the codec. // of the number of buffers in the codec.
if (!tunneling) { if (!tunneling) {
...@@ -891,7 +894,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -891,7 +894,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
/** Called when a buffer was processed in tunneling mode. */ /** Called when a buffer was processed in tunneling mode. */
protected void onProcessedTunneledBuffer(long presentationTimeUs) { protected void onProcessedTunneledBuffer(long presentationTimeUs) throws ExoPlaybackException {
updateOutputFormatForTime(presentationTimeUs); updateOutputFormatForTime(presentationTimeUs);
maybeNotifyVideoSizeChanged(); maybeNotifyVideoSizeChanged();
decoderCounters.renderedOutputBufferCount++; decoderCounters.renderedOutputBufferCount++;
...@@ -1808,7 +1811,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -1808,7 +1811,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
if (presentationTimeUs == TUNNELING_EOS_PRESENTATION_TIME_US) { if (presentationTimeUs == TUNNELING_EOS_PRESENTATION_TIME_US) {
onProcessedTunneledEndOfStream(); onProcessedTunneledEndOfStream();
} else { } else {
onProcessedTunneledBuffer(presentationTimeUs); try {
onProcessedTunneledBuffer(presentationTimeUs);
} catch (ExoPlaybackException e) {
setPendingPlaybackException(e);
}
} }
} }
} }
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.audio; package com.google.android.exoplayer2.audio;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
...@@ -25,13 +26,12 @@ import android.os.SystemClock; ...@@ -25,13 +26,12 @@ import android.os.SystemClock;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.RendererConfiguration; import com.google.android.exoplayer2.RendererConfiguration;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer2.testutil.FakeSampleStream; import com.google.android.exoplayer2.testutil.FakeSampleStream;
import com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem; import com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
...@@ -61,6 +61,7 @@ public class MediaCodecAudioRendererTest { ...@@ -61,6 +61,7 @@ public class MediaCodecAudioRendererTest {
.build(); .build();
private MediaCodecAudioRenderer mediaCodecAudioRenderer; private MediaCodecAudioRenderer mediaCodecAudioRenderer;
private MediaCodecSelector mediaCodecSelector;
@Mock private AudioSink audioSink; @Mock private AudioSink audioSink;
...@@ -72,7 +73,7 @@ public class MediaCodecAudioRendererTest { ...@@ -72,7 +73,7 @@ public class MediaCodecAudioRendererTest {
when(audioSink.handleBuffer(any(), anyLong(), anyInt())).thenReturn(true); when(audioSink.handleBuffer(any(), anyLong(), anyInt())).thenReturn(true);
MediaCodecSelector mediaCodecSelector = mediaCodecSelector =
new MediaCodecSelector() { new MediaCodecSelector() {
@Override @Override
public List<MediaCodecInfo> getDecoderInfos( public List<MediaCodecInfo> getDecoderInfos(
...@@ -98,17 +99,75 @@ public class MediaCodecAudioRendererTest { ...@@ -98,17 +99,75 @@ public class MediaCodecAudioRendererTest {
/* enableDecoderFallback= */ false, /* enableDecoderFallback= */ false,
/* eventHandler= */ null, /* eventHandler= */ null,
/* eventListener= */ null, /* eventListener= */ null,
audioSink) { audioSink);
@Override }
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
throws DecoderQueryException { @Test
return RendererCapabilities.create(FORMAT_HANDLED); public void render_configuresAudioSink_afterFormatChange() throws Exception {
} Format changedFormat = AUDIO_AAC.buildUpon().setSampleRate(48_000).setEncoderDelay(400).build();
};
FakeSampleStream fakeSampleStream =
new FakeSampleStream(
/* format= */ AUDIO_AAC,
DrmSessionManager.DUMMY,
/* eventDispatcher= */ null,
/* firstSampleTimeUs= */ 0,
/* timeUsIncrement= */ 50,
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
new FakeSampleStreamItem(changedFormat),
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
FakeSampleStreamItem.END_OF_STREAM_ITEM);
mediaCodecAudioRenderer.enable(
RendererConfiguration.DEFAULT,
new Format[] {AUDIO_AAC, changedFormat},
fakeSampleStream,
/* positionUs= */ 0,
/* joining= */ false,
/* mayRenderStartOfStream= */ false,
/* offsetUs */ 0);
mediaCodecAudioRenderer.start();
mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
mediaCodecAudioRenderer.render(/* positionUs= */ 250, SystemClock.elapsedRealtime() * 1000);
mediaCodecAudioRenderer.setCurrentStreamFinal();
int positionUs = 500;
do {
mediaCodecAudioRenderer.render(positionUs, SystemClock.elapsedRealtime() * 1000);
positionUs += 250;
} while (!mediaCodecAudioRenderer.isEnded());
verify(audioSink)
.configure(
AUDIO_AAC.pcmEncoding,
AUDIO_AAC.channelCount,
AUDIO_AAC.sampleRate,
/* specifiedBufferSize= */ 0,
/* outputChannels= */ null,
AUDIO_AAC.encoderDelay,
AUDIO_AAC.encoderPadding);
verify(audioSink)
.configure(
changedFormat.pcmEncoding,
changedFormat.channelCount,
changedFormat.sampleRate,
/* specifiedBufferSize= */ 0,
/* outputChannels= */ null,
changedFormat.encoderDelay,
changedFormat.encoderPadding);
} }
@Test @Test
public void render_configuresAudioSink() throws Exception { public void render_configuresAudioSink_afterGaplessFormatChange() throws Exception {
Format changedFormat =
AUDIO_AAC.buildUpon().setEncoderDelay(400).setEncoderPadding(232).build();
FakeSampleStream fakeSampleStream = FakeSampleStream fakeSampleStream =
new FakeSampleStream( new FakeSampleStream(
/* format= */ AUDIO_AAC, /* format= */ AUDIO_AAC,
...@@ -117,11 +176,17 @@ public class MediaCodecAudioRendererTest { ...@@ -117,11 +176,17 @@ public class MediaCodecAudioRendererTest {
/* firstSampleTimeUs= */ 0, /* firstSampleTimeUs= */ 0,
/* timeUsIncrement= */ 50, /* timeUsIncrement= */ 50,
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME), new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
new FakeSampleStreamItem(changedFormat),
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
FakeSampleStreamItem.END_OF_STREAM_ITEM); FakeSampleStreamItem.END_OF_STREAM_ITEM);
mediaCodecAudioRenderer.enable( mediaCodecAudioRenderer.enable(
RendererConfiguration.DEFAULT, RendererConfiguration.DEFAULT,
new Format[] {AUDIO_AAC}, new Format[] {AUDIO_AAC, changedFormat},
fakeSampleStream, fakeSampleStream,
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
...@@ -148,5 +213,77 @@ public class MediaCodecAudioRendererTest { ...@@ -148,5 +213,77 @@ public class MediaCodecAudioRendererTest {
/* outputChannels= */ null, /* outputChannels= */ null,
AUDIO_AAC.encoderDelay, AUDIO_AAC.encoderDelay,
AUDIO_AAC.encoderPadding); AUDIO_AAC.encoderPadding);
verify(audioSink)
.configure(
changedFormat.pcmEncoding,
changedFormat.channelCount,
changedFormat.sampleRate,
/* specifiedBufferSize= */ 0,
/* outputChannels= */ null,
changedFormat.encoderDelay,
changedFormat.encoderPadding);
}
@Test
public void render_throwsExoPlaybackExceptionJustOnce_whenSet() throws Exception {
MediaCodecAudioRenderer exceptionThrowingRenderer =
new MediaCodecAudioRenderer(
ApplicationProvider.getApplicationContext(),
mediaCodecSelector,
/* eventHandler= */ null,
/* eventListener= */ null) {
@Override
protected void onOutputFormatChanged(Format outputFormat) throws ExoPlaybackException {
super.onOutputFormatChanged(outputFormat);
if (!outputFormat.equals(AUDIO_AAC)) {
setPendingPlaybackException(
ExoPlaybackException.createForRenderer(
new AudioSink.ConfigurationException("Test"),
"rendererName",
/* rendererIndex= */ 0,
outputFormat,
FORMAT_HANDLED));
}
}
};
Format changedFormat = AUDIO_AAC.buildUpon().setSampleRate(32_000).build();
FakeSampleStream fakeSampleStream =
new FakeSampleStream(
/* format= */ AUDIO_AAC,
DrmSessionManager.DUMMY,
/* eventDispatcher= */ null,
/* firstSampleTimeUs= */ 0,
/* timeUsIncrement= */ 50,
new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
FakeSampleStreamItem.END_OF_STREAM_ITEM);
exceptionThrowingRenderer.enable(
RendererConfiguration.DEFAULT,
new Format[] {AUDIO_AAC, changedFormat},
fakeSampleStream,
/* positionUs= */ 0,
/* joining= */ false,
/* mayRenderStartOfStream= */ false,
/* offsetUs */ 0);
exceptionThrowingRenderer.start();
exceptionThrowingRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
exceptionThrowingRenderer.render(/* positionUs= */ 250, SystemClock.elapsedRealtime() * 1000);
// Simulating the exception being thrown when not traceable back to render.
exceptionThrowingRenderer.onOutputFormatChanged(changedFormat);
assertThrows(
ExoPlaybackException.class,
() ->
exceptionThrowingRenderer.render(
/* positionUs= */ 500, SystemClock.elapsedRealtime() * 1000));
// Doesn't throw an exception because it's cleared after being thrown in the previous call to
// render.
exceptionThrowingRenderer.render(/* positionUs= */ 750, SystemClock.elapsedRealtime() * 1000);
} }
} }
...@@ -142,7 +142,7 @@ import java.util.ArrayList; ...@@ -142,7 +142,7 @@ import java.util.ArrayList;
} }
@Override @Override
protected void onQueueInputBuffer(DecoderInputBuffer buffer) { protected void onQueueInputBuffer(DecoderInputBuffer buffer) throws ExoPlaybackException {
super.onQueueInputBuffer(buffer); super.onQueueInputBuffer(buffer);
insertTimestamp(buffer.timeUs); insertTimestamp(buffer.timeUs);
maybeShiftTimestampsList(); maybeShiftTimestampsList();
......
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