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:
parent
46e3337bbb
commit
35c891a7bf
@ -736,8 +736,7 @@ public final class TranscodingTransformer {
|
||||
}
|
||||
if (!transformation.removeVideo) {
|
||||
renderers[index] =
|
||||
new TransformerTranscodingVideoRenderer(
|
||||
context, muxerWrapper, mediaClock, transformation);
|
||||
new TransformerVideoRenderer(context, muxerWrapper, mediaClock, transformation);
|
||||
index++;
|
||||
}
|
||||
return renderers;
|
||||
|
@ -509,7 +509,7 @@ public final class Transformer {
|
||||
.build();
|
||||
player =
|
||||
new ExoPlayer.Builder(
|
||||
context, new TransformerRenderersFactory(muxerWrapper, transformation))
|
||||
context, new TransformerRenderersFactory(context, muxerWrapper, transformation))
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.setTrackSelector(trackSelector)
|
||||
.setLoadControl(loadControl)
|
||||
@ -594,11 +594,14 @@ public final class Transformer {
|
||||
|
||||
private static final class TransformerRenderersFactory implements RenderersFactory {
|
||||
|
||||
private final Context context;
|
||||
private final MuxerWrapper muxerWrapper;
|
||||
private final TransformerMediaClock mediaClock;
|
||||
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.transformation = transformation;
|
||||
mediaClock = new TransformerMediaClock();
|
||||
@ -620,7 +623,7 @@ public final class Transformer {
|
||||
}
|
||||
if (!transformation.removeVideo) {
|
||||
renderers[index] =
|
||||
new TransformerMuxingVideoRenderer(muxerWrapper, mediaClock, transformation);
|
||||
new TransformerVideoRenderer(context, muxerWrapper, mediaClock, transformation);
|
||||
index++;
|
||||
}
|
||||
return renderers;
|
||||
|
@ -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
|
||||
* possible to write more data immediately by calling this method again.
|
||||
* 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() {
|
||||
@ -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
|
||||
* pass more data immediately by calling this method again.
|
||||
* 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() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -33,18 +33,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
@RequiresApi(18)
|
||||
/* package */ final class TransformerTranscodingVideoRenderer extends TransformerBaseRenderer {
|
||||
/* package */ final class TransformerVideoRenderer extends TransformerBaseRenderer {
|
||||
|
||||
private static final String TAG = "TransformerTranscodingVideoRenderer";
|
||||
|
||||
private final Context context;
|
||||
private final DecoderInputBuffer decoderInputBuffer;
|
||||
|
||||
private @MonotonicNonNull SampleTransformer slowMotionSampleTransformer;
|
||||
private @MonotonicNonNull SamplePipeline samplePipeline;
|
||||
private boolean muxerWrapperTrackAdded;
|
||||
private boolean muxerWrapperTrackEnded;
|
||||
|
||||
public TransformerTranscodingVideoRenderer(
|
||||
public TransformerVideoRenderer(
|
||||
Context context,
|
||||
MuxerWrapper muxerWrapper,
|
||||
TransformerMediaClock mediaClock,
|
||||
@ -105,12 +106,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
} else {
|
||||
samplePipeline = new PassthroughSamplePipeline(decoderInputFormat);
|
||||
}
|
||||
if (transformation.flattenForSlowMotion) {
|
||||
slowMotionSampleTransformer = new SefSlowMotionVideoSampleTransformer(decoderInputFormat);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to write sample pipeline output data to the muxer, and returns whether it may be
|
||||
* possible to write more data immediately by calling this method again.
|
||||
* 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() {
|
||||
@ -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
|
||||
* pass more data immediately by calling this method again.
|
||||
* 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() {
|
||||
@ -160,9 +172,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
int result = readSource(getFormatHolder(), samplePipelineInputBuffer, /* readFlags= */ 0);
|
||||
switch (result) {
|
||||
case C.RESULT_BUFFER_READ:
|
||||
mediaClock.updateTimeForTrackType(getTrackType(), samplePipelineInputBuffer.timeUs);
|
||||
samplePipelineInputBuffer.timeUs -= streamOffsetUs;
|
||||
samplePipelineInputBuffer.flip();
|
||||
if (samplePipelineInputBuffer.data != null
|
||||
&& samplePipelineInputBuffer.data.position() > 0) {
|
||||
mediaClock.updateTimeForTrackType(getTrackType(), samplePipelineInputBuffer.timeUs);
|
||||
samplePipelineInputBuffer.timeUs -= streamOffsetUs;
|
||||
samplePipelineInputBuffer.flip();
|
||||
if (slowMotionSampleTransformer != null) {
|
||||
slowMotionSampleTransformer.transformSample(samplePipelineInputBuffer);
|
||||
}
|
||||
}
|
||||
samplePipeline.queueInputBuffer();
|
||||
return !samplePipelineInputBuffer.isEndOfStream();
|
||||
case C.RESULT_FORMAT_READ:
|
Loading…
x
Reference in New Issue
Block a user