blob: e37b34d9b2f0b58a3f3920e722f470b5d8d9d593 [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
13import android.annotation.TargetApi;
14import android.app.Activity;
15import android.content.Context;
16import android.content.Intent;
17import android.hardware.display.DisplayManager;
18import android.hardware.display.VirtualDisplay;
19import android.media.projection.MediaProjection;
20import android.media.projection.MediaProjectionManager;
Artem Titarenko69540f42018-12-10 12:30:46 +010021import android.support.annotation.Nullable;
arsanyb75f2542016-08-31 18:50:52 -070022import android.view.Surface;
23
arsanyb75f2542016-08-31 18:50:52 -070024/**
25 * An implementation of VideoCapturer to capture the screen content as a video stream.
26 * Capturing is done by {@code MediaProjection} on a {@code SurfaceTexture}. We interact with this
27 * {@code SurfaceTexture} using a {@code SurfaceTextureHelper}.
28 * The {@code SurfaceTextureHelper} is created by the native code and passed to this capturer in
29 * {@code VideoCapturer.initialize()}. On receiving a new frame, this capturer passes it
Magnus Jedvert1a759c62018-04-24 15:11:02 +020030 * as a texture to the native code via {@code CapturerObserver.onFrameCaptured()}. This takes
arsanyb75f2542016-08-31 18:50:52 -070031 * place on the HandlerThread of the given {@code SurfaceTextureHelper}. When done with each frame,
32 * the native code returns the buffer to the {@code SurfaceTextureHelper} to be used for new
33 * frames. At any time, at most one frame is being processed.
Sami Kalliomäki8db246a2018-10-04 10:58:24 +020034 *
35 * @note This class is only supported on Android Lollipop and above.
arsanyb75f2542016-08-31 18:50:52 -070036 */
37@TargetApi(21)
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +020038public class ScreenCapturerAndroid implements VideoCapturer, VideoSink {
sakalb6760f92016-09-29 04:12:44 -070039 private static final int DISPLAY_FLAGS =
40 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
arsanyb75f2542016-08-31 18:50:52 -070041 // DPI for VirtualDisplay, does not seem to matter for us.
42 private static final int VIRTUAL_DISPLAY_DPI = 400;
43
44 private final Intent mediaProjectionPermissionResultData;
45 private final MediaProjection.Callback mediaProjectionCallback;
46
47 private int width;
48 private int height;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010049 @Nullable private VirtualDisplay virtualDisplay;
50 @Nullable private SurfaceTextureHelper surfaceTextureHelper;
51 @Nullable private CapturerObserver capturerObserver;
Sami Kalliomäki3d50a312018-09-11 11:11:47 +020052 private long numCapturedFrames;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010053 @Nullable private MediaProjection mediaProjection;
Sami Kalliomäki3d50a312018-09-11 11:11:47 +020054 private boolean isDisposed;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010055 @Nullable private MediaProjectionManager mediaProjectionManager;
arsanyb75f2542016-08-31 18:50:52 -070056
57 /**
58 * Constructs a new Screen Capturer.
59 *
60 * @param mediaProjectionPermissionResultData the result data of MediaProjection permission
61 * activity; the calling app must validate that result code is Activity.RESULT_OK before
62 * calling this method.
63 * @param mediaProjectionCallback MediaProjection callback to implement application specific
64 * logic in events such as when the user revokes a previously granted capture permission.
65 **/
sakalb6760f92016-09-29 04:12:44 -070066 public ScreenCapturerAndroid(Intent mediaProjectionPermissionResultData,
arsanyb75f2542016-08-31 18:50:52 -070067 MediaProjection.Callback mediaProjectionCallback) {
68 this.mediaProjectionPermissionResultData = mediaProjectionPermissionResultData;
69 this.mediaProjectionCallback = mediaProjectionCallback;
70 }
71
72 private void checkNotDisposed() {
73 if (isDisposed) {
74 throw new RuntimeException("capturer is disposed.");
75 }
76 }
77
Bin Zhu12e85112020-08-24 14:25:10 -070078 @Nullable
79 public MediaProjection getMediaProjection() {
80 return mediaProjection;
81 }
82
arsanyb75f2542016-08-31 18:50:52 -070083 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -080084 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
85 @SuppressWarnings("NoSynchronizedMethodCheck")
sakalb6760f92016-09-29 04:12:44 -070086 public synchronized void initialize(final SurfaceTextureHelper surfaceTextureHelper,
Sami Kalliomäkib2daaaa2018-07-06 11:25:25 +020087 final Context applicationContext, final CapturerObserver capturerObserver) {
arsanyb75f2542016-08-31 18:50:52 -070088 checkNotDisposed();
89
90 if (capturerObserver == null) {
91 throw new RuntimeException("capturerObserver not set.");
92 }
93 this.capturerObserver = capturerObserver;
94
95 if (surfaceTextureHelper == null) {
96 throw new RuntimeException("surfaceTextureHelper not set.");
97 }
98 this.surfaceTextureHelper = surfaceTextureHelper;
99
sakalb6760f92016-09-29 04:12:44 -0700100 mediaProjectionManager = (MediaProjectionManager) applicationContext.getSystemService(
101 Context.MEDIA_PROJECTION_SERVICE);
arsanyb75f2542016-08-31 18:50:52 -0700102 }
103
arsanyb75f2542016-08-31 18:50:52 -0700104 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800105 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
106 @SuppressWarnings("NoSynchronizedMethodCheck")
sakalb6760f92016-09-29 04:12:44 -0700107 public synchronized void startCapture(
108 final int width, final int height, final int ignoredFramerate) {
arsanyb75f2542016-08-31 18:50:52 -0700109 checkNotDisposed();
110
111 this.width = width;
112 this.height = height;
113
114 mediaProjection = mediaProjectionManager.getMediaProjection(
115 Activity.RESULT_OK, mediaProjectionPermissionResultData);
116
117 // Let MediaProjection callback use the SurfaceTextureHelper thread.
118 mediaProjection.registerCallback(mediaProjectionCallback, surfaceTextureHelper.getHandler());
119
120 createVirtualDisplay();
121 capturerObserver.onCapturerStarted(true);
122 surfaceTextureHelper.startListening(ScreenCapturerAndroid.this);
123 }
124
125 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800126 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
127 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700128 public synchronized void stopCapture() {
129 checkNotDisposed();
130 ThreadUtils.invokeAtFrontUninterruptibly(surfaceTextureHelper.getHandler(), new Runnable() {
131 @Override
132 public void run() {
133 surfaceTextureHelper.stopListening();
134 capturerObserver.onCapturerStopped();
135
136 if (virtualDisplay != null) {
137 virtualDisplay.release();
138 virtualDisplay = null;
139 }
140
141 if (mediaProjection != null) {
142 // Unregister the callback before stopping, otherwise the callback recursively
143 // calls this method.
144 mediaProjection.unregisterCallback(mediaProjectionCallback);
145 mediaProjection.stop();
146 mediaProjection = null;
147 }
148 }
149 });
150 }
151
arsanyb75f2542016-08-31 18:50:52 -0700152 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800153 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
154 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700155 public synchronized void dispose() {
156 isDisposed = true;
157 }
158
arsanyb75f2542016-08-31 18:50:52 -0700159 /**
160 * Changes output video format. This method can be used to scale the output
161 * video, or to change orientation when the captured screen is rotated for example.
162 *
163 * @param width new output video width
164 * @param height new output video height
165 * @param ignoredFramerate ignored
166 */
167 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800168 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
169 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700170 public synchronized void changeCaptureFormat(
171 final int width, final int height, final int ignoredFramerate) {
172 checkNotDisposed();
173
174 this.width = width;
175 this.height = height;
176
177 if (virtualDisplay == null) {
178 // Capturer is stopped, the virtual display will be created in startCaptuer().
179 return;
180 }
181
182 // Create a new virtual display on the surfaceTextureHelper thread to avoid interference
183 // with frame processing, which happens on the same thread (we serialize events by running
184 // them on the same thread).
185 ThreadUtils.invokeAtFrontUninterruptibly(surfaceTextureHelper.getHandler(), new Runnable() {
186 @Override
187 public void run() {
188 virtualDisplay.release();
189 createVirtualDisplay();
190 }
191 });
192 }
193
194 private void createVirtualDisplay() {
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200195 surfaceTextureHelper.setTextureSize(width, height);
sakalb6760f92016-09-29 04:12:44 -0700196 virtualDisplay = mediaProjection.createVirtualDisplay("WebRTC_ScreenCapture", width, height,
197 VIRTUAL_DISPLAY_DPI, DISPLAY_FLAGS, new Surface(surfaceTextureHelper.getSurfaceTexture()),
arsanyb75f2542016-08-31 18:50:52 -0700198 null /* callback */, null /* callback handler */);
199 }
200
201 // This is called on the internal looper thread of {@Code SurfaceTextureHelper}.
202 @Override
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200203 public void onFrame(VideoFrame frame) {
arsanyb75f2542016-08-31 18:50:52 -0700204 numCapturedFrames++;
Sami Kalliomäki682dc612018-01-26 11:06:45 +0100205 capturerObserver.onFrameCaptured(frame);
arsanyb75f2542016-08-31 18:50:52 -0700206 }
207
208 @Override
209 public boolean isScreencast() {
210 return true;
211 }
212
213 public long getNumCapturedFrames() {
214 return numCapturedFrames;
215 }
216}