diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 264b31a9a9..18bbd50939 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -1097,6 +1097,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (codecOperatingRate <= assumedMinimumCodecOperatingRate) { codecOperatingRate = CODEC_OPERATING_RATE_UNSET; } + onReadyToInitializeCodec(inputFormat); codecInitializingTimestamp = SystemClock.elapsedRealtime(); MediaCodecAdapter.Configuration configuration = getMediaCodecConfiguration(codecInfo, inputFormat, crypto, codecOperatingRate); @@ -1381,6 +1382,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return true; } + /** + * Called when ready to initialize the {@link MediaCodecAdapter}. + * + *
This method is called just before the renderer obtains the {@linkplain + * #getMediaCodecConfiguration configuration} for the {@link MediaCodecAdapter} and creates the + * adapter via the passed in {@link MediaCodecAdapter.Factory}. + * + *
The default implementation is a no-op. + * + * @param format The {@link Format} for which the codec is being configured. + * @throws ExoPlaybackException If an error occurs preparing for initializing the codec. + */ + protected void onReadyToInitializeCodec(Format format) throws ExoPlaybackException { + // Do nothing. + } + /** * Called when a {@link MediaCodec} has been created and configured. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index f7cc50486f..2207efb3de 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -50,6 +50,7 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; +import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.decoder.DecoderCounters; @@ -64,15 +65,27 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; +import com.google.android.exoplayer2.util.DebugViewProvider; +import com.google.android.exoplayer2.util.Effect; +import com.google.android.exoplayer2.util.FrameInfo; +import com.google.android.exoplayer2.util.FrameProcessingException; +import com.google.android.exoplayer2.util.FrameProcessor; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MediaFormatUtil; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Size; +import com.google.android.exoplayer2.util.SurfaceInfo; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.nio.ByteBuffer; +import java.util.ArrayDeque; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import org.checkerframework.checker.initialization.qual.UnderInitialization; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Decodes and renders video using {@link MediaCodec}. @@ -84,6 +97,8 @@ import java.util.List; *
Caller must ensure frame processing {@linkplain #isEnabled() is not enabled} before + * calling this method. + * + * @param inputFormat The {@link Format} that is input into the {@link FrameProcessor}. + * @return Whether frame processing is enabled. + * @throws ExoPlaybackException When enabling the {@link FrameProcessor} failed. + */ + @CanIgnoreReturnValue + public boolean maybeEnable(Format inputFormat) throws ExoPlaybackException { + checkState(!isEnabled()); + if (!canEnableFrameProcessing) { + return false; + } + if (videoEffects == null) { + canEnableFrameProcessing = false; + return false; + } + + // Playback thread handler. + handler = Util.createHandlerForCurrentLooper(); + try { + frameProcessor = + ((FrameProcessor.Factory) + Class.forName(FRAME_PROCESSOR_FACTORY_CLASS).getConstructor().newInstance()) + .create( + renderer.context, + checkNotNull(videoEffects), + DebugViewProvider.NONE, + inputFormat.colorInfo != null + ? inputFormat.colorInfo + : ColorInfo.SDR_BT709_LIMITED, + /* outputColorInfo= */ ColorInfo.SDR_BT709_LIMITED, + /* releaseFramesAutomatically= */ false, + /* executor= */ handler::post, + new FrameProcessor.Listener() { + @Override + public void onOutputSizeChanged(int width, int height) { + // TODO(b/238302341) Handle output size change. + } + + @Override + public void onOutputFrameAvailable(long presentationTimeUs) { + processedFrames.add(presentationTimeUs); + } + + @Override + public void onFrameProcessingError(FrameProcessingException exception) { + renderer.setPendingPlaybackException( + renderer.createRendererException( + exception, + inputFormat, + // TODO(b/238302341) Add relevant error codes for frame processing. + PlaybackException.ERROR_CODE_UNSPECIFIED)); + } + + @Override + public void onFrameProcessingEnded() { + throw new IllegalStateException(); + } + }); + } catch (Exception e) { + throw renderer.createRendererException( + e, inputFormat, PlaybackException.ERROR_CODE_UNSPECIFIED); + } + setInputFrameInfo(inputFormat.width, inputFormat.height, inputFormat.pixelWidthHeightRatio); + return true; + } + + /** + * Returns the {@linkplain FrameProcessor#getInputSurface input surface} of the {@link + * FrameProcessor}. + * + *
Caller must ensure the {@code FrameProcessorManager} {@link #isEnabled()} before calling + * this method. + */ + public Surface getInputSurface() { + return checkNotNull(frameProcessor).getInputSurface(); + } + + /** + * Sets the output surface info. + * + *
Caller must ensure the {@code FrameProcessorManager} {@link #isEnabled()} before calling + * this method. + * + * @param outputSurface The {@link Surface} to which {@link FrameProcessor} outputs. + * @param outputResolution The {@link Size} of the output resolution. + */ + public void setOutputSurfaceInfo(Surface outputSurface, Size outputResolution) { + checkNotNull(frameProcessor) + .setOutputSurfaceInfo( + new SurfaceInfo( + outputSurface, outputResolution.getWidth(), outputResolution.getHeight())); + } + + /** + * Sets the input surface info. + * + *
Caller must ensure the {@code FrameProcessorManager} {@link #isEnabled()} before calling + * this method. + */ + public void setInputFrameInfo(int width, int height, float pixelWidthHeightRatio) { + checkNotNull(frameProcessor) + .setInputFrameInfo( + new FrameInfo( + width, height, pixelWidthHeightRatio, renderer.getOutputStreamOffsetUs())); + } + + /** Sets the necessary {@link MediaFormat} keys for frame processing. */ + @SuppressWarnings("InlinedApi") + public MediaFormat amendMediaFormatKeys(MediaFormat mediaFormat) { + if (Util.SDK_INT >= 29 + && renderer.context.getApplicationContext().getApplicationInfo().targetSdkVersion >= 29) { + mediaFormat.setInteger(MediaFormat.KEY_ALLOW_FRAME_DROP, 0); + } + return mediaFormat; + } + + /** + * Releases the resources. + * + *
Caller must ensure frame processing {@linkplain #isEnabled() is not enabled} before + * calling this method. + */ + public void reset() { + checkNotNull(frameProcessor).release(); + frameProcessor = null; + if (handler != null) { + handler.removeCallbacksAndMessages(/* token= */ null); + } + if (videoEffects != null) { + videoEffects.clear(); + } + processedFrames.clear(); + canEnableFrameProcessing = true; + } + } + /** * Returns a maximum video size to use when configuring a codec for {@code format} in a way that * will allow possible adaptation to other compatible formats that are expected to have the same