• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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