blob: c39416c3e1482cef2b109120c56eb05cf7792300 [file] [log] [blame]
Magnus Jedvertc06a7162015-09-11 09:51:52 +02001/*
kjellanderb24317b2016-02-10 07:54:43 -08002 * Copyright 2015 The WebRTC project authors. All Rights Reserved.
Magnus Jedvertc06a7162015-09-11 09:51:52 +02003 *
kjellanderb24317b2016-02-10 07:54:43 -08004 * 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.
Magnus Jedvertc06a7162015-09-11 09:51:52 +02009 */
10
11package org.webrtc;
12
Magnus Jedvertc06a7162015-09-11 09:51:52 +020013import android.content.Context;
magjedb2d1c502015-11-11 03:06:42 -080014import android.content.res.Resources.NotFoundException;
Magnus Jedvertc06a7162015-09-11 09:51:52 +020015import android.graphics.Point;
Xiaolei Yu149533a2017-11-03 07:55:01 +080016import android.os.Looper;
Magnus Jedvertc06a7162015-09-11 09:51:52 +020017import android.util.AttributeSet;
Magnus Jedvertc06a7162015-09-11 09:51:52 +020018import android.view.SurfaceHolder;
19import android.view.SurfaceView;
20
21/**
Xiaolei Yu149533a2017-11-03 07:55:01 +080022 * Display the video stream on a SurfaceView.
Magnus Jedvertc06a7162015-09-11 09:51:52 +020023 */
Magnus Jedverte987f2b2018-04-23 16:14:47 +020024public class SurfaceViewRenderer extends SurfaceView
25 implements SurfaceHolder.Callback, VideoSink, RendererCommon.RendererEvents {
Magnus Jedvertc06a7162015-09-11 09:51:52 +020026 private static final String TAG = "SurfaceViewRenderer";
27
magjeddf494b02016-10-07 05:32:35 -070028 // Cached resource name.
29 private final String resourceName;
30 private final RendererCommon.VideoLayoutMeasure videoLayoutMeasure =
31 new RendererCommon.VideoLayoutMeasure();
Xiaolei Yu149533a2017-11-03 07:55:01 +080032 private final SurfaceEglRenderer eglRenderer;
Magnus Jedvertc06a7162015-09-11 09:51:52 +020033
magjeddf494b02016-10-07 05:32:35 -070034 // Callback for reporting renderer events. Read-only after initilization so no lock required.
35 private RendererCommon.RendererEvents rendererEvents;
Magnus Jedvertc06a7162015-09-11 09:51:52 +020036
Xiaolei Yu149533a2017-11-03 07:55:01 +080037 // Accessed only on the main thread.
Magnus Jedvert62b1c352016-10-05 15:56:06 +020038 private int rotatedFrameWidth;
39 private int rotatedFrameHeight;
sakalb1e6d5e2016-11-22 01:54:42 -080040 private boolean enableFixedSize;
41 private int surfaceWidth;
42 private int surfaceHeight;
43
Magnus Jedvertc06a7162015-09-11 09:51:52 +020044 /**
45 * Standard View constructor. In order to render something, you must first call init().
46 */
47 public SurfaceViewRenderer(Context context) {
48 super(context);
magjeddf494b02016-10-07 05:32:35 -070049 this.resourceName = getResourceName();
Xiaolei Yu149533a2017-11-03 07:55:01 +080050 eglRenderer = new SurfaceEglRenderer(resourceName);
Magnus Jedvert4382d802015-10-08 14:45:34 +020051 getHolder().addCallback(this);
Xiaolei Yu149533a2017-11-03 07:55:01 +080052 getHolder().addCallback(eglRenderer);
Magnus Jedvertc06a7162015-09-11 09:51:52 +020053 }
54
55 /**
56 * Standard View constructor. In order to render something, you must first call init().
57 */
58 public SurfaceViewRenderer(Context context, AttributeSet attrs) {
59 super(context, attrs);
magjeddf494b02016-10-07 05:32:35 -070060 this.resourceName = getResourceName();
Xiaolei Yu149533a2017-11-03 07:55:01 +080061 eglRenderer = new SurfaceEglRenderer(resourceName);
Magnus Jedvert4382d802015-10-08 14:45:34 +020062 getHolder().addCallback(this);
Xiaolei Yu149533a2017-11-03 07:55:01 +080063 getHolder().addCallback(eglRenderer);
Magnus Jedvertc06a7162015-09-11 09:51:52 +020064 }
65
66 /**
Magnus Jedvert4382d802015-10-08 14:45:34 +020067 * Initialize this class, sharing resources with |sharedContext|. It is allowed to call init() to
68 * reinitialize the renderer after a previous init()/release() cycle.
Magnus Jedvertc06a7162015-09-11 09:51:52 +020069 */
sakalb6760f92016-09-29 04:12:44 -070070 public void init(EglBase.Context sharedContext, RendererCommon.RendererEvents rendererEvents) {
Magnus Jedvert51254332015-12-15 16:22:29 +010071 init(sharedContext, rendererEvents, EglBase.CONFIG_PLAIN, new GlRectDrawer());
72 }
73
74 /**
75 * Initialize this class, sharing resources with |sharedContext|. The custom |drawer| will be used
76 * for drawing frames on the EGLSurface. This class is responsible for calling release() on
77 * |drawer|. It is allowed to call init() to reinitialize the renderer after a previous
78 * init()/release() cycle.
79 */
sakalb6760f92016-09-29 04:12:44 -070080 public void init(final EglBase.Context sharedContext,
81 RendererCommon.RendererEvents rendererEvents, final int[] configAttributes,
82 RendererCommon.GlDrawer drawer) {
magjeddf494b02016-10-07 05:32:35 -070083 ThreadUtils.checkIsOnMainThread();
84 this.rendererEvents = rendererEvents;
Xiaolei Yu149533a2017-11-03 07:55:01 +080085 rotatedFrameWidth = 0;
86 rotatedFrameHeight = 0;
87 eglRenderer.init(sharedContext, this /* rendererEvents */, configAttributes, drawer);
Magnus Jedvertc06a7162015-09-11 09:51:52 +020088 }
89
90 /**
Magnus Jedvertc19922c2015-09-21 14:32:07 +020091 * Block until any pending frame is returned and all GL resources released, even if an interrupt
92 * occurs. If an interrupt occurs during release(), the interrupt flag will be set. This function
93 * should be called before the Activity is destroyed and the EGLContext is still valid. If you
94 * don't call this function, the GL resources might leak.
Magnus Jedvertc06a7162015-09-11 09:51:52 +020095 */
96 public void release() {
magjeddf494b02016-10-07 05:32:35 -070097 eglRenderer.release();
Magnus Jedvertc06a7162015-09-11 09:51:52 +020098 }
99
sakal3a9bc172016-11-30 08:30:05 -0800100 /**
101 * Register a callback to be invoked when a new video frame has been received.
102 *
sakald1516522017-03-13 05:11:48 -0700103 * @param listener The callback to be invoked. The callback will be invoked on the render thread.
104 * It should be lightweight and must not call removeFrameListener.
sakal3a9bc172016-11-30 08:30:05 -0800105 * @param scale The scale of the Bitmap passed to the callback, or 0 if no Bitmap is
106 * required.
107 * @param drawer Custom drawer to use for this frame listener.
108 */
109 public void addFrameListener(
sakald1516522017-03-13 05:11:48 -0700110 EglRenderer.FrameListener listener, float scale, RendererCommon.GlDrawer drawerParam) {
111 eglRenderer.addFrameListener(listener, scale, drawerParam);
sakal3a9bc172016-11-30 08:30:05 -0800112 }
113
114 /**
115 * Register a callback to be invoked when a new video frame has been received. This version uses
116 * the drawer of the EglRenderer that was passed in init.
117 *
sakald1516522017-03-13 05:11:48 -0700118 * @param listener The callback to be invoked. The callback will be invoked on the render thread.
119 * It should be lightweight and must not call removeFrameListener.
sakal3a9bc172016-11-30 08:30:05 -0800120 * @param scale The scale of the Bitmap passed to the callback, or 0 if no Bitmap is
121 * required.
122 */
sakal4fe3b8d2016-11-23 06:18:20 -0800123 public void addFrameListener(EglRenderer.FrameListener listener, float scale) {
124 eglRenderer.addFrameListener(listener, scale);
125 }
126
127 public void removeFrameListener(EglRenderer.FrameListener listener) {
128 eglRenderer.removeFrameListener(listener);
129 }
130
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200131 /**
sakalb1e6d5e2016-11-22 01:54:42 -0800132 * Enables fixed size for the surface. This provides better performance but might be buggy on some
133 * devices. By default this is turned off.
134 */
135 public void setEnableHardwareScaler(boolean enabled) {
136 ThreadUtils.checkIsOnMainThread();
137 enableFixedSize = enabled;
138 updateSurfaceSize();
139 }
140
141 /**
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200142 * Set if the video stream should be mirrored or not.
143 */
144 public void setMirror(final boolean mirror) {
magjeddf494b02016-10-07 05:32:35 -0700145 eglRenderer.setMirror(mirror);
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200146 }
147
148 /**
149 * Set how the video will fill the allowed layout area.
150 */
151 public void setScalingType(RendererCommon.ScalingType scalingType) {
Magnus Jedvert62b1c352016-10-05 15:56:06 +0200152 ThreadUtils.checkIsOnMainThread();
153 videoLayoutMeasure.setScalingType(scalingType);
sakalfcf97c32017-07-18 05:01:08 -0700154 requestLayout();
Magnus Jedvert62b1c352016-10-05 15:56:06 +0200155 }
156
157 public void setScalingType(RendererCommon.ScalingType scalingTypeMatchOrientation,
158 RendererCommon.ScalingType scalingTypeMismatchOrientation) {
159 ThreadUtils.checkIsOnMainThread();
160 videoLayoutMeasure.setScalingType(scalingTypeMatchOrientation, scalingTypeMismatchOrientation);
sakalfcf97c32017-07-18 05:01:08 -0700161 requestLayout();
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200162 }
163
sakal8b646282016-11-23 06:19:27 -0800164 /**
165 * Limit render framerate.
166 *
167 * @param fps Limit render framerate to this value, or use Float.POSITIVE_INFINITY to disable fps
168 * reduction.
169 */
170 public void setFpsReduction(float fps) {
171 eglRenderer.setFpsReduction(fps);
172 }
173
174 public void disableFpsReduction() {
175 eglRenderer.disableFpsReduction();
176 }
177
178 public void pauseVideo() {
179 eglRenderer.pauseVideo();
180 }
181
sakal6bdcefc2017-08-15 01:56:02 -0700182 // VideoSink interface.
183 @Override
184 public void onFrame(VideoFrame frame) {
sakal6bdcefc2017-08-15 01:56:02 -0700185 eglRenderer.onFrame(frame);
186 }
187
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200188 // View layout interface.
189 @Override
190 protected void onMeasure(int widthSpec, int heightSpec) {
Magnus Jedvert62b1c352016-10-05 15:56:06 +0200191 ThreadUtils.checkIsOnMainThread();
Xiaolei Yu149533a2017-11-03 07:55:01 +0800192 Point size =
193 videoLayoutMeasure.measure(widthSpec, heightSpec, rotatedFrameWidth, rotatedFrameHeight);
magjeddf494b02016-10-07 05:32:35 -0700194 setMeasuredDimension(size.x, size.y);
195 logD("onMeasure(). New size: " + size.x + "x" + size.y);
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200196 }
197
198 @Override
199 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
magjeddf494b02016-10-07 05:32:35 -0700200 ThreadUtils.checkIsOnMainThread();
201 eglRenderer.setLayoutAspectRatio((right - left) / (float) (bottom - top));
sakalb1e6d5e2016-11-22 01:54:42 -0800202 updateSurfaceSize();
203 }
204
205 private void updateSurfaceSize() {
206 ThreadUtils.checkIsOnMainThread();
Xiaolei Yu149533a2017-11-03 07:55:01 +0800207 if (enableFixedSize && rotatedFrameWidth != 0 && rotatedFrameHeight != 0 && getWidth() != 0
208 && getHeight() != 0) {
209 final float layoutAspectRatio = getWidth() / (float) getHeight();
210 final float frameAspectRatio = rotatedFrameWidth / (float) rotatedFrameHeight;
211 final int drawnFrameWidth;
212 final int drawnFrameHeight;
213 if (frameAspectRatio > layoutAspectRatio) {
214 drawnFrameWidth = (int) (rotatedFrameHeight * layoutAspectRatio);
215 drawnFrameHeight = rotatedFrameHeight;
sakalb1e6d5e2016-11-22 01:54:42 -0800216 } else {
Xiaolei Yu149533a2017-11-03 07:55:01 +0800217 drawnFrameWidth = rotatedFrameWidth;
218 drawnFrameHeight = (int) (rotatedFrameWidth / layoutAspectRatio);
sakalb1e6d5e2016-11-22 01:54:42 -0800219 }
Xiaolei Yu149533a2017-11-03 07:55:01 +0800220 // Aspect ratio of the drawn frame and the view is the same.
221 final int width = Math.min(getWidth(), drawnFrameWidth);
222 final int height = Math.min(getHeight(), drawnFrameHeight);
223 logD("updateSurfaceSize. Layout size: " + getWidth() + "x" + getHeight() + ", frame size: "
224 + rotatedFrameWidth + "x" + rotatedFrameHeight + ", requested surface size: " + width
225 + "x" + height + ", old surface size: " + surfaceWidth + "x" + surfaceHeight);
226 if (width != surfaceWidth || height != surfaceHeight) {
227 surfaceWidth = width;
228 surfaceHeight = height;
229 getHolder().setFixedSize(width, height);
230 }
231 } else {
232 surfaceWidth = surfaceHeight = 0;
233 getHolder().setSizeFromLayout();
sakalb1e6d5e2016-11-22 01:54:42 -0800234 }
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200235 }
236
237 // SurfaceHolder.Callback interface.
238 @Override
239 public void surfaceCreated(final SurfaceHolder holder) {
magjeddf494b02016-10-07 05:32:35 -0700240 ThreadUtils.checkIsOnMainThread();
sakalb1e6d5e2016-11-22 01:54:42 -0800241 surfaceWidth = surfaceHeight = 0;
242 updateSurfaceSize();
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200243 }
244
245 @Override
Xiaolei Yu149533a2017-11-03 07:55:01 +0800246 public void surfaceDestroyed(SurfaceHolder holder) {}
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200247
248 @Override
Xiaolei Yu149533a2017-11-03 07:55:01 +0800249 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200250
magjedb2d1c502015-11-11 03:06:42 -0800251 private String getResourceName() {
252 try {
Xiaolei Yu149533a2017-11-03 07:55:01 +0800253 return getResources().getResourceEntryName(getId());
magjedb2d1c502015-11-11 03:06:42 -0800254 } catch (NotFoundException e) {
255 return "";
256 }
257 }
258
sakal9de49e32017-02-13 06:15:02 -0800259 /**
260 * Post a task to clear the SurfaceView to a transparent uniform color.
261 */
262 public void clearImage() {
263 eglRenderer.clearImage();
264 }
265
Xiaolei Yu149533a2017-11-03 07:55:01 +0800266 @Override
267 public void onFirstFrameRendered() {
268 if (rendererEvents != null) {
269 rendererEvents.onFirstFrameRendered();
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200270 }
271 }
272
Xiaolei Yu149533a2017-11-03 07:55:01 +0800273 @Override
274 public void onFrameResolutionChanged(int videoWidth, int videoHeight, int rotation) {
275 if (rendererEvents != null) {
276 rendererEvents.onFrameResolutionChanged(videoWidth, videoHeight, rotation);
277 }
278 int rotatedWidth = rotation == 0 || rotation == 180 ? videoWidth : videoHeight;
279 int rotatedHeight = rotation == 0 || rotation == 180 ? videoHeight : videoWidth;
280 // run immediately if possible for ui thread tests
281 postOrRun(() -> {
282 rotatedFrameWidth = rotatedWidth;
283 rotatedFrameHeight = rotatedHeight;
284 updateSurfaceSize();
285 requestLayout();
286 });
287 }
288
289 private void postOrRun(Runnable r) {
290 if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
291 r.run();
292 } else {
293 post(r);
sakal6bdcefc2017-08-15 01:56:02 -0700294 }
295 }
296
magjeddf494b02016-10-07 05:32:35 -0700297 private void logD(String string) {
Xiaolei Yu149533a2017-11-03 07:55:01 +0800298 Logging.d(TAG, resourceName + ": " + string);
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200299 }
300}