1 /* 2 * Copyright 2015 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.webrtc; 12 13 import android.graphics.Point; 14 import android.opengl.Matrix; 15 import android.view.View; 16 17 /** 18 * Static helper functions for renderer implementations. 19 */ 20 public class RendererCommon { 21 /** Interface for reporting rendering events. */ 22 public static interface RendererEvents { 23 /** 24 * Callback fired once first frame is rendered. 25 */ onFirstFrameRendered()26 public void onFirstFrameRendered(); 27 28 /** 29 * Callback fired when rendered frame resolution or rotation has changed. 30 */ onFrameResolutionChanged(int videoWidth, int videoHeight, int rotation)31 public void onFrameResolutionChanged(int videoWidth, int videoHeight, int rotation); 32 } 33 34 /** 35 * Interface for rendering frames on an EGLSurface with specified viewport location. Rotation, 36 * mirror, and cropping is specified using a 4x4 texture coordinate transform matrix. The frame 37 * input can either be an OES texture, RGB texture, or YUV textures in I420 format. The function 38 * release() must be called manually to free the resources held by this object. 39 */ 40 public static interface GlDrawer { 41 /** 42 * Functions for drawing frames with different sources. The rendering surface target is 43 * implied by the current EGL context of the calling thread and requires no explicit argument. 44 * The coordinates specify the viewport location on the surface target. 45 */ drawOes(int oesTextureId, float[] texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, int viewportWidth, int viewportHeight)46 void drawOes(int oesTextureId, float[] texMatrix, int frameWidth, int frameHeight, 47 int viewportX, int viewportY, int viewportWidth, int viewportHeight); drawRgb(int textureId, float[] texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, int viewportWidth, int viewportHeight)48 void drawRgb(int textureId, float[] texMatrix, int frameWidth, int frameHeight, int viewportX, 49 int viewportY, int viewportWidth, int viewportHeight); drawYuv(int[] yuvTextures, float[] texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, int viewportWidth, int viewportHeight)50 void drawYuv(int[] yuvTextures, float[] texMatrix, int frameWidth, int frameHeight, 51 int viewportX, int viewportY, int viewportWidth, int viewportHeight); 52 53 /** 54 * Release all GL resources. This needs to be done manually, otherwise resources may leak. 55 */ release()56 void release(); 57 } 58 59 /** 60 * Helper class for determining layout size based on layout requirements, scaling type, and video 61 * aspect ratio. 62 */ 63 public static class VideoLayoutMeasure { 64 // The scaling type determines how the video will fill the allowed layout area in measure(). It 65 // can be specified separately for the case when video has matched orientation with layout size 66 // and when there is an orientation mismatch. 67 private float visibleFractionMatchOrientation = 68 convertScalingTypeToVisibleFraction(ScalingType.SCALE_ASPECT_BALANCED); 69 private float visibleFractionMismatchOrientation = 70 convertScalingTypeToVisibleFraction(ScalingType.SCALE_ASPECT_BALANCED); 71 setScalingType(ScalingType scalingType)72 public void setScalingType(ScalingType scalingType) { 73 setScalingType(/* scalingTypeMatchOrientation= */ scalingType, 74 /* scalingTypeMismatchOrientation= */ scalingType); 75 } 76 setScalingType( ScalingType scalingTypeMatchOrientation, ScalingType scalingTypeMismatchOrientation)77 public void setScalingType( 78 ScalingType scalingTypeMatchOrientation, ScalingType scalingTypeMismatchOrientation) { 79 this.visibleFractionMatchOrientation = 80 convertScalingTypeToVisibleFraction(scalingTypeMatchOrientation); 81 this.visibleFractionMismatchOrientation = 82 convertScalingTypeToVisibleFraction(scalingTypeMismatchOrientation); 83 } 84 setVisibleFraction( float visibleFractionMatchOrientation, float visibleFractionMismatchOrientation)85 public void setVisibleFraction( 86 float visibleFractionMatchOrientation, float visibleFractionMismatchOrientation) { 87 this.visibleFractionMatchOrientation = visibleFractionMatchOrientation; 88 this.visibleFractionMismatchOrientation = visibleFractionMismatchOrientation; 89 } 90 measure(int widthSpec, int heightSpec, int frameWidth, int frameHeight)91 public Point measure(int widthSpec, int heightSpec, int frameWidth, int frameHeight) { 92 // Calculate max allowed layout size. 93 final int maxWidth = View.getDefaultSize(Integer.MAX_VALUE, widthSpec); 94 final int maxHeight = View.getDefaultSize(Integer.MAX_VALUE, heightSpec); 95 if (frameWidth == 0 || frameHeight == 0 || maxWidth == 0 || maxHeight == 0) { 96 return new Point(maxWidth, maxHeight); 97 } 98 // Calculate desired display size based on scaling type, video aspect ratio, 99 // and maximum layout size. 100 final float frameAspect = frameWidth / (float) frameHeight; 101 final float displayAspect = maxWidth / (float) maxHeight; 102 final float visibleFraction = (frameAspect > 1.0f) == (displayAspect > 1.0f) 103 ? visibleFractionMatchOrientation 104 : visibleFractionMismatchOrientation; 105 final Point layoutSize = getDisplaySize(visibleFraction, frameAspect, maxWidth, maxHeight); 106 107 // If the measure specification is forcing a specific size - yield. 108 if (View.MeasureSpec.getMode(widthSpec) == View.MeasureSpec.EXACTLY) { 109 layoutSize.x = maxWidth; 110 } 111 if (View.MeasureSpec.getMode(heightSpec) == View.MeasureSpec.EXACTLY) { 112 layoutSize.y = maxHeight; 113 } 114 return layoutSize; 115 } 116 } 117 118 // Types of video scaling: 119 // SCALE_ASPECT_FIT - video frame is scaled to fit the size of the view by 120 // maintaining the aspect ratio (black borders may be displayed). 121 // SCALE_ASPECT_FILL - video frame is scaled to fill the size of the view by 122 // maintaining the aspect ratio. Some portion of the video frame may be 123 // clipped. 124 // SCALE_ASPECT_BALANCED - Compromise between FIT and FILL. Video frame will fill as much as 125 // possible of the view while maintaining aspect ratio, under the constraint that at least 126 // |BALANCED_VISIBLE_FRACTION| of the frame content will be shown. 127 public static enum ScalingType { SCALE_ASPECT_FIT, SCALE_ASPECT_FILL, SCALE_ASPECT_BALANCED } 128 // The minimum fraction of the frame content that will be shown for |SCALE_ASPECT_BALANCED|. 129 // This limits excessive cropping when adjusting display size. 130 private static float BALANCED_VISIBLE_FRACTION = 0.5625f; 131 132 /** 133 * Returns layout transformation matrix that applies an optional mirror effect and compensates 134 * for video vs display aspect ratio. 135 */ getLayoutMatrix( boolean mirror, float videoAspectRatio, float displayAspectRatio)136 public static float[] getLayoutMatrix( 137 boolean mirror, float videoAspectRatio, float displayAspectRatio) { 138 float scaleX = 1; 139 float scaleY = 1; 140 // Scale X or Y dimension so that video and display size have same aspect ratio. 141 if (displayAspectRatio > videoAspectRatio) { 142 scaleY = videoAspectRatio / displayAspectRatio; 143 } else { 144 scaleX = displayAspectRatio / videoAspectRatio; 145 } 146 // Apply optional horizontal flip. 147 if (mirror) { 148 scaleX *= -1; 149 } 150 final float matrix[] = new float[16]; 151 Matrix.setIdentityM(matrix, 0); 152 Matrix.scaleM(matrix, 0, scaleX, scaleY, 1); 153 adjustOrigin(matrix); 154 return matrix; 155 } 156 157 /** Converts a float[16] matrix array to android.graphics.Matrix. */ convertMatrixToAndroidGraphicsMatrix(float[] matrix4x4)158 public static android.graphics.Matrix convertMatrixToAndroidGraphicsMatrix(float[] matrix4x4) { 159 // clang-format off 160 float[] values = { 161 matrix4x4[0 * 4 + 0], matrix4x4[1 * 4 + 0], matrix4x4[3 * 4 + 0], 162 matrix4x4[0 * 4 + 1], matrix4x4[1 * 4 + 1], matrix4x4[3 * 4 + 1], 163 matrix4x4[0 * 4 + 3], matrix4x4[1 * 4 + 3], matrix4x4[3 * 4 + 3], 164 }; 165 // clang-format on 166 167 android.graphics.Matrix matrix = new android.graphics.Matrix(); 168 matrix.setValues(values); 169 return matrix; 170 } 171 172 /** Converts android.graphics.Matrix to a float[16] matrix array. */ convertMatrixFromAndroidGraphicsMatrix(android.graphics.Matrix matrix)173 public static float[] convertMatrixFromAndroidGraphicsMatrix(android.graphics.Matrix matrix) { 174 float[] values = new float[9]; 175 matrix.getValues(values); 176 177 // The android.graphics.Matrix looks like this: 178 // [x1 y1 w1] 179 // [x2 y2 w2] 180 // [x3 y3 w3] 181 // We want to contruct a matrix that looks like this: 182 // [x1 y1 0 w1] 183 // [x2 y2 0 w2] 184 // [ 0 0 1 0] 185 // [x3 y3 0 w3] 186 // Since it is stored in column-major order, it looks like this: 187 // [x1 x2 0 x3 188 // y1 y2 0 y3 189 // 0 0 1 0 190 // w1 w2 0 w3] 191 // clang-format off 192 float[] matrix4x4 = { 193 values[0 * 3 + 0], values[1 * 3 + 0], 0, values[2 * 3 + 0], 194 values[0 * 3 + 1], values[1 * 3 + 1], 0, values[2 * 3 + 1], 195 0, 0, 1, 0, 196 values[0 * 3 + 2], values[1 * 3 + 2], 0, values[2 * 3 + 2], 197 }; 198 // clang-format on 199 return matrix4x4; 200 } 201 202 /** 203 * Calculate display size based on scaling type, video aspect ratio, and maximum display size. 204 */ getDisplaySize( ScalingType scalingType, float videoAspectRatio, int maxDisplayWidth, int maxDisplayHeight)205 public static Point getDisplaySize( 206 ScalingType scalingType, float videoAspectRatio, int maxDisplayWidth, int maxDisplayHeight) { 207 return getDisplaySize(convertScalingTypeToVisibleFraction(scalingType), videoAspectRatio, 208 maxDisplayWidth, maxDisplayHeight); 209 } 210 211 /** 212 * Move |matrix| transformation origin to (0.5, 0.5). This is the origin for texture coordinates 213 * that are in the range 0 to 1. 214 */ adjustOrigin(float[] matrix)215 private static void adjustOrigin(float[] matrix) { 216 // Note that OpenGL is using column-major order. 217 // Pre translate with -0.5 to move coordinates to range [-0.5, 0.5]. 218 matrix[12] -= 0.5f * (matrix[0] + matrix[4]); 219 matrix[13] -= 0.5f * (matrix[1] + matrix[5]); 220 // Post translate with 0.5 to move coordinates to range [0, 1]. 221 matrix[12] += 0.5f; 222 matrix[13] += 0.5f; 223 } 224 225 /** 226 * Each scaling type has a one-to-one correspondence to a numeric minimum fraction of the video 227 * that must remain visible. 228 */ convertScalingTypeToVisibleFraction(ScalingType scalingType)229 private static float convertScalingTypeToVisibleFraction(ScalingType scalingType) { 230 switch (scalingType) { 231 case SCALE_ASPECT_FIT: 232 return 1.0f; 233 case SCALE_ASPECT_FILL: 234 return 0.0f; 235 case SCALE_ASPECT_BALANCED: 236 return BALANCED_VISIBLE_FRACTION; 237 default: 238 throw new IllegalArgumentException(); 239 } 240 } 241 242 /** 243 * Calculate display size based on minimum fraction of the video that must remain visible, 244 * video aspect ratio, and maximum display size. 245 */ getDisplaySize( float minVisibleFraction, float videoAspectRatio, int maxDisplayWidth, int maxDisplayHeight)246 public static Point getDisplaySize( 247 float minVisibleFraction, float videoAspectRatio, int maxDisplayWidth, int maxDisplayHeight) { 248 // If there is no constraint on the amount of cropping, fill the allowed display area. 249 if (minVisibleFraction == 0 || videoAspectRatio == 0) { 250 return new Point(maxDisplayWidth, maxDisplayHeight); 251 } 252 // Each dimension is constrained on max display size and how much we are allowed to crop. 253 final int width = Math.min( 254 maxDisplayWidth, Math.round(maxDisplayHeight / minVisibleFraction * videoAspectRatio)); 255 final int height = Math.min( 256 maxDisplayHeight, Math.round(maxDisplayWidth / minVisibleFraction / videoAspectRatio)); 257 return new Point(width, height); 258 } 259 } 260