blob: a8eb57b8e3afc01b6ee3fb79b362d53978fa94fc [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 */
Xiaolei Yu149533a2017-11-03 07:55:01 +080024public class SurfaceViewRenderer extends SurfaceView implements SurfaceHolder.Callback,
25 VideoRenderer.Callbacks, VideoSink,
26 RendererCommon.RendererEvents {
Magnus Jedvertc06a7162015-09-11 09:51:52 +020027 private static final String TAG = "SurfaceViewRenderer";
28
magjeddf494b02016-10-07 05:32:35 -070029 // Cached resource name.
30 private final String resourceName;
31 private final RendererCommon.VideoLayoutMeasure videoLayoutMeasure =
32 new RendererCommon.VideoLayoutMeasure();
Xiaolei Yu149533a2017-11-03 07:55:01 +080033 private final SurfaceEglRenderer eglRenderer;
Magnus Jedvertc06a7162015-09-11 09:51:52 +020034
magjeddf494b02016-10-07 05:32:35 -070035 // Callback for reporting renderer events. Read-only after initilization so no lock required.
36 private RendererCommon.RendererEvents rendererEvents;
Magnus Jedvertc06a7162015-09-11 09:51:52 +020037
Xiaolei Yu149533a2017-11-03 07:55:01 +080038 // Accessed only on the main thread.
Magnus Jedvert62b1c352016-10-05 15:56:06 +020039 private int rotatedFrameWidth;
40 private int rotatedFrameHeight;
sakalb1e6d5e2016-11-22 01:54:42 -080041 private boolean enableFixedSize;
42 private int surfaceWidth;
43 private int surfaceHeight;
44
Magnus Jedvertc06a7162015-09-11 09:51:52 +020045 /**
46 * Standard View constructor. In order to render something, you must first call init().
47 */
48 public SurfaceViewRenderer(Context context) {
49 super(context);
magjeddf494b02016-10-07 05:32:35 -070050 this.resourceName = getResourceName();
Xiaolei Yu149533a2017-11-03 07:55:01 +080051 eglRenderer = new SurfaceEglRenderer(resourceName);
Magnus Jedvert4382d802015-10-08 14:45:34 +020052 getHolder().addCallback(this);
Xiaolei Yu149533a2017-11-03 07:55:01 +080053 getHolder().addCallback(eglRenderer);
Magnus Jedvertc06a7162015-09-11 09:51:52 +020054 }
55
56 /**
57 * Standard View constructor. In order to render something, you must first call init().
58 */
59 public SurfaceViewRenderer(Context context, AttributeSet attrs) {
60 super(context, attrs);
magjeddf494b02016-10-07 05:32:35 -070061 this.resourceName = getResourceName();
Xiaolei Yu149533a2017-11-03 07:55:01 +080062 eglRenderer = new SurfaceEglRenderer(resourceName);
Magnus Jedvert4382d802015-10-08 14:45:34 +020063 getHolder().addCallback(this);
Xiaolei Yu149533a2017-11-03 07:55:01 +080064 getHolder().addCallback(eglRenderer);
Magnus Jedvertc06a7162015-09-11 09:51:52 +020065 }
66
67 /**
Magnus Jedvert4382d802015-10-08 14:45:34 +020068 * Initialize this class, sharing resources with |sharedContext|. It is allowed to call init() to
69 * reinitialize the renderer after a previous init()/release() cycle.
Magnus Jedvertc06a7162015-09-11 09:51:52 +020070 */
sakalb6760f92016-09-29 04:12:44 -070071 public void init(EglBase.Context sharedContext, RendererCommon.RendererEvents rendererEvents) {
Magnus Jedvert51254332015-12-15 16:22:29 +010072 init(sharedContext, rendererEvents, EglBase.CONFIG_PLAIN, new GlRectDrawer());
73 }
74
75 /**
76 * Initialize this class, sharing resources with |sharedContext|. The custom |drawer| will be used
77 * for drawing frames on the EGLSurface. This class is responsible for calling release() on
78 * |drawer|. It is allowed to call init() to reinitialize the renderer after a previous
79 * init()/release() cycle.
80 */
sakalb6760f92016-09-29 04:12:44 -070081 public void init(final EglBase.Context sharedContext,
82 RendererCommon.RendererEvents rendererEvents, final int[] configAttributes,
83 RendererCommon.GlDrawer drawer) {
magjeddf494b02016-10-07 05:32:35 -070084 ThreadUtils.checkIsOnMainThread();
85 this.rendererEvents = rendererEvents;
Xiaolei Yu149533a2017-11-03 07:55:01 +080086 rotatedFrameWidth = 0;
87 rotatedFrameHeight = 0;
88 eglRenderer.init(sharedContext, this /* rendererEvents */, configAttributes, drawer);
Magnus Jedvertc06a7162015-09-11 09:51:52 +020089 }
90
91 /**
Magnus Jedvertc19922c2015-09-21 14:32:07 +020092 * Block until any pending frame is returned and all GL resources released, even if an interrupt
93 * occurs. If an interrupt occurs during release(), the interrupt flag will be set. This function
94 * should be called before the Activity is destroyed and the EGLContext is still valid. If you
95 * don't call this function, the GL resources might leak.
Magnus Jedvertc06a7162015-09-11 09:51:52 +020096 */
97 public void release() {
magjeddf494b02016-10-07 05:32:35 -070098 eglRenderer.release();
Magnus Jedvertc06a7162015-09-11 09:51:52 +020099 }
100
sakal3a9bc172016-11-30 08:30:05 -0800101 /**
102 * Register a callback to be invoked when a new video frame has been received.
103 *
sakald1516522017-03-13 05:11:48 -0700104 * @param listener The callback to be invoked. The callback will be invoked on the render thread.
105 * It should be lightweight and must not call removeFrameListener.
sakal3a9bc172016-11-30 08:30:05 -0800106 * @param scale The scale of the Bitmap passed to the callback, or 0 if no Bitmap is
107 * required.
108 * @param drawer Custom drawer to use for this frame listener.
109 */
110 public void addFrameListener(
sakald1516522017-03-13 05:11:48 -0700111 EglRenderer.FrameListener listener, float scale, RendererCommon.GlDrawer drawerParam) {
112 eglRenderer.addFrameListener(listener, scale, drawerParam);
sakal3a9bc172016-11-30 08:30:05 -0800113 }
114
115 /**
116 * Register a callback to be invoked when a new video frame has been received. This version uses
117 * the drawer of the EglRenderer that was passed in init.
118 *
sakald1516522017-03-13 05:11:48 -0700119 * @param listener The callback to be invoked. The callback will be invoked on the render thread.
120 * It should be lightweight and must not call removeFrameListener.
sakal3a9bc172016-11-30 08:30:05 -0800121 * @param scale The scale of the Bitmap passed to the callback, or 0 if no Bitmap is
122 * required.
123 */
sakal4fe3b8d2016-11-23 06:18:20 -0800124 public void addFrameListener(EglRenderer.FrameListener listener, float scale) {
125 eglRenderer.addFrameListener(listener, scale);
126 }
127
128 public void removeFrameListener(EglRenderer.FrameListener listener) {
129 eglRenderer.removeFrameListener(listener);
130 }
131
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200132 /**
sakalb1e6d5e2016-11-22 01:54:42 -0800133 * Enables fixed size for the surface. This provides better performance but might be buggy on some
134 * devices. By default this is turned off.
135 */
136 public void setEnableHardwareScaler(boolean enabled) {
137 ThreadUtils.checkIsOnMainThread();
138 enableFixedSize = enabled;
139 updateSurfaceSize();
140 }
141
142 /**
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200143 * Set if the video stream should be mirrored or not.
144 */
145 public void setMirror(final boolean mirror) {
magjeddf494b02016-10-07 05:32:35 -0700146 eglRenderer.setMirror(mirror);
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200147 }
148
149 /**
150 * Set how the video will fill the allowed layout area.
151 */
152 public void setScalingType(RendererCommon.ScalingType scalingType) {
Magnus Jedvert62b1c352016-10-05 15:56:06 +0200153 ThreadUtils.checkIsOnMainThread();
154 videoLayoutMeasure.setScalingType(scalingType);
sakalfcf97c32017-07-18 05:01:08 -0700155 requestLayout();
Magnus Jedvert62b1c352016-10-05 15:56:06 +0200156 }
157
158 public void setScalingType(RendererCommon.ScalingType scalingTypeMatchOrientation,
159 RendererCommon.ScalingType scalingTypeMismatchOrientation) {
160 ThreadUtils.checkIsOnMainThread();
161 videoLayoutMeasure.setScalingType(scalingTypeMatchOrientation, scalingTypeMismatchOrientation);
sakalfcf97c32017-07-18 05:01:08 -0700162 requestLayout();
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200163 }
164
sakal8b646282016-11-23 06:19:27 -0800165 /**
166 * Limit render framerate.
167 *
168 * @param fps Limit render framerate to this value, or use Float.POSITIVE_INFINITY to disable fps
169 * reduction.
170 */
171 public void setFpsReduction(float fps) {
172 eglRenderer.setFpsReduction(fps);
173 }
174
175 public void disableFpsReduction() {
176 eglRenderer.disableFpsReduction();
177 }
178
179 public void pauseVideo() {
180 eglRenderer.pauseVideo();
181 }
182
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200183 // VideoRenderer.Callbacks interface.
184 @Override
185 public void renderFrame(VideoRenderer.I420Frame frame) {
magjeddf494b02016-10-07 05:32:35 -0700186 eglRenderer.renderFrame(frame);
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200187 }
188
sakal6bdcefc2017-08-15 01:56:02 -0700189 // VideoSink interface.
190 @Override
191 public void onFrame(VideoFrame frame) {
sakal6bdcefc2017-08-15 01:56:02 -0700192 eglRenderer.onFrame(frame);
193 }
194
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200195 // View layout interface.
196 @Override
197 protected void onMeasure(int widthSpec, int heightSpec) {
Magnus Jedvert62b1c352016-10-05 15:56:06 +0200198 ThreadUtils.checkIsOnMainThread();
Xiaolei Yu149533a2017-11-03 07:55:01 +0800199 Point size =
200 videoLayoutMeasure.measure(widthSpec, heightSpec, rotatedFrameWidth, rotatedFrameHeight);
magjeddf494b02016-10-07 05:32:35 -0700201 setMeasuredDimension(size.x, size.y);
202 logD("onMeasure(). New size: " + size.x + "x" + size.y);
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200203 }
204
205 @Override
206 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
magjeddf494b02016-10-07 05:32:35 -0700207 ThreadUtils.checkIsOnMainThread();
208 eglRenderer.setLayoutAspectRatio((right - left) / (float) (bottom - top));
sakalb1e6d5e2016-11-22 01:54:42 -0800209 updateSurfaceSize();
210 }
211
212 private void updateSurfaceSize() {
213 ThreadUtils.checkIsOnMainThread();
Xiaolei Yu149533a2017-11-03 07:55:01 +0800214 if (enableFixedSize && rotatedFrameWidth != 0 && rotatedFrameHeight != 0 && getWidth() != 0
215 && getHeight() != 0) {
216 final float layoutAspectRatio = getWidth() / (float) getHeight();
217 final float frameAspectRatio = rotatedFrameWidth / (float) rotatedFrameHeight;
218 final int drawnFrameWidth;
219 final int drawnFrameHeight;
220 if (frameAspectRatio > layoutAspectRatio) {
221 drawnFrameWidth = (int) (rotatedFrameHeight * layoutAspectRatio);
222 drawnFrameHeight = rotatedFrameHeight;
sakalb1e6d5e2016-11-22 01:54:42 -0800223 } else {
Xiaolei Yu149533a2017-11-03 07:55:01 +0800224 drawnFrameWidth = rotatedFrameWidth;
225 drawnFrameHeight = (int) (rotatedFrameWidth / layoutAspectRatio);
sakalb1e6d5e2016-11-22 01:54:42 -0800226 }
Xiaolei Yu149533a2017-11-03 07:55:01 +0800227 // Aspect ratio of the drawn frame and the view is the same.
228 final int width = Math.min(getWidth(), drawnFrameWidth);
229 final int height = Math.min(getHeight(), drawnFrameHeight);
230 logD("updateSurfaceSize. Layout size: " + getWidth() + "x" + getHeight() + ", frame size: "
231 + rotatedFrameWidth + "x" + rotatedFrameHeight + ", requested surface size: " + width
232 + "x" + height + ", old surface size: " + surfaceWidth + "x" + surfaceHeight);
233 if (width != surfaceWidth || height != surfaceHeight) {
234 surfaceWidth = width;
235 surfaceHeight = height;
236 getHolder().setFixedSize(width, height);
237 }
238 } else {
239 surfaceWidth = surfaceHeight = 0;
240 getHolder().setSizeFromLayout();
sakalb1e6d5e2016-11-22 01:54:42 -0800241 }
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200242 }
243
244 // SurfaceHolder.Callback interface.
245 @Override
246 public void surfaceCreated(final SurfaceHolder holder) {
magjeddf494b02016-10-07 05:32:35 -0700247 ThreadUtils.checkIsOnMainThread();
sakalb1e6d5e2016-11-22 01:54:42 -0800248 surfaceWidth = surfaceHeight = 0;
249 updateSurfaceSize();
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200250 }
251
252 @Override
Xiaolei Yu149533a2017-11-03 07:55:01 +0800253 public void surfaceDestroyed(SurfaceHolder holder) {}
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200254
255 @Override
Xiaolei Yu149533a2017-11-03 07:55:01 +0800256 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200257
magjedb2d1c502015-11-11 03:06:42 -0800258 private String getResourceName() {
259 try {
Xiaolei Yu149533a2017-11-03 07:55:01 +0800260 return getResources().getResourceEntryName(getId());
magjedb2d1c502015-11-11 03:06:42 -0800261 } catch (NotFoundException e) {
262 return "";
263 }
264 }
265
sakal9de49e32017-02-13 06:15:02 -0800266 /**
267 * Post a task to clear the SurfaceView to a transparent uniform color.
268 */
269 public void clearImage() {
270 eglRenderer.clearImage();
271 }
272
Xiaolei Yu149533a2017-11-03 07:55:01 +0800273 @Override
274 public void onFirstFrameRendered() {
275 if (rendererEvents != null) {
276 rendererEvents.onFirstFrameRendered();
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200277 }
278 }
279
Xiaolei Yu149533a2017-11-03 07:55:01 +0800280 @Override
281 public void onFrameResolutionChanged(int videoWidth, int videoHeight, int rotation) {
282 if (rendererEvents != null) {
283 rendererEvents.onFrameResolutionChanged(videoWidth, videoHeight, rotation);
284 }
285 int rotatedWidth = rotation == 0 || rotation == 180 ? videoWidth : videoHeight;
286 int rotatedHeight = rotation == 0 || rotation == 180 ? videoHeight : videoWidth;
287 // run immediately if possible for ui thread tests
288 postOrRun(() -> {
289 rotatedFrameWidth = rotatedWidth;
290 rotatedFrameHeight = rotatedHeight;
291 updateSurfaceSize();
292 requestLayout();
293 });
294 }
295
296 private void postOrRun(Runnable r) {
297 if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
298 r.run();
299 } else {
300 post(r);
sakal6bdcefc2017-08-15 01:56:02 -0700301 }
302 }
303
magjeddf494b02016-10-07 05:32:35 -0700304 private void logD(String string) {
Xiaolei Yu149533a2017-11-03 07:55:01 +0800305 Logging.d(TAG, resourceName + ": " + string);
Magnus Jedvertc06a7162015-09-11 09:51:52 +0200306 }
307}