1 /* 2 * Copyright 2017 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.view.SurfaceHolder; 14 import java.util.concurrent.CountDownLatch; 15 16 /** 17 * Display the video stream on a Surface. 18 * renderFrame() is asynchronous to avoid blocking the calling thread. 19 * This class is thread safe and handles access from potentially three different threads: 20 * Interaction from the main app in init, release and setMirror. 21 * Interaction from C++ rtc::VideoSinkInterface in renderFrame. 22 * Interaction from SurfaceHolder lifecycle in surfaceCreated, surfaceChanged, and surfaceDestroyed. 23 */ 24 public class SurfaceEglRenderer extends EglRenderer implements SurfaceHolder.Callback { 25 private static final String TAG = "SurfaceEglRenderer"; 26 27 // Callback for reporting renderer events. Read-only after initialization so no lock required. 28 private RendererCommon.RendererEvents rendererEvents; 29 30 private final Object layoutLock = new Object(); 31 private boolean isRenderingPaused; 32 private boolean isFirstFrameRendered; 33 private int rotatedFrameWidth; 34 private int rotatedFrameHeight; 35 private int frameRotation; 36 37 /** 38 * In order to render something, you must first call init(). 39 */ SurfaceEglRenderer(String name)40 public SurfaceEglRenderer(String name) { 41 super(name); 42 } 43 44 /** 45 * Initialize this class, sharing resources with `sharedContext`. The custom `drawer` will be used 46 * for drawing frames on the EGLSurface. This class is responsible for calling release() on 47 * `drawer`. It is allowed to call init() to reinitialize the renderer after a previous 48 * init()/release() cycle. 49 */ init(final EglBase.Context sharedContext, RendererCommon.RendererEvents rendererEvents, final int[] configAttributes, RendererCommon.GlDrawer drawer)50 public void init(final EglBase.Context sharedContext, 51 RendererCommon.RendererEvents rendererEvents, final int[] configAttributes, 52 RendererCommon.GlDrawer drawer) { 53 ThreadUtils.checkIsOnMainThread(); 54 this.rendererEvents = rendererEvents; 55 synchronized (layoutLock) { 56 isFirstFrameRendered = false; 57 rotatedFrameWidth = 0; 58 rotatedFrameHeight = 0; 59 frameRotation = 0; 60 } 61 super.init(sharedContext, configAttributes, drawer); 62 } 63 64 @Override init(final EglBase.Context sharedContext, final int[] configAttributes, RendererCommon.GlDrawer drawer)65 public void init(final EglBase.Context sharedContext, final int[] configAttributes, 66 RendererCommon.GlDrawer drawer) { 67 init(sharedContext, null /* rendererEvents */, configAttributes, drawer); 68 } 69 70 /** 71 * Limit render framerate. 72 * 73 * @param fps Limit render framerate to this value, or use Float.POSITIVE_INFINITY to disable fps 74 * reduction. 75 */ 76 @Override setFpsReduction(float fps)77 public void setFpsReduction(float fps) { 78 synchronized (layoutLock) { 79 isRenderingPaused = fps == 0f; 80 } 81 super.setFpsReduction(fps); 82 } 83 84 @Override disableFpsReduction()85 public void disableFpsReduction() { 86 synchronized (layoutLock) { 87 isRenderingPaused = false; 88 } 89 super.disableFpsReduction(); 90 } 91 92 @Override pauseVideo()93 public void pauseVideo() { 94 synchronized (layoutLock) { 95 isRenderingPaused = true; 96 } 97 super.pauseVideo(); 98 } 99 100 // VideoSink interface. 101 @Override onFrame(VideoFrame frame)102 public void onFrame(VideoFrame frame) { 103 updateFrameDimensionsAndReportEvents(frame); 104 super.onFrame(frame); 105 } 106 107 // SurfaceHolder.Callback interface. 108 @Override surfaceCreated(final SurfaceHolder holder)109 public void surfaceCreated(final SurfaceHolder holder) { 110 ThreadUtils.checkIsOnMainThread(); 111 createEglSurface(holder.getSurface()); 112 } 113 114 @Override surfaceDestroyed(SurfaceHolder holder)115 public void surfaceDestroyed(SurfaceHolder holder) { 116 ThreadUtils.checkIsOnMainThread(); 117 final CountDownLatch completionLatch = new CountDownLatch(1); 118 releaseEglSurface(completionLatch::countDown); 119 ThreadUtils.awaitUninterruptibly(completionLatch); 120 } 121 122 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)123 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 124 ThreadUtils.checkIsOnMainThread(); 125 logD("surfaceChanged: format: " + format + " size: " + width + "x" + height); 126 } 127 128 // Update frame dimensions and report any changes to `rendererEvents`. updateFrameDimensionsAndReportEvents(VideoFrame frame)129 private void updateFrameDimensionsAndReportEvents(VideoFrame frame) { 130 synchronized (layoutLock) { 131 if (isRenderingPaused) { 132 return; 133 } 134 if (!isFirstFrameRendered) { 135 isFirstFrameRendered = true; 136 logD("Reporting first rendered frame."); 137 if (rendererEvents != null) { 138 rendererEvents.onFirstFrameRendered(); 139 } 140 } 141 if (rotatedFrameWidth != frame.getRotatedWidth() 142 || rotatedFrameHeight != frame.getRotatedHeight() 143 || frameRotation != frame.getRotation()) { 144 logD("Reporting frame resolution changed to " + frame.getBuffer().getWidth() + "x" 145 + frame.getBuffer().getHeight() + " with rotation " + frame.getRotation()); 146 if (rendererEvents != null) { 147 rendererEvents.onFrameResolutionChanged( 148 frame.getBuffer().getWidth(), frame.getBuffer().getHeight(), frame.getRotation()); 149 } 150 rotatedFrameWidth = frame.getRotatedWidth(); 151 rotatedFrameHeight = frame.getRotatedHeight(); 152 frameRotation = frame.getRotation(); 153 } 154 } 155 } 156 logD(String string)157 private void logD(String string) { 158 Logging.d(TAG, name + ": " + string); 159 } 160 } 161