/*
* 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);
ByteBuffer data = checkNotNull(buffer.data);
data.flip();
if (sampleTransformer != null) {
sampleTransformer.transformSample(buffer);
}
return true;
}
}