diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e72a71b11b..3be18633be 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -26,6 +26,8 @@ ([#5887](https://github.com/google/ExoPlayer/issues/5887)). * Fix bug where `AnalyticsListener` callbacks can arrive in the wrong 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 `AnalyticsListener` to notify when all simultaneous state changes have been handled and the values reported through callbacks are again diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/BatchBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/BatchBuffer.java index 3c40fe02d4..f770e92a21 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/BatchBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/BatchBuffer.java @@ -153,26 +153,26 @@ import java.nio.ByteBuffer; } 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()) { setFlags(C.BUFFER_FLAG_END_OF_STREAM); - } - if (accessUnit.isDecodeOnly()) { - setFlags(C.BUFFER_FLAG_DECODE_ONLY); - } - if (accessUnit.isKeyFrame()) { - setFlags(C.BUFFER_FLAG_KEY_FRAME); - } - accessUnitCount++; - timeUs = accessUnit.timeUs; - if (accessUnitCount == 1) { // First read of the buffer - firstAccessUnitTimeUs = timeUs; + } else { + timeUs = accessUnit.timeUs; + if (accessUnit.isDecodeOnly()) { + setFlags(C.BUFFER_FLAG_DECODE_ONLY); + } + if (accessUnit.isKeyFrame()) { + setFlags(C.BUFFER_FLAG_KEY_FRAME); + } + @Nullable ByteBuffer accessUnitData = accessUnit.data; + if (accessUnitData != null) { + accessUnit.flip(); + ensureSpaceForWrite(accessUnitData.remaining()); + this.data.put(accessUnitData); + } + accessUnitCount++; + if (accessUnitCount == 1) { + firstAccessUnitTimeUs = timeUs; + } } accessUnit.clear(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 1b3cebe6c0..c1368cf91e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -397,6 +397,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer { outputStreamStartPositionUs = C.TIME_UNSET; outputStreamOffsetUs = C.TIME_UNSET; 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(); } @@ -2122,7 +2127,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * iteration of the rendering loop. * @param elapsedRealtimeUs {@link SystemClock#elapsedRealtime()} in microseconds, measured at the * 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 * format change. */ @@ -2130,7 +2135,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { throws ExoPlaybackException { BatchBuffer batchBuffer = bypassBatchBuffer; - // Let's process the pending buffer if any. + // Process any data in the batch buffer. checkState(!outputStreamEnded); if (!batchBuffer.isEmpty()) { // Optimisation: Do not process buffer if empty. if (processOutputBuffer( @@ -2145,12 +2150,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { batchBuffer.isDecodeOnly(), batchBuffer.isEndOfStream(), outputFormat)) { - // Buffer completely processed onProcessedOutputBuffer(batchBuffer.getLastAccessUnitTimeUs()); } 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()) { outputStreamEnded = true; return false; @@ -2185,19 +2191,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { onInputFormatChanged(formatHolder); } + boolean haveDataToProcess = false; if (batchBuffer.isEndOfStream()) { inputStreamEnded = true; + haveDataToProcess = true; + } + if (!batchBuffer.isEmpty()) { + batchBuffer.flip(); + haveDataToProcess = true; } - if (batchBuffer.isEmpty()) { - return false; // The buffer could not be filled, there is nothing more to do. - } - batchBuffer.flip(); // Buffer at least partially full, it can now be processed. - // MediaCodec outputs buffers in native endian: - // 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; + return haveDataToProcess; } /** diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/SilencePlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/SilencePlaybackTest.java index c45d559b12..c1905da7c7 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/SilencePlaybackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/SilencePlaybackTest.java @@ -56,4 +56,25 @@ public final class SilencePlaybackTest { DumpFileAsserts.assertOutput( 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"); + } } diff --git a/testdata/src/test/assets/playbackdumps/silence/0ms.dump b/testdata/src/test/assets/playbackdumps/silence/0ms.dump new file mode 100644 index 0000000000..e69de29bb2