mirror of
https://github.com/androidx/media.git
synced 2025-05-07 15:40:37 +08:00
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:
parent
14e23d34e1
commit
ef568d2061
@ -34,6 +34,7 @@ import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
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.trackselection.DefaultTrackSelector;
|
||||
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.common.collect.ImmutableList;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/* package */ final class ExoPlayerAssetLoader {
|
||||
|
||||
public interface Listener {
|
||||
|
||||
void onTrackRegistered();
|
||||
|
||||
SamplePipeline onTrackAdded(Format format, long streamStartPositionUs, long streamOffsetUs)
|
||||
throws TransformationException;
|
||||
|
||||
void onEnded();
|
||||
|
||||
void onError(Exception e);
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private final TransformationRequest transformationRequest;
|
||||
private final ImmutableList<Effect> videoEffects;
|
||||
private final boolean removeAudio;
|
||||
private final boolean removeVideo;
|
||||
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 DebugViewProvider debugViewProvider;
|
||||
private final Clock clock;
|
||||
|
||||
private @MonotonicNonNull MuxerWrapper muxerWrapper;
|
||||
@ -82,28 +78,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
public ExoPlayerAssetLoader(
|
||||
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.removeAudio = removeAudio;
|
||||
this.removeVideo = removeVideo;
|
||||
this.mediaSourceFactory = mediaSourceFactory;
|
||||
this.decoderFactory = decoderFactory;
|
||||
this.encoderFactory = encoderFactory;
|
||||
this.frameProcessorFactory = frameProcessorFactory;
|
||||
this.looper = looper;
|
||||
this.debugViewProvider = debugViewProvider;
|
||||
this.clock = clock;
|
||||
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
|
||||
}
|
||||
@ -112,7 +96,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
MediaItem mediaItem,
|
||||
MuxerWrapper muxerWrapper,
|
||||
Listener listener,
|
||||
FallbackListener fallbackListener,
|
||||
Transformer.AsyncErrorListener asyncErrorListener) {
|
||||
this.muxerWrapper = muxerWrapper;
|
||||
|
||||
@ -134,20 +117,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
ExoPlayer.Builder playerBuilder =
|
||||
new ExoPlayer.Builder(
|
||||
context,
|
||||
new RenderersFactoryImpl(
|
||||
context,
|
||||
muxerWrapper,
|
||||
removeAudio,
|
||||
removeVideo,
|
||||
transformationRequest,
|
||||
mediaItem.clippingConfiguration.startsAtKeyFrame,
|
||||
videoEffects,
|
||||
frameProcessorFactory,
|
||||
encoderFactory,
|
||||
decoderFactory,
|
||||
fallbackListener,
|
||||
asyncErrorListener,
|
||||
debugViewProvider))
|
||||
new RenderersFactoryImpl(removeAudio, removeVideo, listener, asyncErrorListener))
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.setTrackSelector(trackSelector)
|
||||
.setLoadControl(loadControl)
|
||||
@ -187,48 +157,21 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
private static final class RenderersFactoryImpl implements RenderersFactory {
|
||||
|
||||
private final Context context;
|
||||
private final MuxerWrapper muxerWrapper;
|
||||
private final TransformerMediaClock mediaClock;
|
||||
private final boolean removeAudio;
|
||||
private final boolean removeVideo;
|
||||
private final TransformationRequest transformationRequest;
|
||||
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 ExoPlayerAssetLoader.Listener assetLoaderListener;
|
||||
private final Transformer.AsyncErrorListener asyncErrorListener;
|
||||
private final DebugViewProvider debugViewProvider;
|
||||
|
||||
public RenderersFactoryImpl(
|
||||
Context context,
|
||||
MuxerWrapper muxerWrapper,
|
||||
boolean removeAudio,
|
||||
boolean removeVideo,
|
||||
TransformationRequest transformationRequest,
|
||||
boolean clippingStartsAtKeyFrame,
|
||||
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;
|
||||
ExoPlayerAssetLoader.Listener assetLoaderListener,
|
||||
Transformer.AsyncErrorListener asyncErrorListener) {
|
||||
this.removeAudio = removeAudio;
|
||||
this.removeVideo = removeVideo;
|
||||
this.transformationRequest = transformationRequest;
|
||||
this.clippingStartsAtKeyFrame = clippingStartsAtKeyFrame;
|
||||
this.videoEffects = videoEffects;
|
||||
this.frameProcessorFactory = frameProcessorFactory;
|
||||
this.encoderFactory = encoderFactory;
|
||||
this.decoderFactory = decoderFactory;
|
||||
this.fallbackListener = fallbackListener;
|
||||
this.assetLoaderListener = assetLoaderListener;
|
||||
this.asyncErrorListener = asyncErrorListener;
|
||||
this.debugViewProvider = debugViewProvider;
|
||||
mediaClock = new TransformerMediaClock();
|
||||
}
|
||||
|
||||
@ -244,31 +187,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
int index = 0;
|
||||
if (!removeAudio) {
|
||||
renderers[index] =
|
||||
new TransformerAudioRenderer(
|
||||
muxerWrapper,
|
||||
mediaClock,
|
||||
transformationRequest,
|
||||
encoderFactory,
|
||||
decoderFactory,
|
||||
asyncErrorListener,
|
||||
fallbackListener);
|
||||
new ExoPlayerAssetLoaderRenderer(
|
||||
C.TRACK_TYPE_AUDIO, mediaClock, assetLoaderListener, asyncErrorListener);
|
||||
index++;
|
||||
}
|
||||
if (!removeVideo) {
|
||||
renderers[index] =
|
||||
new TransformerVideoRenderer(
|
||||
context,
|
||||
muxerWrapper,
|
||||
mediaClock,
|
||||
transformationRequest,
|
||||
clippingStartsAtKeyFrame,
|
||||
videoEffects,
|
||||
frameProcessorFactory,
|
||||
encoderFactory,
|
||||
decoderFactory,
|
||||
asyncErrorListener,
|
||||
fallbackListener,
|
||||
debugViewProvider);
|
||||
new ExoPlayerAssetLoaderRenderer(
|
||||
C.TRACK_TYPE_VIDEO, mediaClock, assetLoaderListener, asyncErrorListener);
|
||||
index++;
|
||||
}
|
||||
return renderers;
|
||||
|
@ -16,46 +16,53 @@
|
||||
|
||||
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 com.google.android.exoplayer2.BaseRenderer;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.FormatHolder;
|
||||
import com.google.android.exoplayer2.RendererCapabilities;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
|
||||
import com.google.android.exoplayer2.util.MediaClock;
|
||||
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.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
/* package */ abstract class TransformerBaseRenderer extends BaseRenderer {
|
||||
/* package */ final class ExoPlayerAssetLoaderRenderer extends BaseRenderer {
|
||||
|
||||
protected final MuxerWrapper muxerWrapper;
|
||||
protected final TransformerMediaClock mediaClock;
|
||||
protected final TransformationRequest transformationRequest;
|
||||
protected final Transformer.AsyncErrorListener asyncErrorListener;
|
||||
protected final FallbackListener fallbackListener;
|
||||
private static final String TAG = "ExoPlayerAssetLoaderRenderer";
|
||||
|
||||
private final TransformerMediaClock mediaClock;
|
||||
private final ExoPlayerAssetLoader.Listener assetLoaderListener;
|
||||
private final Transformer.AsyncErrorListener asyncErrorListener;
|
||||
private final DecoderInputBuffer decoderInputBuffer;
|
||||
|
||||
private boolean isTransformationRunning;
|
||||
protected long streamOffsetUs;
|
||||
protected long streamStartPositionUs;
|
||||
protected @MonotonicNonNull SamplePipeline samplePipeline;
|
||||
private long streamOffsetUs;
|
||||
private long streamStartPositionUs;
|
||||
private @MonotonicNonNull SamplePipeline samplePipeline;
|
||||
|
||||
public TransformerBaseRenderer(
|
||||
public ExoPlayerAssetLoaderRenderer(
|
||||
int trackType,
|
||||
MuxerWrapper muxerWrapper,
|
||||
TransformerMediaClock mediaClock,
|
||||
TransformationRequest transformationRequest,
|
||||
Transformer.AsyncErrorListener asyncErrorListener,
|
||||
FallbackListener fallbackListener) {
|
||||
ExoPlayerAssetLoader.Listener assetLoaderListener,
|
||||
Transformer.AsyncErrorListener asyncErrorListener) {
|
||||
super(trackType);
|
||||
this.muxerWrapper = muxerWrapper;
|
||||
this.mediaClock = mediaClock;
|
||||
this.transformationRequest = transformationRequest;
|
||||
this.assetLoaderListener = assetLoaderListener;
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
public final @Capabilities int supportsFormat(Format format) {
|
||||
public @Capabilities int supportsFormat(Format format) {
|
||||
return RendererCapabilities.create(
|
||||
MimeTypes.getTrackType(format.sampleMimeType) == getTrackType()
|
||||
? C.FORMAT_HANDLED
|
||||
@ -73,22 +80,22 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MediaClock getMediaClock() {
|
||||
public MediaClock getMediaClock() {
|
||||
return mediaClock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isReady() {
|
||||
public boolean isReady() {
|
||||
return isSourceReady();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isEnded() {
|
||||
public boolean isEnded() {
|
||||
return samplePipeline != null && samplePipeline.isEnded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void render(long positionUs, long elapsedRealtimeUs) {
|
||||
public void render(long positionUs, long elapsedRealtimeUs) {
|
||||
try {
|
||||
if (!isTransformationRunning || isEnded() || !ensureConfigured()) {
|
||||
return;
|
||||
@ -97,43 +104,56 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
while (samplePipeline.processData() || feedPipelineFromInput()) {}
|
||||
} catch (TransformationException e) {
|
||||
isTransformationRunning = false;
|
||||
asyncErrorListener.onTransformationException(e);
|
||||
asyncErrorListener.onTransformationError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
|
||||
protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
|
||||
this.streamOffsetUs = offsetUs;
|
||||
this.streamStartPositionUs = startPositionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onEnabled(boolean joining, boolean mayRenderStartOfStream) {
|
||||
muxerWrapper.registerTrack();
|
||||
fallbackListener.registerTrack();
|
||||
protected void onEnabled(boolean joining, boolean mayRenderStartOfStream) {
|
||||
assetLoaderListener.onTrackRegistered();
|
||||
mediaClock.updateTimeForTrackType(getTrackType(), 0L);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onStarted() {
|
||||
protected void onStarted() {
|
||||
isTransformationRunning = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onStopped() {
|
||||
protected void onStopped() {
|
||||
isTransformationRunning = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onReset() {
|
||||
protected void onReset() {
|
||||
if (samplePipeline != null) {
|
||||
samplePipeline.release();
|
||||
}
|
||||
}
|
||||
|
||||
@ForOverride
|
||||
@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.
|
@ -311,7 +311,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
return;
|
||||
}
|
||||
isAborted = true;
|
||||
asyncErrorListener.onTransformationException(
|
||||
asyncErrorListener.onTransformationError(
|
||||
TransformationException.createForMuxer(
|
||||
new IllegalStateException(
|
||||
"No output sample written in the last "
|
||||
|
@ -31,7 +31,6 @@ import androidx.annotation.VisibleForTesting;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||
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.GlEffectsFrameProcessor;
|
||||
import com.google.android.exoplayer2.effect.GlMatrixTransformation;
|
||||
@ -550,7 +549,7 @@ public final class Transformer {
|
||||
private final Looper looper;
|
||||
private final DebugViewProvider debugViewProvider;
|
||||
private final Clock clock;
|
||||
private final ExoPlayerAssetLoader exoPlayerAssetLoader;
|
||||
private final TransformerInternal transformerInternal;
|
||||
|
||||
@Nullable private MuxerWrapper muxerWrapper;
|
||||
@Nullable private String outputPath;
|
||||
@ -588,8 +587,8 @@ public final class Transformer {
|
||||
this.looper = looper;
|
||||
this.debugViewProvider = debugViewProvider;
|
||||
this.clock = clock;
|
||||
exoPlayerAssetLoader =
|
||||
new ExoPlayerAssetLoader(
|
||||
transformerInternal =
|
||||
new TransformerInternal(
|
||||
context,
|
||||
transformationRequest,
|
||||
videoEffects,
|
||||
@ -728,7 +727,7 @@ public final class Transformer {
|
||||
this.muxerWrapper = muxerWrapper;
|
||||
FallbackListener fallbackListener =
|
||||
new FallbackListener(mediaItem, listeners, transformationRequest);
|
||||
exoPlayerAssetLoader.start(
|
||||
transformerInternal.start(
|
||||
mediaItem,
|
||||
muxerWrapper,
|
||||
/* listener= */ componentListener,
|
||||
@ -759,7 +758,7 @@ public final class Transformer {
|
||||
*/
|
||||
public @ProgressState int getProgress(ProgressHolder progressHolder) {
|
||||
verifyApplicationThread();
|
||||
return exoPlayerAssetLoader.getProgress(progressHolder);
|
||||
return transformerInternal.getProgress(progressHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -789,7 +788,7 @@ public final class Transformer {
|
||||
*/
|
||||
private void releaseResources(boolean forCancellation) throws TransformationException {
|
||||
transformationInProgress = false;
|
||||
exoPlayerAssetLoader.release();
|
||||
transformerInternal.release();
|
||||
if (muxerWrapper != null) {
|
||||
try {
|
||||
muxerWrapper.release(forCancellation);
|
||||
@ -834,11 +833,11 @@ public final class Transformer {
|
||||
*
|
||||
* <p>Can be called from any thread.
|
||||
*/
|
||||
void onTransformationException(TransformationException exception);
|
||||
void onTransformationError(TransformationException exception);
|
||||
}
|
||||
|
||||
private final class ComponentListener
|
||||
implements ExoPlayerAssetLoader.Listener, AsyncErrorListener {
|
||||
implements TransformerInternal.Listener, AsyncErrorListener {
|
||||
|
||||
private final MediaItem mediaItem;
|
||||
private final Handler handler;
|
||||
@ -849,21 +848,12 @@ public final class Transformer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
TransformationException transformationException =
|
||||
e instanceof PlaybackException
|
||||
? TransformationException.createForPlaybackException((PlaybackException) e)
|
||||
: TransformationException.createForUnexpected(e);
|
||||
handleTransformationException(transformationException);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnded() {
|
||||
public void onTransformationCompleted() {
|
||||
handleTransformationEnded(/* exception= */ null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransformationException(TransformationException exception) {
|
||||
public void onTransformationError(TransformationException exception) {
|
||||
if (Looper.myLooper() == looper) {
|
||||
handleTransformationException(exception);
|
||||
} else {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -154,7 +154,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
checkNotNull(frameProcessor)
|
||||
.setOutputSurfaceInfo(encoderWrapper.getSurfaceInfo(width, height));
|
||||
} catch (TransformationException exception) {
|
||||
asyncErrorListener.onTransformationException(exception);
|
||||
asyncErrorListener.onTransformationError(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,7 +165,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
@Override
|
||||
public void onFrameProcessingError(FrameProcessingException exception) {
|
||||
asyncErrorListener.onTransformationException(
|
||||
asyncErrorListener.onTransformationError(
|
||||
TransformationException.createForFrameProcessingException(
|
||||
exception, TransformationException.ERROR_CODE_FRAME_PROCESSING_FAILED));
|
||||
}
|
||||
@ -175,7 +175,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
try {
|
||||
encoderWrapper.signalEndOfInputStream();
|
||||
} catch (TransformationException exception) {
|
||||
asyncErrorListener.onTransformationException(exception);
|
||||
asyncErrorListener.onTransformationError(exception);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user