VideoEncoderSettings.java

/*
 * 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 androidx.media3.transformer;

import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR;
import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkState;
import static java.lang.annotation.ElementType.TYPE_USE;

import android.annotation.SuppressLint;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.Format;
import androidx.media3.common.util.UnstableApi;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** Represents the video encoder settings. */
@UnstableApi
public final class VideoEncoderSettings {

  /** A value for various fields to indicate that the field's value is unknown or not applicable. */
  public static final int NO_VALUE = Format.NO_VALUE;
  /** The default I-frame interval in seconds. */
  public static final float DEFAULT_I_FRAME_INTERVAL_SECONDS = 1.0f;

  /** A default {@link VideoEncoderSettings}. */
  public static final VideoEncoderSettings DEFAULT = new Builder().build();

  /**
   * The allowed values for {@code bitrateMode}.
   *
   * <ul>
   *   <li>Variable bitrate: {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_VBR}.
   *   <li>Constant bitrate: {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CBR}.
   * </ul>
   */
  @SuppressLint("InlinedApi")
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target(TYPE_USE)
  @IntDef({
    BITRATE_MODE_VBR,
    BITRATE_MODE_CBR,
  })
  public @interface BitrateMode {}

  /** Builds {@link VideoEncoderSettings} instances. */
  public static final class Builder {
    private int bitrate;
    private @BitrateMode int bitrateMode;
    private int profile;
    private int level;
    private float iFrameIntervalSeconds;
    private int operatingRate;
    private int priority;
    private boolean enableHighQualityTargeting;

    /** Creates a new instance. */
    public Builder() {
      this.bitrate = NO_VALUE;
      this.bitrateMode = BITRATE_MODE_VBR;
      this.profile = NO_VALUE;
      this.level = NO_VALUE;
      this.iFrameIntervalSeconds = DEFAULT_I_FRAME_INTERVAL_SECONDS;
      this.operatingRate = NO_VALUE;
      this.priority = NO_VALUE;
    }

    private Builder(VideoEncoderSettings videoEncoderSettings) {
      this.bitrate = videoEncoderSettings.bitrate;
      this.bitrateMode = videoEncoderSettings.bitrateMode;
      this.profile = videoEncoderSettings.profile;
      this.level = videoEncoderSettings.level;
      this.iFrameIntervalSeconds = videoEncoderSettings.iFrameIntervalSeconds;
      this.operatingRate = videoEncoderSettings.operatingRate;
      this.priority = videoEncoderSettings.priority;
      this.enableHighQualityTargeting = videoEncoderSettings.enableHighQualityTargeting;
    }

    /**
     * Sets {@link VideoEncoderSettings#bitrate}. The default value is {@link #NO_VALUE}.
     *
     * <p>Can not be set if enabling {@link #experimentalSetEnableHighQualityTargeting(boolean)}.
     *
     * @param bitrate The {@link VideoEncoderSettings#bitrate} in bits per second.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setBitrate(int bitrate) {
      this.bitrate = bitrate;
      return this;
    }

    /**
     * Sets {@link VideoEncoderSettings#bitrateMode}. The default value is {@code
     * MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR}.
     *
     * <p>Value must be in {@link BitrateMode}.
     *
     * @param bitrateMode The {@link VideoEncoderSettings#bitrateMode}.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setBitrateMode(@BitrateMode int bitrateMode) {
      checkArgument(bitrateMode == BITRATE_MODE_VBR || bitrateMode == BITRATE_MODE_CBR);
      this.bitrateMode = bitrateMode;
      return this;
    }

    /**
     * Sets {@link VideoEncoderSettings#profile} and {@link VideoEncoderSettings#level}. The default
     * values are both {@link #NO_VALUE}.
     *
     * <p>The value must be one of the values defined in {@link MediaCodecInfo.CodecProfileLevel},
     * or {@link #NO_VALUE}.
     *
     * <p>Profile and level settings will be ignored when using {@link DefaultEncoderFactory} and
     * encoding to H264.
     *
     * @param encodingProfile The {@link VideoEncoderSettings#profile}.
     * @param encodingLevel The {@link VideoEncoderSettings#level}.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setEncodingProfileLevel(int encodingProfile, int encodingLevel) {
      this.profile = encodingProfile;
      this.level = encodingLevel;
      return this;
    }

    /**
     * Sets {@link VideoEncoderSettings#iFrameIntervalSeconds}. The default value is {@link
     * #DEFAULT_I_FRAME_INTERVAL_SECONDS}.
     *
     * @param iFrameIntervalSeconds The {@link VideoEncoderSettings#iFrameIntervalSeconds}.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setiFrameIntervalSeconds(float iFrameIntervalSeconds) {
      this.iFrameIntervalSeconds = iFrameIntervalSeconds;
      return this;
    }

    /**
     * Sets encoding operating rate and priority. The default values are {@link #NO_VALUE}, which is
     * treated as configuring the encoder for maximum throughput.
     *
     * @param operatingRate The {@link MediaFormat#KEY_OPERATING_RATE operating rate} in frames per
     *     second.
     * @param priority The {@link MediaFormat#KEY_PRIORITY priority}.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    @VisibleForTesting
    public Builder setEncoderPerformanceParameters(int operatingRate, int priority) {
      this.operatingRate = operatingRate;
      this.priority = priority;
      return this;
    }

    /**
     * Sets whether to enable automatic adjustment of the bitrate to target a high quality encoding.
     *
     * <p>This method is experimental and may be removed or changed without warning.
     *
     * <p>Default value is {@code false}.
     *
     * <p>Requires {@link android.media.MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_VBR}.
     *
     * <p>Can not be enabled alongside setting a custom bitrate with {@link #setBitrate(int)}.
     */
    @CanIgnoreReturnValue
    public Builder experimentalSetEnableHighQualityTargeting(boolean enableHighQualityTargeting) {
      this.enableHighQualityTargeting = enableHighQualityTargeting;
      return this;
    }

