1 /* 2 * Copyright 2016 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.app.Activity; 14 import android.content.Context; 15 import android.content.Intent; 16 import android.hardware.display.DisplayManager; 17 import android.hardware.display.VirtualDisplay; 18 import android.media.projection.MediaProjection; 19 import android.media.projection.MediaProjectionManager; 20 import android.view.Surface; 21 import androidx.annotation.Nullable; 22 23 /** 24 * An implementation of VideoCapturer to capture the screen content as a video stream. 25 * Capturing is done by {@code MediaProjection} on a {@code SurfaceTexture}. We interact with this 26 * {@code SurfaceTexture} using a {@code SurfaceTextureHelper}. 27 * The {@code SurfaceTextureHelper} is created by the native code and passed to this capturer in 28 * {@code VideoCapturer.initialize()}. On receiving a new frame, this capturer passes it 29 * as a texture to the native code via {@code CapturerObserver.onFrameCaptured()}. This takes 30 * place on the HandlerThread of the given {@code SurfaceTextureHelper}. When done with each frame, 31 * the native code returns the buffer to the {@code SurfaceTextureHelper} to be used for new 32 * frames. At any time, at most one frame is being processed. 33 */ 34 public class ScreenCapturerAndroid implements VideoCapturer, VideoSink { 35 private static final int DISPLAY_FLAGS = 36 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; 37 // DPI for VirtualDisplay, does not seem to matter for us. 38 private static final int VIRTUAL_DISPLAY_DPI = 400; 39 40 private final Intent mediaProjectionPermissionResultData; 41 private final MediaProjection.Callback mediaProjectionCallback; 42 43 private int width; 44 private int height; 45 @Nullable private VirtualDisplay virtualDisplay; 46 @Nullable private SurfaceTextureHelper surfaceTextureHelper; 47 @Nullable private CapturerObserver capturerObserver; 48 private long numCapturedFrames; 49 @Nullable private MediaProjection mediaProjection; 50 private boolean isDisposed; 51 @Nullable private MediaProjectionManager mediaProjectionManager; 52 53 /** 54 * Constructs a new Screen Capturer. 55 * 56 * @param mediaProjectionPermissionResultData the result data of MediaProjection permission 57 * activity; the calling app must validate that result code is Activity.RESULT_OK before 58 * calling this method. 59 * @param mediaProjectionCallback MediaProjection callback to implement application specific 60 * logic in events such as when the user revokes a previously granted capture permission. 61 **/ ScreenCapturerAndroid(Intent mediaProjectionPermissionResultData, MediaProjection.Callback mediaProjectionCallback)62 public ScreenCapturerAndroid(Intent mediaProjectionPermissionResultData, 63 MediaProjection.Callback mediaProjectionCallback) { 64 this.mediaProjectionPermissionResultData = mediaProjectionPermissionResultData; 65 this.mediaProjectionCallback = mediaProjectionCallback; 66 } 67 checkNotDisposed()68 private void checkNotDisposed() { 69 if (isDisposed) { 70 throw new RuntimeException("capturer is disposed."); 71 } 72 } 73 74 @Nullable getMediaProjection()75 public MediaProjection getMediaProjection() { 76 return mediaProjection; 77 } 78 79 @Override 80 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression. 81 @SuppressWarnings("NoSynchronizedMethodCheck") initialize(final SurfaceTextureHelper surfaceTextureHelper, final Context applicationContext, final CapturerObserver capturerObserver)82 public synchronized void initialize(final SurfaceTextureHelper surfaceTextureHelper, 83 final Context applicationContext, final CapturerObserver capturerObserver) { 84 checkNotDisposed(); 85 86 if (capturerObserver == null) { 87 throw new RuntimeException("capturerObserver not set."); 88 } 89 this.capturerObserver = capturerObserver; 90 91 if (surfaceTextureHelper == null) { 92 throw new RuntimeException("surfaceTextureHelper not set."); 93 } 94 this.surfaceTextureHelper = surfaceTextureHelper; 95 96 mediaProjectionManager = (MediaProjectionManager) applicationContext.getSystemService( 97 Context.MEDIA_PROJECTION_SERVICE); 98 } 99 100 @Override 101 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression. 102 @SuppressWarnings("NoSynchronizedMethodCheck") startCapture( final int width, final int height, final int ignoredFramerate)103 public synchronized void startCapture( 104 final int width, final int height, final int ignoredFramerate) { 105 checkNotDisposed(); 106 107 this.width = width; 108 this.height = height; 109 110 mediaProjection = mediaProjectionManager.getMediaProjection( 111 Activity.RESULT_OK, mediaProjectionPermissionResultData); 112 113 // Let MediaProjection callback use the SurfaceTextureHelper thread. 114 mediaProjection.registerCallback(mediaProjectionCallback, surfaceTextureHelper.getHandler()); 115 116 createVirtualDisplay(); 117 capturerObserver.onCapturerStarted(true); 118 surfaceTextureHelper.startListening(ScreenCapturerAndroid.this); 119 } 120 121 @Override 122 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression. 123 @SuppressWarnings("NoSynchronizedMethodCheck") stopCapture()124 public synchronized void stopCapture() { 125 checkNotDisposed(); 126 ThreadUtils.invokeAtFrontUninterruptibly(surfaceTextureHelper.getHandler(), new Runnable() { 127 @Override 128 public void run() { 129 surfaceTextureHelper.stopListening(); 130 capturerObserver.onCapturerStopped(); 131 132 if (virtualDisplay != null) { 133 virtualDisplay.release(); 134 virtualDisplay = null; 135 } 136 137 if (mediaProjection != null) { 138 // Unregister the callback before stopping, otherwise the callback recursively 139 // calls this method. 140 mediaProjection.unregisterCallback(mediaProjectionCallback); 141 mediaProjection.stop(); 142 mediaProjection = null; 143 } 144 } 145 }); 146 } 147 148 @Override 149 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression. 150 @SuppressWarnings("NoSynchronizedMethodCheck") dispose()151 public synchronized void dispose() { 152 isDisposed = true; 153 } 154 155 /** 156 * Changes output video format. This method can be used to scale the output 157 * video, or to change orientation when the captured screen is rotated for example. 158 * 159 * @param width new output video width 160 * @param height new output video height 161 * @param ignoredFramerate ignored 162 */ 163 @Override 164 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression. 165 @SuppressWarnings("NoSynchronizedMethodCheck") changeCaptureFormat( final int width, final int height, final int ignoredFramerate)166 public synchronized void changeCaptureFormat( 167 final int width, final int height, final int ignoredFramerate) { 168 checkNotDisposed(); 169 170 this.width = width; 171 this.height = height; 172 173 if (virtualDisplay == null) { 174 // Capturer is stopped, the virtual display will be created in startCaptuer(). 175 return; 176 } 177 178 // Create a new virtual display on the surfaceTextureHelper thread to avoid interference 179 // with frame processing, which happens on the same thread (we serialize events by running 180 // them on the same thread). 181 ThreadUtils.invokeAtFrontUninterruptibly(surfaceTextureHelper.getHandler(), new Runnable() { 182 @Override 183 public void run() { 184 virtualDisplay.release(); 185 createVirtualDisplay(); 186 } 187 }); 188 } 189 createVirtualDisplay()190 private void createVirtualDisplay() { 191 surfaceTextureHelper.setTextureSize(width, height); 192 virtualDisplay = mediaProjection.createVirtualDisplay("WebRTC_ScreenCapture", width, height, 193 VIRTUAL_DISPLAY_DPI, DISPLAY_FLAGS, new Surface(surfaceTextureHelper.getSurfaceTexture()), 194 null /* callback */, null /* callback handler */); 195 } 196 197 // This is called on the internal looper thread of {@Code SurfaceTextureHelper}. 198 @Override onFrame(VideoFrame frame)199 public void onFrame(VideoFrame frame) { 200 numCapturedFrames++; 201 capturerObserver.onFrameCaptured(frame); 202 } 203 204 @Override isScreencast()205 public boolean isScreencast() { 206 return true; 207 } 208 getNumCapturedFrames()209 public long getNumCapturedFrames() { 210 return numCapturedFrames; 211 } 212 } 213