mirror of
https://github.com/androidx/media.git
synced 2025-05-08 08:00:49 +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.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;
|
||||||
|
@ -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.
|
@ -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 "
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user