content: /*
   * Copyright (C) 2019 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.ext.av1;
  
  import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION;
  
  import android.os.Handler;
  import android.view.Surface;
  import androidx.annotation.Nullable;
  import com.google.android.exoplayer2.C;
  import com.google.android.exoplayer2.Format;
  import com.google.android.exoplayer2.RendererCapabilities;
  import com.google.android.exoplayer2.decoder.CryptoConfig;
  import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
  import com.google.android.exoplayer2.decoder.VideoDecoderOutputBuffer;
  import com.google.android.exoplayer2.util.MimeTypes;
  import com.google.android.exoplayer2.util.TraceUtil;
  import com.google.android.exoplayer2.util.Util;
  import com.google.android.exoplayer2.video.DecoderVideoRenderer;
  import com.google.android.exoplayer2.video.VideoRendererEventListener;
  
  /** Decodes and renders video using libgav1 decoder. */
  public class Libgav1VideoRenderer extends DecoderVideoRenderer {
  
    /**
     * Attempts to use as many threads as performance processors available on the device. If the
     * number of performance processors cannot be detected, the number of available processors is
     * used.
     */
    public static final int THREAD_COUNT_AUTODETECT = 0;
  
    private static final String TAG = "Libgav1VideoRenderer";
    private static final int DEFAULT_NUM_OF_INPUT_BUFFERS = 4;
    private static final int DEFAULT_NUM_OF_OUTPUT_BUFFERS = 4;
    /**
     * Default input buffer size in bytes, based on 720p resolution video compressed by a factor of
     * two.
     */
    private static final int DEFAULT_INPUT_BUFFER_SIZE =
        Util.ceilDivide(1280, 64) * Util.ceilDivide(720, 64) * (64 * 64 * 3 / 2) / 2;
  
    /** The number of input buffers. */
    private final int numInputBuffers;
    /**
     * The number of output buffers. The renderer may limit the minimum possible value due to
     * requiring multiple output buffers to be dequeued at a time for it to make progress.
     */
    private final int numOutputBuffers;
  
    private final int threads;
  
    @Nullable private Gav1Decoder decoder;
  
    /**
     * Creates a new instance.
     *
     * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
     *     can attempt to seamlessly join an ongoing playback.
     * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
     *     null if delivery of events is not required.
     * @param eventListener A listener of events. May be null if delivery of events is not required.
     * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
     *     invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
     */
    public Libgav1VideoRenderer(
        long allowedJoiningTimeMs,
        @Nullable Handler eventHandler,
        @Nullable VideoRendererEventListener eventListener,
        int maxDroppedFramesToNotify) {
      this(
          allowedJoiningTimeMs,
          eventHandler,
          eventListener,
          maxDroppedFramesToNotify,
          THREAD_COUNT_AUTODETECT,
          DEFAULT_NUM_OF_INPUT_BUFFERS,
          DEFAULT_NUM_OF_OUTPUT_BUFFERS);
    }
  
    /**
     * Creates a new instance.
     *
     * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
     *     can attempt to seamlessly join an ongoing playback.
     * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
     *     null if delivery of events is not required.
     * @param eventListener A listener of events. May be null if delivery of events is not required.
     * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
     *     invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
     * @param threads Number of threads libgav1 will use to decode. If {@link
     *     #THREAD_COUNT_AUTODETECT} is passed, then the number of threads to use is autodetected
     *     based on CPU capabilities.
     * @param numInputBuffers Number of input buffers.
     * @param numOutputBuffers Number of output buffers.
     */
    public Libgav1VideoRenderer(
        long allowedJoiningTimeMs,
        @Nullable Handler eventHandler,
        @Nullable VideoRendererEventListener eventListener,
        int maxDroppedFramesToNotify,
        int threads,
        int numInputBuffers,
        int numOutputBuffers) {
      super(allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify);
      this.threads = threads;
      this.numInputBuffers = numInputBuffers;
      this.numOutputBuffers = numOutputBuffers;
    }
  
    @Override
    public String getName() {
      return TAG;
    }
  
    @Override
    public final @Capabilities int supportsFormat(Format format) {
      if (!MimeTypes.VIDEO_AV1.equalsIgnoreCase(format.sampleMimeType)
          || !Gav1Library.isAvailable()) {
        return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE);
      }
      if (format.cryptoType != C.CRYPTO_TYPE_NONE) {
        return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_DRM);
      }
      return RendererCapabilities.create(
          C.FORMAT_HANDLED, ADAPTIVE_SEAMLESS, TUNNELING_NOT_SUPPORTED);
    }
  
    /** {@inheritDoc} */
    @Override
    protected final Gav1Decoder createDecoder(Format format, @Nullable CryptoConfig cryptoConfig)
        throws Gav1DecoderException {
      TraceUtil.beginSection("createGav1Decoder");
      int initialInputBufferSize =
          format.maxInputSize != Format.NO_VALUE ? format.maxInputSize : DEFAULT_INPUT_BUFFER_SIZE;
      Gav1Decoder decoder =
          new Gav1Decoder(numInputBuffers, numOutputBuffers, initialInputBufferSize, threads);
      this.decoder = decoder;
      TraceUtil.endSection();
      return decoder;
    }
  
    @Override
    protected void renderOutputBufferToSurface(VideoDecoderOutputBuffer outputBuffer, Surface surface)
        throws Gav1DecoderException {
      if (decoder == null) {
        throw new Gav1DecoderException(
            "Failed to render output buffer to surface: decoder is not initialized.");
      }
      decoder.renderToSurface(outputBuffer, surface);
      outputBuffer.release();
    }
  
    @Override
    protected void setDecoderOutputMode(@C.VideoOutputMode int outputMode) {
      if (decoder != null) {
        decoder.setOutputMode(outputMode);
      }
    }
  
    @Override
    protected DecoderReuseEvaluation canReuseDecoder(
        String decoderName, Format oldFormat, Format newFormat) {
      return new DecoderReuseEvaluation(
          decoderName,
          oldFormat,
          newFormat,
          REUSE_RESULT_YES_WITHOUT_RECONFIGURATION,
          /* discardReasons= */ 0);
    }
  }
  