Commit 8af7089c by hschlueter Committed by kim-vde

Deduplicate transformer audio and video renderer implementations.

This change moves methods that are the same in
`TransformerAudioRenderer` and `TransformerVideoRenderer` to
`TransformerBaseRenderer`.

PiperOrigin-RevId: 411758928
parent 92507e02
...@@ -150,6 +150,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -150,6 +150,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
encoderOutputBuffer.data = encoder.getOutputBuffer(); encoderOutputBuffer.data = encoder.getOutputBuffer();
if (encoderOutputBuffer.data != null) { if (encoderOutputBuffer.data != null) {
encoderOutputBuffer.timeUs = checkNotNull(encoder.getOutputBufferInfo()).presentationTimeUs; encoderOutputBuffer.timeUs = checkNotNull(encoder.getOutputBufferInfo()).presentationTimeUs;
encoderOutputBuffer.setFlags(C.BUFFER_FLAG_KEY_FRAME);
return encoderOutputBuffer; return encoderOutputBuffer;
} }
} }
......
...@@ -18,9 +18,7 @@ package com.google.android.exoplayer2.transformer; ...@@ -18,9 +18,7 @@ package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT; import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -28,9 +26,6 @@ import com.google.android.exoplayer2.Format; ...@@ -28,9 +26,6 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@RequiresApi(18) @RequiresApi(18)
/* package */ final class TransformerAudioRenderer extends TransformerBaseRenderer { /* package */ final class TransformerAudioRenderer extends TransformerBaseRenderer {
...@@ -39,10 +34,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -39,10 +34,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final DecoderInputBuffer decoderInputBuffer; private final DecoderInputBuffer decoderInputBuffer;
private @MonotonicNonNull SamplePipeline samplePipeline;
private boolean muxerWrapperTrackAdded;
private boolean muxerWrapperTrackEnded;
public TransformerAudioRenderer( public TransformerAudioRenderer(
MuxerWrapper muxerWrapper, TransformerMediaClock mediaClock, Transformation transformation) { MuxerWrapper muxerWrapper, TransformerMediaClock mediaClock, Transformation transformation) {
super(C.TRACK_TYPE_AUDIO, muxerWrapper, mediaClock, transformation); super(C.TRACK_TYPE_AUDIO, muxerWrapper, mediaClock, transformation);
...@@ -55,32 +46,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -55,32 +46,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return TAG; return TAG;
} }
/** Attempts to read the input format and to initialize the sample or passthrough pipeline. */
@Override @Override
public boolean isEnded() { protected boolean ensureConfigured() throws ExoPlaybackException {
return muxerWrapperTrackEnded;
}
@Override
protected void onReset() {
if (samplePipeline != null) {
samplePipeline.release();
}
muxerWrapperTrackAdded = false;
muxerWrapperTrackEnded = false;
}
@Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (!isRendererStarted || isEnded() || !ensureRendererConfigured()) {
return;
}
while (feedMuxerFromPipeline() || samplePipeline.processData() || feedPipelineFromInput()) {}
}
/** Attempts to read the input format and to initialize the sample pipeline. */
@EnsuresNonNullIf(expression = "samplePipeline", result = true)
private boolean ensureRendererConfigured() throws ExoPlaybackException {
if (samplePipeline != null) { if (samplePipeline != null) {
return true; return true;
} }
...@@ -100,73 +68,4 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -100,73 +68,4 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
return true; return true;
} }
/**
* Attempts to write sample pipeline output data to the muxer.
*
* @return Whether it may be possible to write more data immediately by calling this method again.
*/
@RequiresNonNull("samplePipeline")
private boolean feedMuxerFromPipeline() {
if (!muxerWrapperTrackAdded) {
@Nullable Format samplePipelineOutputFormat = samplePipeline.getOutputFormat();
if (samplePipelineOutputFormat == null) {
return false;
}
muxerWrapperTrackAdded = true;
muxerWrapper.addTrackFormat(samplePipelineOutputFormat);
}
if (samplePipeline.isEnded()) {
muxerWrapper.endTrack(getTrackType());
muxerWrapperTrackEnded = true;
return false;
}
@Nullable DecoderInputBuffer samplePipelineOutputBuffer = samplePipeline.getOutputBuffer();
if (samplePipelineOutputBuffer == null) {
return false;
}
if (!muxerWrapper.writeSample(
getTrackType(),
checkStateNotNull(samplePipelineOutputBuffer.data),
/* isKeyFrame= */ true,
samplePipelineOutputBuffer.timeUs)) {
return false;
}
samplePipeline.releaseOutputBuffer();
return true;
}
/**
* Attempts to pass input data to the sample pipeline.
*
* @return Whether it may be possible to pass more data immediately by calling this method again.
*/
@RequiresNonNull("samplePipeline")
private boolean feedPipelineFromInput() {
@Nullable DecoderInputBuffer samplePipelineInputBuffer = samplePipeline.dequeueInputBuffer();
if (samplePipelineInputBuffer == null) {
return false;
}
@ReadDataResult
int result = readSource(getFormatHolder(), samplePipelineInputBuffer, /* readFlags= */ 0);
switch (result) {
case C.RESULT_BUFFER_READ:
if (samplePipelineInputBuffer.isEndOfStream()) {
samplePipeline.queueInputBuffer();
return false;
}
mediaClock.updateTimeForTrackType(getTrackType(), samplePipelineInputBuffer.timeUs);
samplePipelineInputBuffer.timeUs -= streamOffsetUs;
samplePipelineInputBuffer.flip();
samplePipeline.queueInputBuffer();
return true;
case C.RESULT_FORMAT_READ:
throw new IllegalStateException("Format changes are not supported.");
case C.RESULT_NOTHING_READ:
default:
return false;
}
}
} }
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package com.google.android.exoplayer2.transformer; package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.BaseRenderer;
...@@ -23,8 +25,14 @@ import com.google.android.exoplayer2.C; ...@@ -23,8 +25,14 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; 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.RendererCapabilities;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.errorprone.annotations.ForOverride;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@RequiresApi(18) @RequiresApi(18)
/* package */ abstract class TransformerBaseRenderer extends BaseRenderer { /* package */ abstract class TransformerBaseRenderer extends BaseRenderer {
...@@ -34,7 +42,10 @@ import com.google.android.exoplayer2.util.MimeTypes; ...@@ -34,7 +42,10 @@ import com.google.android.exoplayer2.util.MimeTypes;
protected final Transformation transformation; protected final Transformation transformation;
protected boolean isRendererStarted; protected boolean isRendererStarted;
protected boolean muxerWrapperTrackAdded;
protected boolean muxerWrapperTrackEnded;
protected long streamOffsetUs; protected long streamOffsetUs;
protected @MonotonicNonNull SamplePipeline samplePipeline;
public TransformerBaseRenderer( public TransformerBaseRenderer(
int trackType, int trackType,
...@@ -48,11 +59,6 @@ import com.google.android.exoplayer2.util.MimeTypes; ...@@ -48,11 +59,6 @@ import com.google.android.exoplayer2.util.MimeTypes;
} }
@Override @Override
protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
this.streamOffsetUs = offsetUs;
}
@Override
@C.FormatSupport @C.FormatSupport
public final int supportsFormat(Format format) { public final int supportsFormat(Format format) {
@Nullable String sampleMimeType = format.sampleMimeType; @Nullable String sampleMimeType = format.sampleMimeType;
...@@ -85,13 +91,41 @@ import com.google.android.exoplayer2.util.MimeTypes; ...@@ -85,13 +91,41 @@ import com.google.android.exoplayer2.util.MimeTypes;
} }
@Override @Override
public final boolean isEnded() {
return muxerWrapperTrackEnded;
}
@Override
public final void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (!isRendererStarted || isEnded() || !ensureConfigured()) {
return;
}
while (feedMuxerFromPipeline() || samplePipeline.processData() || feedPipelineFromInput()) {}
}
@Override
protected final void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
this.streamOffsetUs = offsetUs;
}
@Override
protected final void onReset() {
if (samplePipeline != null) {
samplePipeline.release();
}
muxerWrapperTrackAdded = false;
muxerWrapperTrackEnded = false;
}
@Override
protected final void onEnabled(boolean joining, boolean mayRenderStartOfStream) { protected final void onEnabled(boolean joining, boolean mayRenderStartOfStream) {
muxerWrapper.registerTrack(); muxerWrapper.registerTrack();
mediaClock.updateTimeForTrackType(getTrackType(), 0L); mediaClock.updateTimeForTrackType(getTrackType(), 0L);
} }
@Override @Override
protected void onStarted() throws ExoPlaybackException { protected final void onStarted() {
isRendererStarted = true; isRendererStarted = true;
} }
...@@ -99,4 +133,85 @@ import com.google.android.exoplayer2.util.MimeTypes; ...@@ -99,4 +133,85 @@ import com.google.android.exoplayer2.util.MimeTypes;
protected final void onStopped() { protected final void onStopped() {
isRendererStarted = false; isRendererStarted = false;
} }
@ForOverride
@EnsuresNonNullIf(expression = "samplePipeline", result = true)
protected abstract boolean ensureConfigured() throws ExoPlaybackException;
@RequiresNonNull({"samplePipeline", "#1.data"})
protected void maybeQueueSampleToPipeline(DecoderInputBuffer inputBuffer) {
samplePipeline.queueInputBuffer();
}
/**
* Attempts to write sample pipeline output data to the muxer.
*
* @return Whether it may be possible to write more data immediately by calling this method again.
*/
@RequiresNonNull("samplePipeline")
private boolean feedMuxerFromPipeline() {
if (!muxerWrapperTrackAdded) {
@Nullable Format samplePipelineOutputFormat = samplePipeline.getOutputFormat();
if (samplePipelineOutputFormat == null) {
return false;
}
muxerWrapperTrackAdded = true;
muxerWrapper.addTrackFormat(samplePipelineOutputFormat);
}
if (samplePipeline.isEnded()) {
muxerWrapper.endTrack(getTrackType());
muxerWrapperTrackEnded = true;
return false;
}
@Nullable DecoderInputBuffer samplePipelineOutputBuffer = samplePipeline.getOutputBuffer();
if (samplePipelineOutputBuffer == null) {
return false;
}
if (!muxerWrapper.writeSample(
getTrackType(),
checkStateNotNull(samplePipelineOutputBuffer.data),
samplePipelineOutputBuffer.isKeyFrame(),
samplePipelineOutputBuffer.timeUs)) {
return false;
}
samplePipeline.releaseOutputBuffer();
return true;
}
/**
* Attempts to read input data and pass the input data to the sample pipeline.
*
* @return Whether it may be possible to read more data immediately by calling this method again.
*/
@RequiresNonNull("samplePipeline")
private boolean feedPipelineFromInput() {
@Nullable DecoderInputBuffer samplePipelineInputBuffer = samplePipeline.dequeueInputBuffer();
if (samplePipelineInputBuffer == null) {
return false;
}
@ReadDataResult
int result = readSource(getFormatHolder(), samplePipelineInputBuffer, /* readFlags= */ 0);
switch (result) {
case C.RESULT_BUFFER_READ:
if (samplePipelineInputBuffer.isEndOfStream()) {
samplePipeline.queueInputBuffer();
return false;
}
mediaClock.updateTimeForTrackType(getTrackType(), samplePipelineInputBuffer.timeUs);
samplePipelineInputBuffer.timeUs -= streamOffsetUs;
samplePipelineInputBuffer.flip();
checkStateNotNull(samplePipelineInputBuffer.data);
maybeQueueSampleToPipeline(samplePipelineInputBuffer);
return true;
case C.RESULT_FORMAT_READ:
throw new IllegalStateException("Format changes are not supported.");
case C.RESULT_NOTHING_READ:
default:
return false;
}
}
} }
...@@ -18,10 +18,8 @@ package com.google.android.exoplayer2.transformer; ...@@ -18,10 +18,8 @@ package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT; import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.content.Context; import android.content.Context;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -30,7 +28,6 @@ import com.google.android.exoplayer2.FormatHolder; ...@@ -30,7 +28,6 @@ import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...@@ -43,9 +40,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -43,9 +40,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final DecoderInputBuffer decoderInputBuffer; private final DecoderInputBuffer decoderInputBuffer;
private @MonotonicNonNull SefSlowMotionFlattener sefSlowMotionFlattener; private @MonotonicNonNull SefSlowMotionFlattener sefSlowMotionFlattener;
private @MonotonicNonNull SamplePipeline samplePipeline;
private boolean muxerWrapperTrackAdded;
private boolean muxerWrapperTrackEnded;
public TransformerVideoRenderer( public TransformerVideoRenderer(
Context context, Context context,
...@@ -63,32 +57,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -63,32 +57,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return TAG; return TAG;
} }
@Override
public boolean isEnded() {
return muxerWrapperTrackEnded;
}
@Override
protected void onReset() {
if (samplePipeline != null) {
samplePipeline.release();
}
muxerWrapperTrackAdded = false;
muxerWrapperTrackEnded = false;
}
@Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (!isRendererStarted || isEnded() || !ensureRendererConfigured()) {
return;
}
while (feedMuxerFromPipeline() || samplePipeline.processData() || feedPipelineFromInput()) {}
}
/** Attempts to read the input format and to initialize the sample or passthrough pipeline. */ /** Attempts to read the input format and to initialize the sample or passthrough pipeline. */
@EnsuresNonNullIf(expression = "samplePipeline", result = true) @Override
private boolean ensureRendererConfigured() throws ExoPlaybackException { protected boolean ensureConfigured() throws ExoPlaybackException {
if (samplePipeline != null) { if (samplePipeline != null) {
return true; return true;
} }
...@@ -115,88 +86,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -115,88 +86,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
/** /**
* Attempts to write sample pipeline output data to the muxer. * Queues the input buffer to the sample pipeline unless it should be dropped because of slow
* * motion flattening.
* @return Whether it may be possible to write more data immediately by calling this method again.
*/
@RequiresNonNull("samplePipeline")
private boolean feedMuxerFromPipeline() {
if (!muxerWrapperTrackAdded) {
@Nullable Format samplePipelineOutputFormat = samplePipeline.getOutputFormat();
if (samplePipelineOutputFormat == null) {
return false;
}
muxerWrapperTrackAdded = true;
muxerWrapper.addTrackFormat(samplePipelineOutputFormat);
}
if (samplePipeline.isEnded()) {
muxerWrapper.endTrack(getTrackType());
muxerWrapperTrackEnded = true;
return false;
}
@Nullable DecoderInputBuffer samplePipelineOutputBuffer = samplePipeline.getOutputBuffer();
if (samplePipelineOutputBuffer == null) {
return false;
}
if (!muxerWrapper.writeSample(
getTrackType(),
checkStateNotNull(samplePipelineOutputBuffer.data),
samplePipelineOutputBuffer.isKeyFrame(),
samplePipelineOutputBuffer.timeUs)) {
return false;
}
samplePipeline.releaseOutputBuffer();
return true;
}
/**
* Attempts to:
*
* <ol>
* <li>read input data,
* <li>optionally, apply slow motion flattening, and
* <li>pass input data to the sample pipeline.
* </ol>
*
* @return Whether it may be possible to read more data immediately by calling this method again.
*/ */
@RequiresNonNull("samplePipeline") @Override
private boolean feedPipelineFromInput() { @RequiresNonNull({"samplePipeline", "#1.data"})
@Nullable DecoderInputBuffer samplePipelineInputBuffer = samplePipeline.dequeueInputBuffer(); protected void maybeQueueSampleToPipeline(DecoderInputBuffer inputBuffer) {
if (samplePipelineInputBuffer == null) { ByteBuffer data = inputBuffer.data;
return false;
}
@ReadDataResult
int result = readSource(getFormatHolder(), samplePipelineInputBuffer, /* readFlags= */ 0);
switch (result) {
case C.RESULT_BUFFER_READ:
if (samplePipelineInputBuffer.isEndOfStream()) {
samplePipeline.queueInputBuffer();
return false;
}
mediaClock.updateTimeForTrackType(getTrackType(), samplePipelineInputBuffer.timeUs);
samplePipelineInputBuffer.timeUs -= streamOffsetUs;
samplePipelineInputBuffer.flip();
if (sefSlowMotionFlattener != null) {
ByteBuffer data = checkStateNotNull(samplePipelineInputBuffer.data);
boolean shouldDropSample = boolean shouldDropSample =
sefSlowMotionFlattener.dropOrTransformSample(samplePipelineInputBuffer); sefSlowMotionFlattener != null && sefSlowMotionFlattener.dropOrTransformSample(inputBuffer);
if (shouldDropSample) { if (shouldDropSample) {
data.clear(); data.clear();
return true; } else {
}
}
samplePipeline.queueInputBuffer(); samplePipeline.queueInputBuffer();
return true;
case C.RESULT_FORMAT_READ:
throw new IllegalStateException("Format changes are not supported.");
case C.RESULT_NOTHING_READ:
default:
return false;
} }
} }
} }
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