EncoderCompatibilityTransformation.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 androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull;

import android.graphics.Matrix;
import android.util.Size;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.util.GlUtil;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;

/**
 * Specifies a {@link Format#rotationDegrees} to apply to each frame for encoder compatibility, if
 * needed.
 *
 * <p>Encoders commonly support higher maximum widths than maximum heights. This may rotate the
 * decoded frame before encoding, so the encoded frame's width >= height, and set {@link
 * Format#rotationDegrees} to ensure the frame is displayed in the correct orientation.
 */
/* package */ class EncoderCompatibilityTransformation implements MatrixTransformation {
  // TODO(b/218488308): Allow reconfiguration of the output size, as encoders may not support the
  //  requested output resolution.

  static {
    GlUtil.glAssertionsEnabled = true;
  }

  private int outputRotationDegrees;
  private @MonotonicNonNull Matrix transformationMatrix;

  /** Creates a new instance. */
  public EncoderCompatibilityTransformation() {
    outputRotationDegrees = C.LENGTH_UNSET;
  }

  @Override
  public Size configure(int inputWidth, int inputHeight) {
    checkArgument(inputWidth > 0, "inputWidth must be positive");
    checkArgument(inputHeight > 0, "inputHeight must be positive");

    transformationMatrix = new Matrix();
    if (inputHeight > inputWidth) {
      outputRotationDegrees = 90;
      transformationMatrix.postRotate(outputRotationDegrees);
      return new Size(inputHeight, inputWidth);
    } else {
      outputRotationDegrees = 0;
      return new Size(inputWidth, inputHeight);
    }
  }

  @Override
  public Matrix getMatrix(long presentationTimeUs) {
    return checkStateNotNull(transformationMatrix, "configure must be called first");
  }

  /**
   * Returns {@link Format#rotationDegrees} for the output frame.
   *
   * <p>Return values may be {@code 0} or {@code 90} degrees.
   *
   * <p>Should only be called after {@linkplain #configure(int, int) configuration}.
   */
  public int getOutputRotationDegrees() {
    checkState(
        outputRotationDegrees != C.LENGTH_UNSET,
        "configure must be called before getOutputRotationDegrees");
    return outputRotationDegrees;
  }
}