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