Commit 5f3d1c1b by olly Committed by Oliver Woodman

Fix bypass mode when the stream is empty

#minor-release
Issue: #8374
PiperOrigin-RevId: 348792965
parent e71ce067
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
([#5887](https://github.com/google/ExoPlayer/issues/5887)). ([#5887](https://github.com/google/ExoPlayer/issues/5887)).
* Fix bug where `AnalyticsListener` callbacks can arrive in the wrong * Fix bug where `AnalyticsListener` callbacks can arrive in the wrong
order ([#8048](https://github.com/google/ExoPlayer/issues/8048)). order ([#8048](https://github.com/google/ExoPlayer/issues/8048)).
* Fix `MediaCodecRenderer` issue where empty streams would fail to play in
bypass mode ([#8374](https://github.com/google/ExoPlayer/issues/8374)).
* Add `onEvents` callback to `Player.EventListener` and * Add `onEvents` callback to `Player.EventListener` and
`AnalyticsListener` to notify when all simultaneous state changes have `AnalyticsListener` to notify when all simultaneous state changes have
been handled and the values reported through callbacks are again been handled and the values reported through callbacks are again
......
...@@ -153,26 +153,26 @@ import java.nio.ByteBuffer; ...@@ -153,26 +153,26 @@ import java.nio.ByteBuffer;
} }
private void putAccessUnit(DecoderInputBuffer accessUnit) { private void putAccessUnit(DecoderInputBuffer accessUnit) {
@Nullable ByteBuffer accessUnitData = accessUnit.data;
if (accessUnitData != null) {
accessUnit.flip();
ensureSpaceForWrite(accessUnitData.remaining());
this.data.put(accessUnitData);
}
if (accessUnit.isEndOfStream()) { if (accessUnit.isEndOfStream()) {
setFlags(C.BUFFER_FLAG_END_OF_STREAM); setFlags(C.BUFFER_FLAG_END_OF_STREAM);
} } else {
if (accessUnit.isDecodeOnly()) { timeUs = accessUnit.timeUs;
setFlags(C.BUFFER_FLAG_DECODE_ONLY); if (accessUnit.isDecodeOnly()) {
} setFlags(C.BUFFER_FLAG_DECODE_ONLY);
if (accessUnit.isKeyFrame()) { }
setFlags(C.BUFFER_FLAG_KEY_FRAME); if (accessUnit.isKeyFrame()) {
} setFlags(C.BUFFER_FLAG_KEY_FRAME);
accessUnitCount++; }
timeUs = accessUnit.timeUs; @Nullable ByteBuffer accessUnitData = accessUnit.data;
if (accessUnitCount == 1) { // First read of the buffer if (accessUnitData != null) {
firstAccessUnitTimeUs = timeUs; accessUnit.flip();
ensureSpaceForWrite(accessUnitData.remaining());
this.data.put(accessUnitData);
}
accessUnitCount++;
if (accessUnitCount == 1) {
firstAccessUnitTimeUs = timeUs;
}
} }
accessUnit.clear(); accessUnit.clear();
} }
......
...@@ -397,6 +397,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -397,6 +397,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
outputStreamStartPositionUs = C.TIME_UNSET; outputStreamStartPositionUs = C.TIME_UNSET;
outputStreamOffsetUs = C.TIME_UNSET; outputStreamOffsetUs = C.TIME_UNSET;
bypassBatchBuffer = new BatchBuffer(); bypassBatchBuffer = new BatchBuffer();
bypassBatchBuffer.ensureSpaceForWrite(/* length= */ 0);
// MediaCodec outputs audio buffers in native endian:
// https://developer.android.com/reference/android/media/MediaCodec#raw-audio-buffers
// and code called from MediaCodecAudioRenderer.processOutputBuffer expects this endianness.
bypassBatchBuffer.data.order(ByteOrder.nativeOrder());
resetCodecStateForRelease(); resetCodecStateForRelease();
} }
...@@ -2122,7 +2127,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -2122,7 +2127,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* iteration of the rendering loop. * iteration of the rendering loop.
* @param elapsedRealtimeUs {@link SystemClock#elapsedRealtime()} in microseconds, measured at the * @param elapsedRealtimeUs {@link SystemClock#elapsedRealtime()} in microseconds, measured at the
* start of the current iteration of the rendering loop. * start of the current iteration of the rendering loop.
* @return If more buffers are ready to be rendered. * @return Whether immediately calling this method again will make more progress.
* @throws ExoPlaybackException If an error occurred while processing a buffer or handling a * @throws ExoPlaybackException If an error occurred while processing a buffer or handling a
* format change. * format change.
*/ */
...@@ -2130,7 +2135,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -2130,7 +2135,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
throws ExoPlaybackException { throws ExoPlaybackException {
BatchBuffer batchBuffer = bypassBatchBuffer; BatchBuffer batchBuffer = bypassBatchBuffer;
// Let's process the pending buffer if any. // Process any data in the batch buffer.
checkState(!outputStreamEnded); checkState(!outputStreamEnded);
if (!batchBuffer.isEmpty()) { // Optimisation: Do not process buffer if empty. if (!batchBuffer.isEmpty()) { // Optimisation: Do not process buffer if empty.
if (processOutputBuffer( if (processOutputBuffer(
...@@ -2145,12 +2150,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -2145,12 +2150,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
batchBuffer.isDecodeOnly(), batchBuffer.isDecodeOnly(),
batchBuffer.isEndOfStream(), batchBuffer.isEndOfStream(),
outputFormat)) { outputFormat)) {
// Buffer completely processed
onProcessedOutputBuffer(batchBuffer.getLastAccessUnitTimeUs()); onProcessedOutputBuffer(batchBuffer.getLastAccessUnitTimeUs());
} else { } else {
return false; // Could not process buffer, let's try later. // Could not process the whole buffer. Try again later.
return false;
} }
} }
// Process the end of stream, if it has been reached.
if (batchBuffer.isEndOfStream()) { if (batchBuffer.isEndOfStream()) {
outputStreamEnded = true; outputStreamEnded = true;
return false; return false;
...@@ -2185,19 +2191,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -2185,19 +2191,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
onInputFormatChanged(formatHolder); onInputFormatChanged(formatHolder);
} }
boolean haveDataToProcess = false;
if (batchBuffer.isEndOfStream()) { if (batchBuffer.isEndOfStream()) {
inputStreamEnded = true; inputStreamEnded = true;
haveDataToProcess = true;
} }
if (!batchBuffer.isEmpty()) {
if (batchBuffer.isEmpty()) { batchBuffer.flip();
return false; // The buffer could not be filled, there is nothing more to do. haveDataToProcess = true;
} }
batchBuffer.flip(); // Buffer at least partially full, it can now be processed.
// MediaCodec outputs buffers in native endian: return haveDataToProcess;
// https://developer.android.com/reference/android/media/MediaCodec#raw-audio-buffers
// and code called from processOutputBuffer expects this endianness.
batchBuffer.data.order(ByteOrder.nativeOrder());
return true;
} }
/** /**
......
...@@ -56,4 +56,25 @@ public final class SilencePlaybackTest { ...@@ -56,4 +56,25 @@ public final class SilencePlaybackTest {
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
applicationContext, playbackOutput, "playbackdumps/silence/500ms.dump"); applicationContext, playbackOutput, "playbackdumps/silence/500ms.dump");
} }
@Test
public void test_0ms() throws Exception {
Context applicationContext = ApplicationProvider.getApplicationContext();
CapturingRenderersFactory capturingRenderersFactory =
new CapturingRenderersFactory(applicationContext);
SimpleExoPlayer player =
new SimpleExoPlayer.Builder(applicationContext, capturingRenderersFactory)
.setClock(new AutoAdvancingFakeClock())
.build();
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
player.setMediaSource(new SilenceMediaSource(/* durationUs= */ 0));
player.prepare();
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
DumpFileAsserts.assertOutput(
applicationContext, playbackOutput, "playbackdumps/silence/0ms.dump");
}
} }
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