Add TransformerInternal

The player is still driving the transformation at this point. The
transformer thread will be added in another CL.

PiperOrigin-RevId: 487479148
This commit is contained in:
kimvde 2022-11-10 10:32:35 +00:00 committed by microkatz
parent 14e23d34e1
commit ef568d2061
8 changed files with 345 additions and 447 deletions

View File

@ -34,6 +34,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
@ -47,33 +48,28 @@ import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.DebugViewProvider;
import com.google.android.exoplayer2.util.Effect;
import com.google.android.exoplayer2.util.FrameProcessor;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* package */ final class ExoPlayerAssetLoader { /* package */ final class ExoPlayerAssetLoader {
public interface Listener { public interface Listener {
void onTrackRegistered();
SamplePipeline onTrackAdded(Format format, long streamStartPositionUs, long streamOffsetUs)
throws TransformationException;
void onEnded(); void onEnded();
void onError(Exception e); void onError(Exception e);
} }
private final Context context; private final Context context;
private final TransformationRequest transformationRequest;
private final ImmutableList<Effect> videoEffects;
private final boolean removeAudio; private final boolean removeAudio;
private final boolean removeVideo; private final boolean removeVideo;
private final MediaSource.Factory mediaSourceFactory; private final MediaSource.Factory mediaSourceFactory;
private final Codec.DecoderFactory decoderFactory;
private final Codec.EncoderFactory encoderFactory;
private final FrameProcessor.Factory frameProcessorFactory;
private final Looper looper; private final Looper looper;
private final DebugViewProvider debugViewProvider;
private final Clock clock; private final Clock clock;
private @MonotonicNonNull MuxerWrapper muxerWrapper; private @MonotonicNonNull MuxerWrapper muxerWrapper;
@ -82,28 +78,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public ExoPlayerAssetLoader( public ExoPlayerAssetLoader(
Context context, Context context,
TransformationRequest transformationRequest,
ImmutableList<Effect> videoEffects,
boolean removeAudio, boolean removeAudio,
boolean removeVideo, boolean removeVideo,
MediaSource.Factory mediaSourceFactory, MediaSource.Factory mediaSourceFactory,
Codec.DecoderFactory decoderFactory,
Codec.EncoderFactory encoderFactory,
FrameProcessor.Factory frameProcessorFactory,
Looper looper, Looper looper,
DebugViewProvider debugViewProvider,
Clock clock) { Clock clock) {
this.context = context; this.context = context;
this.transformationRequest = transformationRequest;
this.videoEffects = videoEffects;
this.removeAudio = removeAudio; this.removeAudio = removeAudio;
this.removeVideo = removeVideo; this.removeVideo = removeVideo;
this.mediaSourceFactory = mediaSourceFactory; this.mediaSourceFactory = mediaSourceFactory;
this.decoderFactory = decoderFactory;
this.encoderFactory = encoderFactory;
this.frameProcessorFactory = frameProcessorFactory;
this.looper = looper; this.looper = looper;
this.debugViewProvider = debugViewProvider;
this.clock = clock; this.clock = clock;
progressState = PROGRESS_STATE_NO_TRANSFORMATION; progressState = PROGRESS_STATE_NO_TRANSFORMATION;
} }
@ -112,7 +96,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
MediaItem mediaItem, MediaItem mediaItem,
MuxerWrapper muxerWrapper, MuxerWrapper muxerWrapper,
Listener listener, Listener listener,
FallbackListener fallbackListener,
Transformer.AsyncErrorListener asyncErrorListener) { Transformer.AsyncErrorListener asyncErrorListener) {
this.muxerWrapper = muxerWrapper; this.muxerWrapper = muxerWrapper;
@ -134,20 +117,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
ExoPlayer.Builder playerBuilder = ExoPlayer.Builder playerBuilder =
new ExoPlayer.Builder( new ExoPlayer.Builder(
context, context,
new RenderersFactoryImpl( new RenderersFactoryImpl(removeAudio, removeVideo, listener, asyncErrorListener))
context,
muxerWrapper,
removeAudio,
removeVideo,
transformationRequest,
mediaItem.clippingConfiguration.startsAtKeyFrame,
videoEffects,
frameProcessorFactory,
encoderFactory,
decoderFactory,
fallbackListener,
asyncErrorListener,
debugViewProvider))
.setMediaSourceFactory(mediaSourceFactory) .setMediaSourceFactory(mediaSourceFactory)
.setTrackSelector(trackSelector) .setTrackSelector(trackSelector)
.setLoadControl(loadControl) .setLoadControl(loadControl)
@ -187,48 +157,21 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private static final class RenderersFactoryImpl implements RenderersFactory { private static final class RenderersFactoryImpl implements RenderersFactory {
private final Context context;
private final MuxerWrapper muxerWrapper;
private final TransformerMediaClock mediaClock; private final TransformerMediaClock mediaClock;
private final boolean removeAudio; private final boolean removeAudio;
private final boolean removeVideo; private final boolean removeVideo;
private final TransformationRequest transformationRequest; private final ExoPlayerAssetLoader.Listener assetLoaderListener;
private final boolean clippingStartsAtKeyFrame;
private final ImmutableList<Effect> videoEffects;
private final FrameProcessor.Factory frameProcessorFactory;
private final Codec.EncoderFactory encoderFactory;
private final Codec.DecoderFactory decoderFactory;
private final FallbackListener fallbackListener;
private final Transformer.AsyncErrorListener asyncErrorListener; private final Transformer.AsyncErrorListener asyncErrorListener;
private final DebugViewProvider debugViewProvider;
public RenderersFactoryImpl( public RenderersFactoryImpl(
Context context,
MuxerWrapper muxerWrapper,
boolean removeAudio, boolean removeAudio,
boolean removeVideo, boolean removeVideo,
TransformationRequest transformationRequest, ExoPlayerAssetLoader.Listener assetLoaderListener,
boolean clippingStartsAtKeyFrame, Transformer.AsyncErrorListener asyncErrorListener) {
ImmutableList<Effect> videoEffects,
FrameProcessor.Factory frameProcessorFactory,
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory,
FallbackListener fallbackListener,
Transformer.AsyncErrorListener asyncErrorListener,
DebugViewProvider debugViewProvider) {
this.context = context;
this.muxerWrapper = muxerWrapper;
this.removeAudio = removeAudio; this.removeAudio = removeAudio;
this.removeVideo = removeVideo; this.removeVideo = removeVideo;
this.transformationRequest = transformationRequest; this.assetLoaderListener = assetLoaderListener;
this.clippingStartsAtKeyFrame = clippingStartsAtKeyFrame;
this.videoEffects = videoEffects;
this.frameProcessorFactory = frameProcessorFactory;
this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory;
this.fallbackListener = fallbackListener;
this.asyncErrorListener = asyncErrorListener; this.asyncErrorListener = asyncErrorListener;
this.debugViewProvider = debugViewProvider;
mediaClock = new TransformerMediaClock(); mediaClock = new TransformerMediaClock();
} }
@ -244,31 +187,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
int index = 0; int index = 0;
if (!removeAudio) { if (!removeAudio) {
renderers[index] = renderers[index] =
new TransformerAudioRenderer( new ExoPlayerAssetLoaderRenderer(
muxerWrapper, C.TRACK_TYPE_AUDIO, mediaClock, assetLoaderListener, asyncErrorListener);
mediaClock,
transformationRequest,
encoderFactory,
decoderFactory,
asyncErrorListener,
fallbackListener);
index++; index++;
} }
if (!removeVideo) { if (!removeVideo) {
renderers[index] = renderers[index] =
new TransformerVideoRenderer( new ExoPlayerAssetLoaderRenderer(
context, C.TRACK_TYPE_VIDEO, mediaClock, assetLoaderListener, asyncErrorListener);
muxerWrapper,
mediaClock,
transformationRequest,
clippingStartsAtKeyFrame,
videoEffects,
frameProcessorFactory,
encoderFactory,
decoderFactory,
asyncErrorListener,
fallbackListener,
debugViewProvider);
index++; index++;
} }
return renderers; return renderers;

View File

@ -16,46 +16,53 @@
package com.google.android.exoplayer2.transformer; package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.decoder.DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED;
import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.errorprone.annotations.ForOverride;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/* package */ abstract class TransformerBaseRenderer extends BaseRenderer { /* package */ final class ExoPlayerAssetLoaderRenderer extends BaseRenderer {
protected final MuxerWrapper muxerWrapper; private static final String TAG = "ExoPlayerAssetLoaderRenderer";
protected final TransformerMediaClock mediaClock;
protected final TransformationRequest transformationRequest; private final TransformerMediaClock mediaClock;
protected final Transformer.AsyncErrorListener asyncErrorListener; private final ExoPlayerAssetLoader.Listener assetLoaderListener;
protected final FallbackListener fallbackListener; private final Transformer.AsyncErrorListener asyncErrorListener;
private final DecoderInputBuffer decoderInputBuffer;
private boolean isTransformationRunning; private boolean isTransformationRunning;
protected long streamOffsetUs; private long streamOffsetUs;
protected long streamStartPositionUs; private long streamStartPositionUs;
protected @MonotonicNonNull SamplePipeline samplePipeline; private @MonotonicNonNull SamplePipeline samplePipeline;
public TransformerBaseRenderer( public ExoPlayerAssetLoaderRenderer(
int trackType, int trackType,
MuxerWrapper muxerWrapper,
TransformerMediaClock mediaClock, TransformerMediaClock mediaClock,
TransformationRequest transformationRequest, ExoPlayerAssetLoader.Listener assetLoaderListener,
Transformer.AsyncErrorListener asyncErrorListener, Transformer.AsyncErrorListener asyncErrorListener) {
FallbackListener fallbackListener) {
super(trackType); super(trackType);
this.muxerWrapper = muxerWrapper;
this.mediaClock = mediaClock; this.mediaClock = mediaClock;
this.transformationRequest = transformationRequest; this.assetLoaderListener = assetLoaderListener;
this.asyncErrorListener = asyncErrorListener; this.asyncErrorListener = asyncErrorListener;
this.fallbackListener = fallbackListener; decoderInputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
}
@Override
public String getName() {
return TAG;
} }
/** /**
@ -65,7 +72,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* @return The {@link Capabilities} for this format. * @return The {@link Capabilities} for this format.
*/ */
@Override @Override
public final @Capabilities int supportsFormat(Format format) { public @Capabilities int supportsFormat(Format format) {
return RendererCapabilities.create( return RendererCapabilities.create(
MimeTypes.getTrackType(format.sampleMimeType) == getTrackType() MimeTypes.getTrackType(format.sampleMimeType) == getTrackType()
? C.FORMAT_HANDLED ? C.FORMAT_HANDLED
@ -73,22 +80,22 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
@Override @Override
public final MediaClock getMediaClock() { public MediaClock getMediaClock() {
return mediaClock; return mediaClock;
} }
@Override @Override
public final boolean isReady() { public boolean isReady() {
return isSourceReady(); return isSourceReady();
} }
@Override @Override
public final boolean isEnded() { public boolean isEnded() {
return samplePipeline != null && samplePipeline.isEnded(); return samplePipeline != null && samplePipeline.isEnded();
} }
@Override @Override
public final void render(long positionUs, long elapsedRealtimeUs) { public void render(long positionUs, long elapsedRealtimeUs) {
try { try {
if (!isTransformationRunning || isEnded() || !ensureConfigured()) { if (!isTransformationRunning || isEnded() || !ensureConfigured()) {
return; return;
@ -97,43 +104,56 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
while (samplePipeline.processData() || feedPipelineFromInput()) {} while (samplePipeline.processData() || feedPipelineFromInput()) {}
} catch (TransformationException e) { } catch (TransformationException e) {
isTransformationRunning = false; isTransformationRunning = false;
asyncErrorListener.onTransformationException(e); asyncErrorListener.onTransformationError(e);
} }
} }
@Override @Override
protected final void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) { protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
this.streamOffsetUs = offsetUs; this.streamOffsetUs = offsetUs;
this.streamStartPositionUs = startPositionUs; this.streamStartPositionUs = startPositionUs;
} }
@Override @Override
protected final void onEnabled(boolean joining, boolean mayRenderStartOfStream) { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream) {
muxerWrapper.registerTrack(); assetLoaderListener.onTrackRegistered();
fallbackListener.registerTrack();
mediaClock.updateTimeForTrackType(getTrackType(), 0L); mediaClock.updateTimeForTrackType(getTrackType(), 0L);
} }
@Override @Override
protected final void onStarted() { protected void onStarted() {
isTransformationRunning = true; isTransformationRunning = true;
} }
@Override @Override
protected final void onStopped() { protected void onStopped() {
isTransformationRunning = false; isTransformationRunning = false;
} }
@Override @Override
protected final void onReset() { protected void onReset() {
if (samplePipeline != null) { if (samplePipeline != null) {
samplePipeline.release(); samplePipeline.release();
} }
} }
@ForOverride
@EnsuresNonNullIf(expression = "samplePipeline", result = true) @EnsuresNonNullIf(expression = "samplePipeline", result = true)
protected abstract boolean ensureConfigured() throws TransformationException; private boolean ensureConfigured() throws TransformationException {
if (samplePipeline != null) {
return true;
}
FormatHolder formatHolder = getFormatHolder();
@ReadDataResult
int result = readSource(formatHolder, decoderInputBuffer, /* readFlags= */ FLAG_REQUIRE_FORMAT);
if (result != C.RESULT_FORMAT_READ) {
return false;
}
Format inputFormat = checkNotNull(formatHolder.format);
samplePipeline =
assetLoaderListener.onTrackAdded(inputFormat, streamStartPositionUs, streamOffsetUs);
return true;
}
/** /**
* Attempts to read input data and pass the input data to the sample pipeline. * Attempts to read input data and pass the input data to the sample pipeline.

View File

@ -311,7 +311,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return; return;
} }
isAborted = true; isAborted = true;
asyncErrorListener.onTransformationException( asyncErrorListener.onTransformationError(
TransformationException.createForMuxer( TransformationException.createForMuxer(
new IllegalStateException( new IllegalStateException(
"No output sample written in the last " "No output sample written in the last "

View File

@ -31,7 +31,6 @@ import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.effect.GlEffect; import com.google.android.exoplayer2.effect.GlEffect;
import com.google.android.exoplayer2.effect.GlEffectsFrameProcessor; import com.google.android.exoplayer2.effect.GlEffectsFrameProcessor;
import com.google.android.exoplayer2.effect.GlMatrixTransformation; import com.google.android.exoplayer2.effect.GlMatrixTransformation;
@ -550,7 +549,7 @@ public final class Transformer {
private final Looper looper; private final Looper looper;
private final DebugViewProvider debugViewProvider; private final DebugViewProvider debugViewProvider;
private final Clock clock; private final Clock clock;
private final ExoPlayerAssetLoader exoPlayerAssetLoader; private final TransformerInternal transformerInternal;
@Nullable private MuxerWrapper muxerWrapper; @Nullable private MuxerWrapper muxerWrapper;
@Nullable private String outputPath; @Nullable private String outputPath;
@ -588,8 +587,8 @@ public final class Transformer {
this.looper = looper; this.looper = looper;
this.debugViewProvider = debugViewProvider; this.debugViewProvider = debugViewProvider;
this.clock = clock; this.clock = clock;
exoPlayerAssetLoader = transformerInternal =
new ExoPlayerAssetLoader( new TransformerInternal(
context, context,
transformationRequest, transformationRequest,
videoEffects, videoEffects,
@ -728,7 +727,7 @@ public final class Transformer {
this.muxerWrapper = muxerWrapper; this.muxerWrapper = muxerWrapper;
FallbackListener fallbackListener = FallbackListener fallbackListener =
new FallbackListener(mediaItem, listeners, transformationRequest); new FallbackListener(mediaItem, listeners, transformationRequest);
exoPlayerAssetLoader.start( transformerInternal.start(
mediaItem, mediaItem,
muxerWrapper, muxerWrapper,
/* listener= */ componentListener, /* listener= */ componentListener,
@ -759,7 +758,7 @@ public final class Transformer {
*/ */
public @ProgressState int getProgress(ProgressHolder progressHolder) { public @ProgressState int getProgress(ProgressHolder progressHolder) {
verifyApplicationThread(); verifyApplicationThread();
return exoPlayerAssetLoader.getProgress(progressHolder); return transformerInternal.getProgress(progressHolder);
} }
/** /**
@ -789,7 +788,7 @@ public final class Transformer {
*/ */
private void releaseResources(boolean forCancellation) throws TransformationException { private void releaseResources(boolean forCancellation) throws TransformationException {
transformationInProgress = false; transformationInProgress = false;
exoPlayerAssetLoader.release(); transformerInternal.release();
if (muxerWrapper != null) { if (muxerWrapper != null) {
try { try {
muxerWrapper.release(forCancellation); muxerWrapper.release(forCancellation);
@ -834,11 +833,11 @@ public final class Transformer {
* *
* <p>Can be called from any thread. * <p>Can be called from any thread.
*/ */
void onTransformationException(TransformationException exception); void onTransformationError(TransformationException exception);
} }
private final class ComponentListener private final class ComponentListener
implements ExoPlayerAssetLoader.Listener, AsyncErrorListener { implements TransformerInternal.Listener, AsyncErrorListener {
private final MediaItem mediaItem; private final MediaItem mediaItem;
private final Handler handler; private final Handler handler;
@ -849,21 +848,12 @@ public final class Transformer {
} }
@Override @Override
public void onError(Exception e) { public void onTransformationCompleted() {
TransformationException transformationException =
e instanceof PlaybackException
? TransformationException.createForPlaybackException((PlaybackException) e)
: TransformationException.createForUnexpected(e);
handleTransformationException(transformationException);
}
@Override
public void onEnded() {
handleTransformationEnded(/* exception= */ null); handleTransformationEnded(/* exception= */ null);
} }
@Override @Override
public void onTransformationException(TransformationException exception) { public void onTransformationError(TransformationException exception) {
if (Looper.myLooper() == looper) { if (Looper.myLooper() == looper) {
handleTransformationException(exception); handleTransformationException(exception);
} else { } else {

View File

@ -1,132 +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 com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.mp4.SlowMotionData;
import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
/* package */ final class TransformerAudioRenderer extends TransformerBaseRenderer {
private static final String TAG = "TAudioRenderer";
private final Codec.EncoderFactory encoderFactory;
private final Codec.DecoderFactory decoderFactory;
private final DecoderInputBuffer decoderInputBuffer;
public TransformerAudioRenderer(
MuxerWrapper muxerWrapper,
TransformerMediaClock mediaClock,
TransformationRequest transformationRequest,
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory,
Transformer.AsyncErrorListener asyncErrorListener,
FallbackListener fallbackListener) {
super(
C.TRACK_TYPE_AUDIO,
muxerWrapper,
mediaClock,
transformationRequest,
asyncErrorListener,
fallbackListener);
this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory;
decoderInputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
}
@Override
public String getName() {
return TAG;
}
/** Attempts to read the input format and to initialize the {@link SamplePipeline}. */
@Override
protected boolean ensureConfigured() throws TransformationException {
if (samplePipeline != null) {
return true;
}
FormatHolder formatHolder = getFormatHolder();
@ReadDataResult
int result = readSource(formatHolder, decoderInputBuffer, /* readFlags= */ FLAG_REQUIRE_FORMAT);
if (result != C.RESULT_FORMAT_READ) {
return false;
}
Format inputFormat = checkNotNull(formatHolder.format);
if (shouldPassthrough(inputFormat)) {
samplePipeline =
new PassthroughSamplePipeline(
inputFormat,
streamOffsetUs,
streamStartPositionUs,
transformationRequest,
muxerWrapper,
fallbackListener);
} else {
samplePipeline =
new AudioTranscodingSamplePipeline(
inputFormat,
streamOffsetUs,
streamStartPositionUs,
transformationRequest,
decoderFactory,
encoderFactory,
muxerWrapper,
fallbackListener);
}
return true;
}
private boolean shouldPassthrough(Format inputFormat) {
if (encoderFactory.audioNeedsEncoding()) {
return false;
}
if (transformationRequest.audioMimeType != null
&& !transformationRequest.audioMimeType.equals(inputFormat.sampleMimeType)) {
return false;
}
if (transformationRequest.audioMimeType == null
&& !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) {
return false;
}
if (transformationRequest.flattenForSlowMotion && isSlowMotion(inputFormat)) {
return false;
}
return true;
}
private static boolean isSlowMotion(Format format) {
@Nullable Metadata metadata = format.metadata;
if (metadata == null) {
return false;
}
for (int i = 0; i < metadata.length(); i++) {
if (metadata.get(i) instanceof SlowMotionData) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,263 @@
/*
* Copyright 2022 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 com.google.android.exoplayer2.transformer;
import android.content.Context;
import android.os.Looper;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.mp4.SlowMotionData;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.DebugViewProvider;
import com.google.android.exoplayer2.util.Effect;
import com.google.android.exoplayer2.util.FrameProcessor;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.common.collect.ImmutableList;
/* package */ final class TransformerInternal {
public interface Listener {
void onTransformationCompleted();
void onTransformationError(TransformationException exception);
}
private final Context context;
private final TransformationRequest transformationRequest;
private final ImmutableList<Effect> videoEffects;
private final Codec.DecoderFactory decoderFactory;
private final Codec.EncoderFactory encoderFactory;
private final FrameProcessor.Factory frameProcessorFactory;
private final DebugViewProvider debugViewProvider;
private final ExoPlayerAssetLoader exoPlayerAssetLoader;
public TransformerInternal(
Context context,
TransformationRequest transformationRequest,
ImmutableList<Effect> videoEffects,
boolean removeAudio,
boolean removeVideo,
MediaSource.Factory mediaSourceFactory,
Codec.DecoderFactory decoderFactory,
Codec.EncoderFactory encoderFactory,
FrameProcessor.Factory frameProcessorFactory,
Looper looper,
DebugViewProvider debugViewProvider,
Clock clock) {
this.context = context;
this.transformationRequest = transformationRequest;
this.videoEffects = videoEffects;
this.decoderFactory = decoderFactory;
this.encoderFactory = encoderFactory;
this.frameProcessorFactory = frameProcessorFactory;
this.debugViewProvider = debugViewProvider;
exoPlayerAssetLoader =
new ExoPlayerAssetLoader(
context, removeAudio, removeVideo, mediaSourceFactory, looper, clock);
}
public void start(
MediaItem mediaItem,
MuxerWrapper muxerWrapper,
Listener listener,
FallbackListener fallbackListener,
Transformer.AsyncErrorListener asyncErrorListener) {
ComponentListener componentListener =
new ComponentListener(
mediaItem, muxerWrapper, listener, fallbackListener, asyncErrorListener);
exoPlayerAssetLoader.start(mediaItem, muxerWrapper, componentListener, asyncErrorListener);
}
public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) {
return exoPlayerAssetLoader.getProgress(progressHolder);
}
public void release() {
exoPlayerAssetLoader.release();
}
private class ComponentListener implements ExoPlayerAssetLoader.Listener {
private final MediaItem mediaItem;
private final MuxerWrapper muxerWrapper;
private final TransformerInternal.Listener listener;
private final FallbackListener fallbackListener;
private final Transformer.AsyncErrorListener asyncErrorListener;
public ComponentListener(
MediaItem mediaItem,
MuxerWrapper muxerWrapper,
Listener listener,
FallbackListener fallbackListener,
Transformer.AsyncErrorListener asyncErrorListener) {
this.mediaItem = mediaItem;
this.muxerWrapper = muxerWrapper;
this.listener = listener;
this.fallbackListener = fallbackListener;
this.asyncErrorListener = asyncErrorListener;
}
@Override
public void onTrackRegistered() {
muxerWrapper.registerTrack();
fallbackListener.registerTrack();
}
@Override
public SamplePipeline onTrackAdded(
Format format, long streamStartPositionUs, long streamOffsetUs)
throws TransformationException {
return getSamplePipeline(format, streamStartPositionUs, streamOffsetUs);
}
@Override
public void onError(Exception e) {
TransformationException transformationException =
e instanceof PlaybackException
? TransformationException.createForPlaybackException((PlaybackException) e)
: TransformationException.createForUnexpected(e);
listener.onTransformationError(transformationException);
}
@Override
public void onEnded() {
listener.onTransformationCompleted();
}
private SamplePipeline getSamplePipeline(
Format inputFormat, long streamStartPositionUs, long streamOffsetUs)
throws TransformationException {
if (MimeTypes.isAudio(inputFormat.sampleMimeType) && shouldTranscodeAudio(inputFormat)) {
return new AudioTranscodingSamplePipeline(
inputFormat,
streamOffsetUs,
streamOffsetUs,
transformationRequest,
decoderFactory,
encoderFactory,
muxerWrapper,
fallbackListener);
} else if (MimeTypes.isVideo(inputFormat.sampleMimeType)
&& shouldTranscodeVideo(inputFormat, streamStartPositionUs, streamOffsetUs)) {
return new VideoTranscodingSamplePipeline(
context,
inputFormat,
streamOffsetUs,
streamStartPositionUs,
transformationRequest,
videoEffects,
frameProcessorFactory,
decoderFactory,
encoderFactory,
muxerWrapper,
fallbackListener,
asyncErrorListener,
debugViewProvider);
} else {
return new PassthroughSamplePipeline(
inputFormat,
streamOffsetUs,
streamStartPositionUs,
transformationRequest,
muxerWrapper,
fallbackListener);
}
}
private boolean shouldTranscodeAudio(Format inputFormat) {
if (encoderFactory.audioNeedsEncoding()) {
return true;
}
if (transformationRequest.audioMimeType != null
&& !transformationRequest.audioMimeType.equals(inputFormat.sampleMimeType)) {
return true;
}
if (transformationRequest.audioMimeType == null
&& !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) {
return true;
}
if (transformationRequest.flattenForSlowMotion && isSlowMotion(inputFormat)) {
return true;
}
return false;
}
private boolean isSlowMotion(Format format) {
@Nullable Metadata metadata = format.metadata;
if (metadata == null) {
return false;
}
for (int i = 0; i < metadata.length(); i++) {
if (metadata.get(i) instanceof SlowMotionData) {
return true;
}
}
return false;
}
private boolean shouldTranscodeVideo(
Format inputFormat, long streamStartPositionUs, long streamOffsetUs) {
if ((streamStartPositionUs - streamOffsetUs) != 0
&& !mediaItem.clippingConfiguration.startsAtKeyFrame) {
return true;
}
if (encoderFactory.videoNeedsEncoding()) {
return true;
}
if (transformationRequest.enableRequestSdrToneMapping) {
return true;
}
if (transformationRequest.videoMimeType != null
&& !transformationRequest.videoMimeType.equals(inputFormat.sampleMimeType)) {
return true;
}
if (transformationRequest.videoMimeType == null
&& !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) {
return true;
}
if (inputFormat.pixelWidthHeightRatio != 1f) {
return true;
}
if (transformationRequest.rotationDegrees != 0f) {
return true;
}
if (transformationRequest.scaleX != 1f) {
return true;
}
if (transformationRequest.scaleY != 1f) {
return true;
}
// The decoder rotates encoded frames for display by inputFormat.rotationDegrees.
int decodedHeight =
(inputFormat.rotationDegrees % 180 == 0) ? inputFormat.height : inputFormat.width;
if (transformationRequest.outputHeight != C.LENGTH_UNSET
&& transformationRequest.outputHeight != decodedHeight) {
return true;
}
if (!videoEffects.isEmpty()) {
return true;
}
return false;
}
}
}

View File

@ -1,169 +0,0 @@
/*
* Copyright 2021 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 com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.content.Context;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
import com.google.android.exoplayer2.util.DebugViewProvider;
import com.google.android.exoplayer2.util.Effect;
import com.google.android.exoplayer2.util.FrameProcessor;
import com.google.common.collect.ImmutableList;
/* package */ final class TransformerVideoRenderer extends TransformerBaseRenderer {
private static final String TAG = "TVideoRenderer";
private final Context context;
private final boolean clippingStartsAtKeyFrame;
private final ImmutableList<Effect> effects;
private final FrameProcessor.Factory frameProcessorFactory;
private final Codec.EncoderFactory encoderFactory;
private final Codec.DecoderFactory decoderFactory;
private final DebugViewProvider debugViewProvider;
private final DecoderInputBuffer decoderInputBuffer;
public TransformerVideoRenderer(
Context context,
MuxerWrapper muxerWrapper,
TransformerMediaClock mediaClock,
TransformationRequest transformationRequest,
boolean clippingStartsAtKeyFrame,
ImmutableList<Effect> effects,
FrameProcessor.Factory frameProcessorFactory,
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory,
Transformer.AsyncErrorListener asyncErrorListener,
FallbackListener fallbackListener,
DebugViewProvider debugViewProvider) {
super(
C.TRACK_TYPE_VIDEO,
muxerWrapper,
mediaClock,
transformationRequest,
asyncErrorListener,
fallbackListener);
this.context = context;
this.clippingStartsAtKeyFrame = clippingStartsAtKeyFrame;
this.effects = effects;
this.frameProcessorFactory = frameProcessorFactory;
this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory;
this.debugViewProvider = debugViewProvider;
decoderInputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
}
@Override
public String getName() {
return TAG;
}
/** Attempts to read the input format and to initialize the {@link SamplePipeline}. */
@Override
protected boolean ensureConfigured() throws TransformationException {
if (samplePipeline != null) {
return true;
}
FormatHolder formatHolder = getFormatHolder();
@ReadDataResult
int result = readSource(formatHolder, decoderInputBuffer, /* readFlags= */ FLAG_REQUIRE_FORMAT);
if (result != C.RESULT_FORMAT_READ) {
return false;
}
Format inputFormat = checkNotNull(formatHolder.format);
if (shouldTranscode(inputFormat)) {
samplePipeline =
new VideoTranscodingSamplePipeline(
context,
inputFormat,
streamOffsetUs,
streamStartPositionUs,
transformationRequest,
effects,
frameProcessorFactory,
decoderFactory,
encoderFactory,
muxerWrapper,
fallbackListener,
asyncErrorListener,
debugViewProvider);
} else {
samplePipeline =
new PassthroughSamplePipeline(
inputFormat,
streamOffsetUs,
streamStartPositionUs,
transformationRequest,
muxerWrapper,
fallbackListener);
}
return true;
}
private boolean shouldTranscode(Format inputFormat) {
if ((streamStartPositionUs - streamOffsetUs) != 0 && !clippingStartsAtKeyFrame) {
return true;
}
if (encoderFactory.videoNeedsEncoding()) {
return true;
}
if (transformationRequest.enableRequestSdrToneMapping) {
return true;
}
if (transformationRequest.forceInterpretHdrVideoAsSdr) {
return true;
}
if (transformationRequest.videoMimeType != null
&& !transformationRequest.videoMimeType.equals(inputFormat.sampleMimeType)) {
return true;
}
if (transformationRequest.videoMimeType == null
&& !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) {
return true;
}
if (inputFormat.pixelWidthHeightRatio != 1f) {
return true;
}
if (transformationRequest.rotationDegrees != 0f) {
return true;
}
if (transformationRequest.scaleX != 1f) {
return true;
}
if (transformationRequest.scaleY != 1f) {
return true;
}
// The decoder rotates encoded frames for display by inputFormat.rotationDegrees.
int decodedHeight =
(inputFormat.rotationDegrees % 180 == 0) ? inputFormat.height : inputFormat.width;
if (transformationRequest.outputHeight != C.LENGTH_UNSET
&& transformationRequest.outputHeight != decodedHeight) {
return true;
}
if (!effects.isEmpty()) {
return true;
}
return false;
}
}

View File

@ -154,7 +154,7 @@ import org.checkerframework.dataflow.qual.Pure;
checkNotNull(frameProcessor) checkNotNull(frameProcessor)
.setOutputSurfaceInfo(encoderWrapper.getSurfaceInfo(width, height)); .setOutputSurfaceInfo(encoderWrapper.getSurfaceInfo(width, height));
} catch (TransformationException exception) { } catch (TransformationException exception) {
asyncErrorListener.onTransformationException(exception); asyncErrorListener.onTransformationError(exception);
} }
} }
@ -165,7 +165,7 @@ import org.checkerframework.dataflow.qual.Pure;
@Override @Override
public void onFrameProcessingError(FrameProcessingException exception) { public void onFrameProcessingError(FrameProcessingException exception) {
asyncErrorListener.onTransformationException( asyncErrorListener.onTransformationError(
TransformationException.createForFrameProcessingException( TransformationException.createForFrameProcessingException(
exception, TransformationException.ERROR_CODE_FRAME_PROCESSING_FAILED)); exception, TransformationException.ERROR_CODE_FRAME_PROCESSING_FAILED));
} }
@ -175,7 +175,7 @@ import org.checkerframework.dataflow.qual.Pure;
try { try {
encoderWrapper.signalEndOfInputStream(); encoderWrapper.signalEndOfInputStream();
} catch (TransformationException exception) { } catch (TransformationException exception) {
asyncErrorListener.onTransformationException(exception); asyncErrorListener.onTransformationError(exception);
} }
} }
}, },