blob: 231d507155f921b99d269b32ced7e2b8b026df82 [file] [log] [blame]
arsanyb75f2542016-08-31 18:50:52 -07001/*
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
11package org.webrtc;
12
arsanyb75f2542016-08-31 18:50:52 -070013import android.app.Activity;
14import android.content.Context;
15import android.content.Intent;
16import android.hardware.display.DisplayManager;
17import android.hardware.display.VirtualDisplay;
18import android.media.projection.MediaProjection;
19import android.media.projection.MediaProjectionManager;
20import android.view.Surface;
Byoungchan Lee02334e02021-08-14 11:41:59 +090021import androidx.annotation.Nullable;
arsanyb75f2542016-08-31 18:50:52 -070022
arsanyb75f2542016-08-31 18:50:52 -070023/**
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
Magnus Jedvert1a759c62018-04-24 15:11:02 +020029 * as a texture to the native code via {@code CapturerObserver.onFrameCaptured()}. This takes
arsanyb75f2542016-08-31 18:50:52 -070030 * 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 */
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +020034public class ScreenCapturerAndroid implements VideoCapturer, VideoSink {
sakalb6760f92016-09-29 04:12:44 -070035 private static final int DISPLAY_FLAGS =
36 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
arsanyb75f2542016-08-31 18:50:52 -070037 // 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;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010045 @Nullable private VirtualDisplay virtualDisplay;
46 @Nullable private SurfaceTextureHelper surfaceTextureHelper;
47 @Nullable private CapturerObserver capturerObserver;
Sami Kalliomäki3d50a312018-09-11 11:11:47 +020048 private long numCapturedFrames;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010049 @Nullable private MediaProjection mediaProjection;
Sami Kalliomäki3d50a312018-09-11 11:11:47 +020050 private boolean isDisposed;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010051 @Nullable private MediaProjectionManager mediaProjectionManager;
arsanyb75f2542016-08-31 18:50:52 -070052
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 **/
sakalb6760f92016-09-29 04:12:44 -070062 public ScreenCapturerAndroid(Intent mediaProjectionPermissionResultData,
arsanyb75f2542016-08-31 18:50:52 -070063 MediaProjection.Callback mediaProjectionCallback) {
64 this.mediaProjectionPermissionResultData = mediaProjectionPermissionResultData;
65 this.mediaProjectionCallback = mediaProjectionCallback;
66 }
67
68 private void checkNotDisposed() {
69 if (isDisposed) {
70 throw new RuntimeException("capturer is disposed.");
71 }
72 }
73
Bin Zhu12e85112020-08-24 14:25:10 -070074 @Nullable
75 public MediaProjection getMediaProjection() {
76 return mediaProjection;
77 }
78
arsanyb75f2542016-08-31 18:50:52 -070079 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -080080 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
81 @SuppressWarnings("NoSynchronizedMethodCheck")
sakalb6760f92016-09-29 04:12:44 -070082 public synchronized void initialize(final SurfaceTextureHelper surfaceTextureHelper,
Sami Kalliomäkib2daaaa2018-07-06 11:25:25 +020083 final Context applicationContext, final CapturerObserver capturerObserver) {
arsanyb75f2542016-08-31 18:50:52 -070084 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
sakalb6760f92016-09-29 04:12:44 -070096 mediaProjectionManager = (MediaProjectionManager) applicationContext.getSystemService(
97 Context.MEDIA_PROJECTION_SERVICE);
arsanyb75f2542016-08-31 18:50:52 -070098 }
99
arsanyb75f2542016-08-31 18:50:52 -0700100 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800101 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
102 @SuppressWarnings("NoSynchronizedMethodCheck")
sakalb6760f92016-09-29 04:12:44 -0700103 public synchronized void startCapture(
104 final int width, final int height, final int ignoredFramerate) {
arsanyb75f2542016-08-31 18:50:52 -0700105 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
Mirko Bonadei12251b62017-11-05 19:35:31 -0800122 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
123 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700124 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
arsanyb75f2542016-08-31 18:50:52 -0700148 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800149 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
150 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700151 public synchronized void dispose() {
152 isDisposed = true;
153 }
154
arsanyb75f2542016-08-31 18:50:52 -0700155 /**
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
Mirko Bonadei12251b62017-11-05 19:35:31 -0800164 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
165 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700166 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
190 private void createVirtualDisplay() {
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200191 surfaceTextureHelper.setTextureSize(width, height);
sakalb6760f92016-09-29 04:12:44 -0700192 virtualDisplay = mediaProjection.createVirtualDisplay("WebRTC_ScreenCapture", width, height,
193 VIRTUAL_DISPLAY_DPI, DISPLAY_FLAGS, new Surface(surfaceTextureHelper.getSurfaceTexture()),
arsanyb75f2542016-08-31 18:50:52 -0700194 null /* callback */, null /* callback handler */);
195 }
196
197 // This is called on the internal looper thread of {@Code SurfaceTextureHelper}.
198 @Override
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200199 public void onFrame(VideoFrame frame) {
arsanyb75f2542016-08-31 18:50:52 -0700200 numCapturedFrames++;
Sami Kalliomäki682dc612018-01-26 11:06:45 +0100201 capturerObserver.onFrameCaptured(frame);
arsanyb75f2542016-08-31 18:50:52 -0700202 }
203
204 @Override
205 public boolean isScreencast() {
206 return true;
207 }
208
209 public long getNumCapturedFrames() {
210 return numCapturedFrames;
211 }
212}