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) {
|
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;
|
||||||
|
@ -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;
|
||||||
|
@ -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() {
|
||||||
|
@ -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;
|
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:
|
||||||
mediaClock.updateTimeForTrackType(getTrackType(), samplePipelineInputBuffer.timeUs);
|
if (samplePipelineInputBuffer.data != null
|
||||||
samplePipelineInputBuffer.timeUs -= streamOffsetUs;
|
&& samplePipelineInputBuffer.data.position() > 0) {
|
||||||
samplePipelineInputBuffer.flip();
|
mediaClock.updateTimeForTrackType(getTrackType(), samplePipelineInputBuffer.timeUs);
|
||||||
|
samplePipelineInputBuffer.timeUs -= streamOffsetUs;
|
||||||
|
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:
|
Loading…
x
Reference in New Issue
Block a user