mirror of
https://github.com/androidx/media.git
synced 2025-05-09 16:40:55 +08:00
Deduplicate transformer audio and video renderer implementations.
This change moves methods that are the same in `TransformerAudioRenderer` and `TransformerVideoRenderer` to `TransformerBaseRenderer`. PiperOrigin-RevId: 411758928
This commit is contained in:
parent
736ea9a148
commit
551a47e1fc
@ -150,6 +150,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
encoderOutputBuffer.data = encoder.getOutputBuffer();
|
||||
if (encoderOutputBuffer.data != null) {
|
||||
encoderOutputBuffer.timeUs = checkNotNull(encoder.getOutputBufferInfo()).presentationTimeUs;
|
||||
encoderOutputBuffer.setFlags(C.BUFFER_FLAG_KEY_FRAME);
|
||||
return encoderOutputBuffer;
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,8 @@
|
||||
package androidx.media3.transformer;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
@ -28,9 +26,6 @@ import androidx.media3.decoder.DecoderInputBuffer;
|
||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
import androidx.media3.exoplayer.FormatHolder;
|
||||
import androidx.media3.exoplayer.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)
|
||||
/* package */ final class TransformerAudioRenderer extends TransformerBaseRenderer {
|
||||
@ -39,10 +34,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
private final DecoderInputBuffer decoderInputBuffer;
|
||||
|
||||
private @MonotonicNonNull SamplePipeline samplePipeline;
|
||||
private boolean muxerWrapperTrackAdded;
|
||||
private boolean muxerWrapperTrackEnded;
|
||||
|
||||
public TransformerAudioRenderer(
|
||||
MuxerWrapper muxerWrapper, TransformerMediaClock mediaClock, Transformation transformation) {
|
||||
super(C.TRACK_TYPE_AUDIO, muxerWrapper, mediaClock, transformation);
|
||||
@ -55,32 +46,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
return TAG;
|
||||
}
|
||||
|
||||
/** Attempts to read the input format and to initialize the sample or passthrough pipeline. */
|
||||
@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 pipeline. */
|
||||
@EnsuresNonNullIf(expression = "samplePipeline", result = true)
|
||||
private boolean ensureRendererConfigured() throws ExoPlaybackException {
|
||||
protected boolean ensureConfigured() throws ExoPlaybackException {
|
||||
if (samplePipeline != null) {
|
||||
return true;
|
||||
}
|
||||
@ -100,73 +68,4 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
}
|
||||
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,15 +16,23 @@
|
||||
|
||||
package androidx.media3.transformer;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.decoder.DecoderInputBuffer;
|
||||
import androidx.media3.exoplayer.BaseRenderer;
|
||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
import androidx.media3.exoplayer.MediaClock;
|
||||
import androidx.media3.exoplayer.RendererCapabilities;
|
||||
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
|
||||
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)
|
||||
/* package */ abstract class TransformerBaseRenderer extends BaseRenderer {
|
||||
@ -34,7 +42,10 @@ import androidx.media3.exoplayer.RendererCapabilities;
|
||||
protected final Transformation transformation;
|
||||
|
||||
protected boolean isRendererStarted;
|
||||
protected boolean muxerWrapperTrackAdded;
|
||||
protected boolean muxerWrapperTrackEnded;
|
||||
protected long streamOffsetUs;
|
||||
protected @MonotonicNonNull SamplePipeline samplePipeline;
|
||||
|
||||
public TransformerBaseRenderer(
|
||||
int trackType,
|
||||
@ -47,11 +58,6 @@ import androidx.media3.exoplayer.RendererCapabilities;
|
||||
this.transformation = transformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
|
||||
this.streamOffsetUs = offsetUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
@C.FormatSupport
|
||||
public final int supportsFormat(Format format) {
|
||||
@ -84,6 +90,34 @@ import androidx.media3.exoplayer.RendererCapabilities;
|
||||
return mediaClock;
|
||||
}
|
||||
|
||||
@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) {
|
||||
muxerWrapper.registerTrack();
|
||||
@ -91,7 +125,7 @@ import androidx.media3.exoplayer.RendererCapabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStarted() throws ExoPlaybackException {
|
||||
protected final void onStarted() {
|
||||
isRendererStarted = true;
|
||||
}
|
||||
|
||||
@ -99,4 +133,85 @@ import androidx.media3.exoplayer.RendererCapabilities;
|
||||
protected final void onStopped() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,11 +17,9 @@
|
||||
package androidx.media3.transformer;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
@ -30,7 +28,6 @@ import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
import androidx.media3.exoplayer.FormatHolder;
|
||||
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
@ -43,9 +40,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
private final DecoderInputBuffer decoderInputBuffer;
|
||||
|
||||
private @MonotonicNonNull SefSlowMotionFlattener sefSlowMotionFlattener;
|
||||
private @MonotonicNonNull SamplePipeline samplePipeline;
|
||||
private boolean muxerWrapperTrackAdded;
|
||||
private boolean muxerWrapperTrackEnded;
|
||||
|
||||
public TransformerVideoRenderer(
|
||||
Context context,
|
||||
@ -63,32 +57,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
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. */
|
||||
@EnsuresNonNullIf(expression = "samplePipeline", result = true)
|
||||
private boolean ensureRendererConfigured() throws ExoPlaybackException {
|
||||
@Override
|
||||
protected boolean ensureConfigured() throws ExoPlaybackException {
|
||||
if (samplePipeline != null) {
|
||||
return true;
|
||||
}
|
||||
@ -115,88 +86,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Queues the input buffer to the sample pipeline unless it should be dropped because of slow
|
||||
* motion flattening.
|
||||
*/
|
||||
@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")
|
||||
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();
|
||||
if (sefSlowMotionFlattener != null) {
|
||||
ByteBuffer data = checkStateNotNull(samplePipelineInputBuffer.data);
|
||||
@Override
|
||||
@RequiresNonNull({"samplePipeline", "#1.data"})
|
||||
protected void maybeQueueSampleToPipeline(DecoderInputBuffer inputBuffer) {
|
||||
ByteBuffer data = inputBuffer.data;
|
||||
boolean shouldDropSample =
|
||||
sefSlowMotionFlattener.dropOrTransformSample(samplePipelineInputBuffer);
|
||||
sefSlowMotionFlattener != null && sefSlowMotionFlattener.dropOrTransformSample(inputBuffer);
|
||||
if (shouldDropSample) {
|
||||
data.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user