diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 41836c2648..56a597d916 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -29,6 +29,9 @@ * Smooth Streaming Extension: * RTSP Extension: * Decoder Extensions (FFmpeg, VP9, AV1, etc.): + * Add `DecoderOutputBuffer.shouldBeSkipped` to directly mark output + buffers that don't need to be presented. This is preferred over + `C.BUFFER_FLAG_DECODE_ONLY`. * MIDI extension: * Leanback extension: * Cast Extension: diff --git a/libraries/decoder/src/main/java/androidx/media3/decoder/Buffer.java b/libraries/decoder/src/main/java/androidx/media3/decoder/Buffer.java index 5c4b0e0dbd..6e2dbeb581 100644 --- a/libraries/decoder/src/main/java/androidx/media3/decoder/Buffer.java +++ b/libraries/decoder/src/main/java/androidx/media3/decoder/Buffer.java @@ -15,6 +15,7 @@ */ package androidx.media3.decoder; +import androidx.annotation.CallSuper; import androidx.media3.common.C; import androidx.media3.common.util.UnstableApi; @@ -25,6 +26,7 @@ public abstract class Buffer { private @C.BufferFlags int flags; /** Clears the buffer. */ + @CallSuper public void clear() { flags = 0; } diff --git a/libraries/decoder/src/main/java/androidx/media3/decoder/DecoderOutputBuffer.java b/libraries/decoder/src/main/java/androidx/media3/decoder/DecoderOutputBuffer.java index 1bca3cb817..b2038801aa 100644 --- a/libraries/decoder/src/main/java/androidx/media3/decoder/DecoderOutputBuffer.java +++ b/libraries/decoder/src/main/java/androidx/media3/decoder/DecoderOutputBuffer.java @@ -15,6 +15,7 @@ */ package androidx.media3.decoder; +import androidx.annotation.CallSuper; import androidx.media3.common.util.UnstableApi; /** Output buffer decoded by a {@link Decoder}. */ @@ -40,6 +41,21 @@ public abstract class DecoderOutputBuffer extends Buffer { */ public int skippedOutputBufferCount; + /** + * Whether this buffer should be skipped, usually because the decoding process generated no data + * or invalid data. + */ + public boolean shouldBeSkipped; + /** Releases the output buffer for reuse. Must be called when the buffer is no longer needed. */ public abstract void release(); + + @Override + @CallSuper + public void clear() { + super.clear(); + timeUs = 0; + skippedOutputBufferCount = 0; + shouldBeSkipped = false; + } } diff --git a/libraries/decoder/src/main/java/androidx/media3/decoder/SimpleDecoder.java b/libraries/decoder/src/main/java/androidx/media3/decoder/SimpleDecoder.java index 58d2943da0..d4084a904e 100644 --- a/libraries/decoder/src/main/java/androidx/media3/decoder/SimpleDecoder.java +++ b/libraries/decoder/src/main/java/androidx/media3/decoder/SimpleDecoder.java @@ -232,6 +232,7 @@ public abstract class SimpleDecoder< if (inputBuffer.isEndOfStream()) { outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); } else { + outputBuffer.timeUs = inputBuffer.timeUs; if (inputBuffer.isDecodeOnly()) { outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); } @@ -262,7 +263,7 @@ public abstract class SimpleDecoder< synchronized (lock) { if (flushed) { outputBuffer.release(); - } else if (outputBuffer.isDecodeOnly()) { + } else if (outputBuffer.isDecodeOnly() || outputBuffer.shouldBeSkipped) { skippedOutputBufferCount++; outputBuffer.release(); } else { diff --git a/libraries/decoder_av1/src/main/java/androidx/media3/decoder/av1/Gav1Decoder.java b/libraries/decoder_av1/src/main/java/androidx/media3/decoder/av1/Gav1Decoder.java index 2a93e0fb6f..49feb4e68c 100644 --- a/libraries/decoder_av1/src/main/java/androidx/media3/decoder/av1/Gav1Decoder.java +++ b/libraries/decoder_av1/src/main/java/androidx/media3/decoder/av1/Gav1Decoder.java @@ -117,7 +117,7 @@ public final class Gav1Decoder "gav1GetFrame error: " + gav1GetErrorMessage(gav1DecoderContext)); } if (getFrameResult == GAV1_DECODE_ONLY) { - outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + outputBuffer.shouldBeSkipped = true; } if (!decodeOnly) { outputBuffer.format = inputBuffer.format; @@ -139,9 +139,9 @@ public final class Gav1Decoder @Override protected void releaseOutputBuffer(VideoDecoderOutputBuffer outputBuffer) { - // Decode only frames do not acquire a reference on the internal decoder buffer and thus do not + // Skipped frames do not acquire a reference on the internal decoder buffer and thus do not // require a call to gav1ReleaseFrame. - if (outputBuffer.mode == C.VIDEO_OUTPUT_MODE_SURFACE_YUV && !outputBuffer.isDecodeOnly()) { + if (outputBuffer.mode == C.VIDEO_OUTPUT_MODE_SURFACE_YUV && !outputBuffer.shouldBeSkipped) { gav1ReleaseFrame(gav1DecoderContext, outputBuffer); } super.releaseOutputBuffer(outputBuffer); diff --git a/libraries/decoder_ffmpeg/src/main/java/androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder.java b/libraries/decoder_ffmpeg/src/main/java/androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder.java index 0e07287400..6fc34e7191 100644 --- a/libraries/decoder_ffmpeg/src/main/java/androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder.java +++ b/libraries/decoder_ffmpeg/src/main/java/androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder.java @@ -113,13 +113,13 @@ import java.util.List; return new FfmpegDecoderException("Error decoding (see logcat)."); } else if (result == AUDIO_DECODER_ERROR_INVALID_DATA) { // Treat invalid data errors as non-fatal to match the behavior of MediaCodec. No output will - // be produced for this buffer, so mark it as decode-only to ensure that the audio sink's + // be produced for this buffer, so mark it as skipped to ensure that the audio sink's // position is reset when more audio is produced. - outputBuffer.setFlags(C.BUFFER_FLAG_DECODE_ONLY); + outputBuffer.shouldBeSkipped = true; return null; } else if (result == 0) { // There's no need to output empty buffers. - outputBuffer.setFlags(C.BUFFER_FLAG_DECODE_ONLY); + outputBuffer.shouldBeSkipped = true; return null; } if (!hasOutputFormat) { diff --git a/libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/OpusDecoder.java b/libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/OpusDecoder.java index a381b49a4c..d5637ff201 100644 --- a/libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/OpusDecoder.java +++ b/libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/OpusDecoder.java @@ -229,7 +229,7 @@ public final class OpusDecoder int skipBytes = skipSamples * bytesPerSample; if (result <= skipBytes) { skipSamples -= result / bytesPerSample; - outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + outputBuffer.shouldBeSkipped = true; outputData.position(result); } else { skipSamples = 0; diff --git a/libraries/decoder_vp9/src/main/java/androidx/media3/decoder/vp9/VpxDecoder.java b/libraries/decoder_vp9/src/main/java/androidx/media3/decoder/vp9/VpxDecoder.java index 30bdaecb7f..db85283ec6 100644 --- a/libraries/decoder_vp9/src/main/java/androidx/media3/decoder/vp9/VpxDecoder.java +++ b/libraries/decoder_vp9/src/main/java/androidx/media3/decoder/vp9/VpxDecoder.java @@ -102,9 +102,9 @@ public final class VpxDecoder @Override protected void releaseOutputBuffer(VideoDecoderOutputBuffer outputBuffer) { - // Decode only frames do not acquire a reference on the internal decoder buffer and thus do not + // Skipped frames do not acquire a reference on the internal decoder buffer and thus do not // require a call to vpxReleaseFrame. - if (outputMode == C.VIDEO_OUTPUT_MODE_SURFACE_YUV && !outputBuffer.isDecodeOnly()) { + if (outputMode == C.VIDEO_OUTPUT_MODE_SURFACE_YUV && !outputBuffer.shouldBeSkipped) { vpxReleaseFrame(vpxDecContext, outputBuffer); } super.releaseOutputBuffer(outputBuffer); @@ -169,11 +169,13 @@ public final class VpxDecoder outputBuffer.init(inputBuffer.timeUs, outputMode, lastSupplementalData); int getFrameResult = vpxGetFrame(vpxDecContext, outputBuffer); if (getFrameResult == 1) { - outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + outputBuffer.shouldBeSkipped = true; } else if (getFrameResult == -1) { return new VpxDecoderException("Buffer initialization failed."); } outputBuffer.format = inputBuffer.format; + } else { + outputBuffer.shouldBeSkipped = true; } return null; }