1 /* 2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.transformer; 17 18 import static com.google.android.exoplayer2.util.Assertions.checkState; 19 import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; 20 21 import android.content.Context; 22 import android.graphics.Matrix; 23 import android.util.Size; 24 import androidx.annotation.VisibleForTesting; 25 import com.google.android.exoplayer2.C; 26 import com.google.android.exoplayer2.Format; 27 import com.google.android.exoplayer2.util.GlUtil; 28 import java.io.IOException; 29 import org.checkerframework.checker.nullness.qual.EnsuresNonNull; 30 import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 31 32 /** Controls how a frame is viewed, by changing resolution. */ 33 // TODO(b/213190310): Implement crop, aspect ratio changes, etc. 34 public final class PresentationFrameProcessor implements GlFrameProcessor { 35 36 /** A builder for {@link PresentationFrameProcessor} instances. */ 37 public static final class Builder { 38 39 // Mandatory field. 40 private final Context context; 41 42 // Optional field. 43 private int outputHeight; 44 45 /** 46 * Creates a builder with default values. 47 * 48 * @param context The {@link Context}. 49 */ Builder(Context context)50 public Builder(Context context) { 51 this.context = context; 52 outputHeight = C.LENGTH_UNSET; 53 } 54 55 /** 56 * Sets the output resolution using the output height. 57 * 58 * <p>The default value {@link C#LENGTH_UNSET} corresponds to using the same height as the 59 * input. Output width of the displayed frame will scale to preserve the frame's aspect ratio 60 * after other transformations. 61 * 62 * <p>For example, a 1920x1440 frame can be scaled to 640x480 by calling setResolution(480). 63 * 64 * @param outputHeight The output height of the displayed frame, in pixels. 65 * @return This builder. 66 */ setResolution(int outputHeight)67 public Builder setResolution(int outputHeight) { 68 this.outputHeight = outputHeight; 69 return this; 70 } 71 build()72 public PresentationFrameProcessor build() { 73 return new PresentationFrameProcessor(context, outputHeight); 74 } 75 } 76 77 static { 78 GlUtil.glAssertionsEnabled = true; 79 } 80 81 private final Context context; 82 private final int requestedHeight; 83 84 private @MonotonicNonNull Size outputSize; 85 private int outputRotationDegrees; 86 private @MonotonicNonNull Matrix transformationMatrix; 87 private @MonotonicNonNull AdvancedFrameProcessor advancedFrameProcessor; 88 89 /** 90 * Creates a new instance. 91 * 92 * @param context The {@link Context}. 93 * @param requestedHeight The height of the output frame, in pixels. 94 */ PresentationFrameProcessor(Context context, int requestedHeight)95 private PresentationFrameProcessor(Context context, int requestedHeight) { 96 this.context = context; 97 this.requestedHeight = requestedHeight; 98 99 outputRotationDegrees = C.LENGTH_UNSET; 100 } 101 102 @Override initialize(int inputTexId, int inputWidth, int inputHeight)103 public void initialize(int inputTexId, int inputWidth, int inputHeight) throws IOException { 104 configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); 105 advancedFrameProcessor = new AdvancedFrameProcessor(context, transformationMatrix); 106 advancedFrameProcessor.initialize(inputTexId, inputWidth, inputHeight); 107 } 108 109 @Override getOutputSize()110 public Size getOutputSize() { 111 return checkStateNotNull(outputSize); 112 } 113 114 /** 115 * Returns {@link Format#rotationDegrees} for the output frame. 116 * 117 * <p>Return values may be {@code 0} or {@code 90} degrees. 118 * 119 * <p>The frame processor must be {@linkplain #initialize(int,int,int) initialized}. 120 */ getOutputRotationDegrees()121 public int getOutputRotationDegrees() { 122 checkState(outputRotationDegrees != C.LENGTH_UNSET); 123 return outputRotationDegrees; 124 } 125 126 @Override updateProgramAndDraw(long presentationTimeUs)127 public void updateProgramAndDraw(long presentationTimeUs) { 128 checkStateNotNull(advancedFrameProcessor).updateProgramAndDraw(presentationTimeUs); 129 } 130 131 @Override release()132 public void release() { 133 if (advancedFrameProcessor != null) { 134 advancedFrameProcessor.release(); 135 } 136 } 137 138 @EnsuresNonNull("transformationMatrix") 139 @VisibleForTesting // Allows roboletric testing of output size calculation without OpenGL. configureOutputSizeAndTransformationMatrix(int inputWidth, int inputHeight)140 /* package */ void configureOutputSizeAndTransformationMatrix(int inputWidth, int inputHeight) { 141 transformationMatrix = new Matrix(); 142 int displayWidth = inputWidth; 143 int displayHeight = inputHeight; 144 // Scale width and height to desired requestedHeight, preserving aspect ratio. 145 if (requestedHeight != C.LENGTH_UNSET && requestedHeight != displayHeight) { 146 displayWidth = Math.round((float) requestedHeight * displayWidth / displayHeight); 147 displayHeight = requestedHeight; 148 } 149 // Encoders commonly support higher maximum widths than maximum heights. Rotate the decoded 150 // frame before encoding, so the encoded frame's width >= height, and set 151 // outputRotationDegrees to ensure the frame is displayed in the correct orientation. 152 if (displayHeight > displayWidth) { 153 outputRotationDegrees = 90; 154 // TODO(b/201293185): After fragment shader transformations are implemented, put 155 // postRotate in a later GlFrameProcessor. 156 transformationMatrix.postRotate(outputRotationDegrees); 157 outputSize = new Size(displayHeight, displayWidth); 158 } else { 159 outputRotationDegrees = 0; 160 outputSize = new Size(displayWidth, displayHeight); 161 } 162 } 163 } 164