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 static org.junit.Assert.assertEquals; 14 import static org.junit.Assert.fail; 15 16 import android.annotation.SuppressLint; 17 import android.graphics.Point; 18 import android.support.test.InstrumentationRegistry; 19 import android.support.test.annotation.UiThreadTest; 20 import android.support.test.rule.UiThreadTestRule; 21 import android.view.View.MeasureSpec; 22 import androidx.test.filters.MediumTest; 23 import java.nio.ByteBuffer; 24 import java.util.Arrays; 25 import java.util.List; 26 import org.junit.Rule; 27 import org.junit.Test; 28 29 public class SurfaceViewRendererOnMeasureTest { 30 @Rule public final UiThreadTestRule uiThreadRule = new UiThreadTestRule(); 31 32 /** 33 * List with all possible scaling types. 34 */ 35 private static final List<RendererCommon.ScalingType> scalingTypes = Arrays.asList( 36 RendererCommon.ScalingType.SCALE_ASPECT_FIT, RendererCommon.ScalingType.SCALE_ASPECT_FILL, 37 RendererCommon.ScalingType.SCALE_ASPECT_BALANCED); 38 39 /** 40 * List with MeasureSpec modes. 41 */ 42 private static final List<Integer> measureSpecModes = 43 Arrays.asList(MeasureSpec.EXACTLY, MeasureSpec.AT_MOST); 44 45 /** 46 * Returns a dummy YUV frame. 47 */ createFrame(int width, int height, int rotationDegree)48 static VideoFrame createFrame(int width, int height, int rotationDegree) { 49 final int[] yuvStrides = new int[] {width, (width + 1) / 2, (width + 1) / 2}; 50 final int[] yuvHeights = new int[] {height, (height + 1) / 2, (height + 1) / 2}; 51 final ByteBuffer[] yuvPlanes = new ByteBuffer[3]; 52 for (int i = 0; i < 3; ++i) { 53 yuvPlanes[i] = ByteBuffer.allocateDirect(yuvStrides[i] * yuvHeights[i]); 54 } 55 final VideoFrame.I420Buffer buffer = 56 JavaI420Buffer.wrap(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1], yuvStrides[1], 57 yuvPlanes[2], yuvStrides[2], null /* releaseCallback */); 58 return new VideoFrame(buffer, rotationDegree, 0 /* timestamp */); 59 } 60 61 /** 62 * Assert onMeasure() with given parameters will result in expected measured size. 63 */ 64 @SuppressLint("WrongCall") assertMeasuredSize(SurfaceViewRenderer surfaceViewRenderer, RendererCommon.ScalingType scalingType, String frameDimensions, int expectedWidth, int expectedHeight, int widthSpec, int heightSpec)65 private static void assertMeasuredSize(SurfaceViewRenderer surfaceViewRenderer, 66 RendererCommon.ScalingType scalingType, String frameDimensions, int expectedWidth, 67 int expectedHeight, int widthSpec, int heightSpec) { 68 surfaceViewRenderer.setScalingType(scalingType); 69 surfaceViewRenderer.onMeasure(widthSpec, heightSpec); 70 final int measuredWidth = surfaceViewRenderer.getMeasuredWidth(); 71 final int measuredHeight = surfaceViewRenderer.getMeasuredHeight(); 72 if (measuredWidth != expectedWidth || measuredHeight != expectedHeight) { 73 fail("onMeasure(" + MeasureSpec.toString(widthSpec) + ", " + MeasureSpec.toString(heightSpec) 74 + ")" 75 + " with scaling type " + scalingType + " and frame: " + frameDimensions 76 + " expected measured size " + expectedWidth + "x" + expectedHeight + ", but was " 77 + measuredWidth + "x" + measuredHeight); 78 } 79 } 80 81 /** 82 * Test how SurfaceViewRenderer.onMeasure() behaves when no frame has been delivered. 83 */ 84 @Test 85 @UiThreadTest 86 @MediumTest testNoFrame()87 public void testNoFrame() { 88 final SurfaceViewRenderer surfaceViewRenderer = 89 new SurfaceViewRenderer(InstrumentationRegistry.getContext()); 90 final String frameDimensions = "null"; 91 92 // Test behaviour before SurfaceViewRenderer.init() is called. 93 for (RendererCommon.ScalingType scalingType : scalingTypes) { 94 for (int measureSpecMode : measureSpecModes) { 95 final int zeroMeasureSize = MeasureSpec.makeMeasureSpec(0, measureSpecMode); 96 assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 0, 0, zeroMeasureSize, 97 zeroMeasureSize); 98 assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 1280, 720, 99 MeasureSpec.makeMeasureSpec(1280, measureSpecMode), 100 MeasureSpec.makeMeasureSpec(720, measureSpecMode)); 101 } 102 } 103 104 // Test behaviour after SurfaceViewRenderer.init() is called, but still no frame. 105 surfaceViewRenderer.init((EglBase.Context) null, null); 106 for (RendererCommon.ScalingType scalingType : scalingTypes) { 107 for (int measureSpecMode : measureSpecModes) { 108 final int zeroMeasureSize = MeasureSpec.makeMeasureSpec(0, measureSpecMode); 109 assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 0, 0, zeroMeasureSize, 110 zeroMeasureSize); 111 assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 1280, 720, 112 MeasureSpec.makeMeasureSpec(1280, measureSpecMode), 113 MeasureSpec.makeMeasureSpec(720, measureSpecMode)); 114 } 115 } 116 117 surfaceViewRenderer.release(); 118 } 119 120 /** 121 * Test how SurfaceViewRenderer.onMeasure() behaves with a 1280x720 frame. 122 */ 123 @Test 124 @UiThreadTest 125 @MediumTest testFrame1280x720()126 public void testFrame1280x720() throws InterruptedException { 127 final SurfaceViewRenderer surfaceViewRenderer = 128 new SurfaceViewRenderer(InstrumentationRegistry.getContext()); 129 /** 130 * Mock renderer events with blocking wait functionality for frame size changes. 131 */ 132 class MockRendererEvents implements RendererCommon.RendererEvents { 133 private int frameWidth; 134 private int frameHeight; 135 private int rotation; 136 137 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression. 138 @SuppressWarnings("NoSynchronizedMethodCheck") 139 public synchronized void waitForFrameSize(int frameWidth, int frameHeight, int rotation) 140 throws InterruptedException { 141 while (this.frameWidth != frameWidth || this.frameHeight != frameHeight 142 || this.rotation != rotation) { 143 wait(); 144 } 145 } 146 147 @Override 148 public void onFirstFrameRendered() {} 149 150 @Override 151 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression. 152 @SuppressWarnings("NoSynchronizedMethodCheck") 153 public synchronized void onFrameResolutionChanged( 154 int frameWidth, int frameHeight, int rotation) { 155 this.frameWidth = frameWidth; 156 this.frameHeight = frameHeight; 157 this.rotation = rotation; 158 notifyAll(); 159 } 160 } 161 final MockRendererEvents rendererEvents = new MockRendererEvents(); 162 surfaceViewRenderer.init((EglBase.Context) null, rendererEvents); 163 164 // Test different rotation degress, but same rotated size. 165 for (int rotationDegree : new int[] {0, 90, 180, 270}) { 166 final int rotatedWidth = 1280; 167 final int rotatedHeight = 720; 168 final int unrotatedWidth = (rotationDegree % 180 == 0 ? rotatedWidth : rotatedHeight); 169 final int unrotatedHeight = (rotationDegree % 180 == 0 ? rotatedHeight : rotatedWidth); 170 final VideoFrame frame = createFrame(unrotatedWidth, unrotatedHeight, rotationDegree); 171 assertEquals(rotatedWidth, frame.getRotatedWidth()); 172 assertEquals(rotatedHeight, frame.getRotatedHeight()); 173 final String frameDimensions = 174 unrotatedWidth + "x" + unrotatedHeight + " with rotation " + rotationDegree; 175 surfaceViewRenderer.onFrame(frame); 176 frame.release(); 177 rendererEvents.waitForFrameSize(unrotatedWidth, unrotatedHeight, rotationDegree); 178 179 // Test forcing to zero size. 180 for (RendererCommon.ScalingType scalingType : scalingTypes) { 181 for (int measureSpecMode : measureSpecModes) { 182 final int zeroMeasureSize = MeasureSpec.makeMeasureSpec(0, measureSpecMode); 183 assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 0, 0, 184 zeroMeasureSize, zeroMeasureSize); 185 } 186 } 187 188 // Test perfect fit. 189 for (RendererCommon.ScalingType scalingType : scalingTypes) { 190 for (int measureSpecMode : measureSpecModes) { 191 assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, rotatedWidth, 192 rotatedHeight, MeasureSpec.makeMeasureSpec(rotatedWidth, measureSpecMode), 193 MeasureSpec.makeMeasureSpec(rotatedHeight, measureSpecMode)); 194 } 195 } 196 197 // Force spec size with different aspect ratio than frame aspect ratio. 198 for (RendererCommon.ScalingType scalingType : scalingTypes) { 199 assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, 720, 1280, 200 MeasureSpec.makeMeasureSpec(720, MeasureSpec.EXACTLY), 201 MeasureSpec.makeMeasureSpec(1280, MeasureSpec.EXACTLY)); 202 } 203 204 final float videoAspectRatio = (float) rotatedWidth / rotatedHeight; 205 { 206 // Relax both width and height constraints. 207 final int widthSpec = MeasureSpec.makeMeasureSpec(720, MeasureSpec.AT_MOST); 208 final int heightSpec = MeasureSpec.makeMeasureSpec(1280, MeasureSpec.AT_MOST); 209 for (RendererCommon.ScalingType scalingType : scalingTypes) { 210 final Point expectedSize = 211 RendererCommon.getDisplaySize(scalingType, videoAspectRatio, 720, 1280); 212 assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, expectedSize.x, 213 expectedSize.y, widthSpec, heightSpec); 214 } 215 } 216 { 217 // Force width to 720, but relax height constraint. This will give the same result as 218 // above, because width is already the limiting factor and will be maxed out. 219 final int widthSpec = MeasureSpec.makeMeasureSpec(720, MeasureSpec.EXACTLY); 220 final int heightSpec = MeasureSpec.makeMeasureSpec(1280, MeasureSpec.AT_MOST); 221 for (RendererCommon.ScalingType scalingType : scalingTypes) { 222 final Point expectedSize = 223 RendererCommon.getDisplaySize(scalingType, videoAspectRatio, 720, 1280); 224 assertMeasuredSize(surfaceViewRenderer, scalingType, frameDimensions, expectedSize.x, 225 expectedSize.y, widthSpec, heightSpec); 226 } 227 } 228 { 229 // Force height, but relax width constraint. This will force a bad layout size. 230 final int widthSpec = MeasureSpec.makeMeasureSpec(720, MeasureSpec.AT_MOST); 231 final int heightSpec = MeasureSpec.makeMeasureSpec(1280, MeasureSpec.EXACTLY); 232 for (RendererCommon.ScalingType scalingType : scalingTypes) { 233 assertMeasuredSize( 234 surfaceViewRenderer, scalingType, frameDimensions, 720, 1280, widthSpec, heightSpec); 235 } 236 } 237 } 238 239 surfaceViewRenderer.release(); 240 } 241 } 242