    /** Builds the instance. */
    public VideoEncoderSettings build() {
      checkState(
          !enableHighQualityTargeting || bitrate == NO_VALUE,
          "Bitrate can not be set if enabling high quality targeting.");
      checkState(
          !enableHighQualityTargeting || bitrateMode == BITRATE_MODE_VBR,
          "Bitrate mode must be VBR if enabling high quality targeting.");
      return new VideoEncoderSettings(
          bitrate,
          bitrateMode,
          profile,
          level,
          iFrameIntervalSeconds,
          operatingRate,
          priority,
          enableHighQualityTargeting);
    }
  }

  /** The encoding bitrate in bits per second. */
  public final int bitrate;
  /** One of {@linkplain BitrateMode}. */
  public final @BitrateMode int bitrateMode;
  /** The encoding profile. */
  public final int profile;
  /** The encoding level. */
  public final int level;
  /** The encoding I-Frame interval in seconds. */
  public final float iFrameIntervalSeconds;
  /** The encoder {@link MediaFormat#KEY_OPERATING_RATE operating rate} in frames per second. */
  public final int operatingRate;
  /** The encoder {@link MediaFormat#KEY_PRIORITY priority}. */
  public final int priority;
  /** Whether the encoder should automatically set the bitrate to target a high quality encoding. */
  public final boolean enableHighQualityTargeting;

  private VideoEncoderSettings(
      int bitrate,
      int bitrateMode,
      int profile,
      int level,
      float iFrameIntervalSeconds,
      int operatingRate,
      int priority,
      boolean enableHighQualityTargeting) {
    this.bitrate = bitrate;
    this.bitrateMode = bitrateMode;
    this.profile = profile;
    this.level = level;
    this.iFrameIntervalSeconds = iFrameIntervalSeconds;
    this.operatingRate = operatingRate;
    this.priority = priority;
    this.enableHighQualityTargeting = enableHighQualityTargeting;
  }

  /**
   * Returns a {@link VideoEncoderSettings.Builder} initialized with the values of this instance.
   */
  public Builder buildUpon() {
    return new Builder(this);
  }

  @Override
  public boolean equals(@Nullable Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof VideoEncoderSettings)) {
      return false;
    }
    VideoEncoderSettings that = (VideoEncoderSettings) o;
    return bitrate == that.bitrate
        && bitrateMode == that.bitrateMode
        && profile == that.profile
        && level == that.level
        && iFrameIntervalSeconds == that.iFrameIntervalSeconds
        && operatingRate == that.operatingRate
        && priority == that.priority
        && enableHighQualityTargeting == that.enableHighQualityTargeting;
  }

  @Override
  public int hashCode() {
    int result = 7;
    result = 31 * result + bitrate;
    result = 31 * result + bitrateMode;
    result = 31 * result + profile;
    result = 31 * result + level;
    result = 31 * result + Float.floatToIntBits(iFrameIntervalSeconds);
    result = 31 * result + operatingRate;
    result = 31 * result + priority;
    result = 31 * result + (enableHighQualityTargeting ? 1 : 0);
    return result;
  }
}