Explicitly mark DecoderOutputBuffer as shouldBeSkipped if needed

In some cases, SimpleDecoder output needs to be skipped for rendering
because the decoder produces no data. This is one of the remaining
usages of BUFFER_FLAG_DECODE_ONLY at the moment and can be more
directly solved without using the flag. SimpleDecoder still needs to
check the flag though for backwards compatbility with custom decoders
while the flag is not completely removed.

PiperOrigin-RevId: 570345233
This commit is contained in:
tonihei 2023-10-03 04:25:47 -07:00 committed by Copybara-Service
parent 1bb501ab50
commit c8aac24ffd
8 changed files with 35 additions and 11 deletions

View File

@ -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:

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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) {

View File

@ -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;

View File

@ -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;
}