Merge transformer video renderers.

This change merges `TransformerMuxingVideoRenderer` and
`TransformerTranscodingVideoRenderer` into `TransformerVideoRenderer`.
Besides all features supported by `TransformerTranscodingVideoRenderer`
the new merged `TransformerVideoRenderer` also supports SEF slow motion
flatting without re-encoding like the `TransformerMuxingVideoRenderer`.
To do this, it uses a `SefSlowMotionVideoSampleTransformer` with
the `PassthroughPipeline`.

PiperOrigin-RevId: 410531478
This commit is contained in:
hschlueter 2021-11-17 16:11:59 +00:00 committed by Ian Baker
parent 46e3337bbb
commit 35c891a7bf
5 changed files with 40 additions and 147 deletions

View File

@ -736,8 +736,7 @@ public final class TranscodingTransformer {
} }
if (!transformation.removeVideo) { if (!transformation.removeVideo) {
renderers[index] = renderers[index] =
new TransformerTranscodingVideoRenderer( new TransformerVideoRenderer(context, muxerWrapper, mediaClock, transformation);
context, muxerWrapper, mediaClock, transformation);
index++; index++;
} }
return renderers; return renderers;

View File

@ -509,7 +509,7 @@ public final class Transformer {
.build(); .build();
player = player =
new ExoPlayer.Builder( new ExoPlayer.Builder(
context, new TransformerRenderersFactory(muxerWrapper, transformation)) context, new TransformerRenderersFactory(context, muxerWrapper, transformation))
.setMediaSourceFactory(mediaSourceFactory) .setMediaSourceFactory(mediaSourceFactory)
.setTrackSelector(trackSelector) .setTrackSelector(trackSelector)
.setLoadControl(loadControl) .setLoadControl(loadControl)
@ -594,11 +594,14 @@ public final class Transformer {
private static final class TransformerRenderersFactory implements RenderersFactory { private static final class TransformerRenderersFactory implements RenderersFactory {
private final Context context;
private final MuxerWrapper muxerWrapper; private final MuxerWrapper muxerWrapper;
private final TransformerMediaClock mediaClock; private final TransformerMediaClock mediaClock;
private final Transformation transformation; private final Transformation transformation;
public TransformerRenderersFactory(MuxerWrapper muxerWrapper, Transformation transformation) { public TransformerRenderersFactory(
Context context, MuxerWrapper muxerWrapper, Transformation transformation) {
this.context = context;
this.muxerWrapper = muxerWrapper; this.muxerWrapper = muxerWrapper;
this.transformation = transformation; this.transformation = transformation;
mediaClock = new TransformerMediaClock(); mediaClock = new TransformerMediaClock();
@ -620,7 +623,7 @@ public final class Transformer {
} }
if (!transformation.removeVideo) { if (!transformation.removeVideo) {
renderers[index] = renderers[index] =
new TransformerMuxingVideoRenderer(muxerWrapper, mediaClock, transformation); new TransformerVideoRenderer(context, muxerWrapper, mediaClock, transformation);
index++; index++;
} }
return renderers; return renderers;

View File

@ -101,8 +101,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
/** /**
* Attempts to write sample pipeline output data to the muxer, and returns whether it may be * Attempts to write sample pipeline output data to the muxer.
* possible to write more data immediately by calling this method again. *
* @return Whether it may be possible to write more data immediately by calling this method again.
*/ */
@RequiresNonNull("samplePipeline") @RequiresNonNull("samplePipeline")
private boolean feedMuxerFromPipeline() { private boolean feedMuxerFromPipeline() {
@ -136,8 +137,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
/** /**
* Attempts to pass input data to the sample pipeline, and returns whether it may be possible to * Attempts to pass input data to the sample pipeline.
* pass more data immediately by calling this method again. *
* @return Whether it may be possible to pass more data immediately by calling this method again.
*/ */
@RequiresNonNull("samplePipeline") @RequiresNonNull("samplePipeline")
private boolean feedPipelineFromInput() { private boolean feedPipelineFromInput() {

View File

@ -1,129 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkNotNull;
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;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.exoplayer.FormatHolder;
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
import java.nio.ByteBuffer;
/** A video renderer that doesn't re-encoder the samples. */
@RequiresApi(18)
/* package */ final class TransformerMuxingVideoRenderer extends TransformerBaseRenderer {
private static final String TAG = "TransformerVideoRenderer";
private final DecoderInputBuffer buffer;
@Nullable private SampleTransformer sampleTransformer;
private boolean formatRead;
private boolean isBufferPending;
private boolean isInputStreamEnded;
public TransformerMuxingVideoRenderer(
MuxerWrapper muxerWrapper, TransformerMediaClock mediaClock, Transformation transformation) {
super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformation);
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
}
@Override
public String getName() {
return TAG;
}
@Override
public void render(long positionUs, long elapsedRealtimeUs) {
if (!isRendererStarted || isEnded()) {
return;
}
if (!formatRead) {
FormatHolder formatHolder = getFormatHolder();
@ReadDataResult int result = readSource(formatHolder, buffer, FLAG_REQUIRE_FORMAT);
if (result != C.RESULT_FORMAT_READ) {
return;
}
Format format = checkNotNull(formatHolder.format);
formatRead = true;
if (transformation.flattenForSlowMotion) {
sampleTransformer = new SefSlowMotionVideoSampleTransformer(format);
}
muxerWrapper.addTrackFormat(format);
}
while (true) {
// Read sample.
if (!isBufferPending && !readAndTransformBuffer()) {
return;
}
// Write sample.
isBufferPending =
!muxerWrapper.writeSample(
getTrackType(), buffer.data, buffer.isKeyFrame(), buffer.timeUs);
if (isBufferPending) {
return;
}
}
}
@Override
public boolean isEnded() {
return isInputStreamEnded;
}
/**
* Checks whether a sample can be read and, if so, reads it, transforms it and writes the
* resulting sample to the {@link #buffer}.
*
* <p>The buffer data can be set to null if the transformation applied discards the sample.
*
* @return Whether a sample has been read and transformed.
*/
private boolean readAndTransformBuffer() {
buffer.clear();
@ReadDataResult int result = readSource(getFormatHolder(), buffer, /* readFlags= */ 0);
if (result == C.RESULT_FORMAT_READ) {
throw new IllegalStateException("Format changes are not supported.");
} else if (result == C.RESULT_NOTHING_READ) {
return false;
}
// Buffer read.
if (buffer.isEndOfStream()) {
isInputStreamEnded = true;
muxerWrapper.endTrack(getTrackType());
return false;
}
mediaClock.updateTimeForTrackType(getTrackType(), buffer.timeUs);
buffer.timeUs -= streamOffsetUs;
ByteBuffer data = checkNotNull(buffer.data);
data.flip();
if (sampleTransformer != null) {
sampleTransformer.transformSample(buffer);
}
return true;
}
}

View File

@ -33,18 +33,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@RequiresApi(18) @RequiresApi(18)
/* package */ final class TransformerTranscodingVideoRenderer extends TransformerBaseRenderer { /* package */ final class TransformerVideoRenderer extends TransformerBaseRenderer {
private static final String TAG = "TransformerTranscodingVideoRenderer"; private static final String TAG = "TransformerTranscodingVideoRenderer";
private final Context context; private final Context context;
private final DecoderInputBuffer decoderInputBuffer; private final DecoderInputBuffer decoderInputBuffer;
private @MonotonicNonNull SampleTransformer slowMotionSampleTransformer;
private @MonotonicNonNull SamplePipeline samplePipeline; private @MonotonicNonNull SamplePipeline samplePipeline;
private boolean muxerWrapperTrackAdded; private boolean muxerWrapperTrackAdded;
private boolean muxerWrapperTrackEnded; private boolean muxerWrapperTrackEnded;
public TransformerTranscodingVideoRenderer( public TransformerVideoRenderer(
Context context, Context context,
MuxerWrapper muxerWrapper, MuxerWrapper muxerWrapper,
TransformerMediaClock mediaClock, TransformerMediaClock mediaClock,
@ -105,12 +106,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} else { } else {
samplePipeline = new PassthroughSamplePipeline(decoderInputFormat); samplePipeline = new PassthroughSamplePipeline(decoderInputFormat);
} }
if (transformation.flattenForSlowMotion) {
slowMotionSampleTransformer = new SefSlowMotionVideoSampleTransformer(decoderInputFormat);
}
return true; return true;
} }
/** /**
* Attempts to write sample pipeline output data to the muxer, and returns whether it may be * Attempts to write sample pipeline output data to the muxer.
* possible to write more data immediately by calling this method again. *
* @return Whether it may be possible to write more data immediately by calling this method again.
*/ */
@RequiresNonNull("samplePipeline") @RequiresNonNull("samplePipeline")
private boolean feedMuxerFromPipeline() { private boolean feedMuxerFromPipeline() {
@ -146,8 +151,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
/** /**
* Attempts to pass input data to the sample pipeline, and returns whether it may be possible to * Attempts to:
* pass more data immediately by calling this method again. *
* <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") @RequiresNonNull("samplePipeline")
private boolean feedPipelineFromInput() { private boolean feedPipelineFromInput() {
@ -160,9 +172,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
int result = readSource(getFormatHolder(), samplePipelineInputBuffer, /* readFlags= */ 0); int result = readSource(getFormatHolder(), samplePipelineInputBuffer, /* readFlags= */ 0);
switch (result) { switch (result) {
case C.RESULT_BUFFER_READ: case C.RESULT_BUFFER_READ:
if (samplePipelineInputBuffer.data != null
&& samplePipelineInputBuffer.data.position() > 0) {
mediaClock.updateTimeForTrackType(getTrackType(), samplePipelineInputBuffer.timeUs); mediaClock.updateTimeForTrackType(getTrackType(), samplePipelineInputBuffer.timeUs);
samplePipelineInputBuffer.timeUs -= streamOffsetUs; samplePipelineInputBuffer.timeUs -= streamOffsetUs;
samplePipelineInputBuffer.flip(); samplePipelineInputBuffer.flip();
if (slowMotionSampleTransformer != null) {
slowMotionSampleTransformer.transformSample(samplePipelineInputBuffer);
}
}
samplePipeline.queueInputBuffer(); samplePipeline.queueInputBuffer();
return !samplePipelineInputBuffer.isEndOfStream(); return !samplePipelineInputBuffer.isEndOfStream();
case C.RESULT_FORMAT_READ: case C.RESULT_FORMAT_READ: