blob: fef28f5ac19389be9e2b2269408535a16fb7fffd [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;
21import android.view.Surface;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010022import javax.annotation.Nullable;
arsanyb75f2542016-08-31 18:50:52 -070023
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
78 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -080079 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
80 @SuppressWarnings("NoSynchronizedMethodCheck")
sakalb6760f92016-09-29 04:12:44 -070081 public synchronized void initialize(final SurfaceTextureHelper surfaceTextureHelper,
Sami Kalliomäkib2daaaa2018-07-06 11:25:25 +020082 final Context applicationContext, final CapturerObserver capturerObserver) {
arsanyb75f2542016-08-31 18:50:52 -070083 checkNotDisposed();
84
85 if (capturerObserver == null) {
86 throw new RuntimeException("capturerObserver not set.");
87 }
88 this.capturerObserver = capturerObserver;
89
90 if (surfaceTextureHelper == null) {
91 throw new RuntimeException("surfaceTextureHelper not set.");
92 }
93 this.surfaceTextureHelper = surfaceTextureHelper;
94
sakalb6760f92016-09-29 04:12:44 -070095 mediaProjectionManager = (MediaProjectionManager) applicationContext.getSystemService(
96 Context.MEDIA_PROJECTION_SERVICE);
arsanyb75f2542016-08-31 18:50:52 -070097 }
98
arsanyb75f2542016-08-31 18:50:52 -070099 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800100 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
101 @SuppressWarnings("NoSynchronizedMethodCheck")
sakalb6760f92016-09-29 04:12:44 -0700102 public synchronized void startCapture(
103 final int width, final int height, final int ignoredFramerate) {
arsanyb75f2542016-08-31 18:50:52 -0700104 checkNotDisposed();
105
106 this.width = width;
107 this.height = height;
108
109 mediaProjection = mediaProjectionManager.getMediaProjection(
110 Activity.RESULT_OK, mediaProjectionPermissionResultData);
111
112 // Let MediaProjection callback use the SurfaceTextureHelper thread.
113 mediaProjection.registerCallback(mediaProjectionCallback, surfaceTextureHelper.getHandler());
114
115 createVirtualDisplay();
116 capturerObserver.onCapturerStarted(true);
117 surfaceTextureHelper.startListening(ScreenCapturerAndroid.this);
118 }
119
120 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800121 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
122 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700123 public synchronized void stopCapture() {
124 checkNotDisposed();
125 ThreadUtils.invokeAtFrontUninterruptibly(surfaceTextureHelper.getHandler(), new Runnable() {
126 @Override
127 public void run() {
128 surfaceTextureHelper.stopListening();
129 capturerObserver.onCapturerStopped();
130
131 if (virtualDisplay != null) {
132 virtualDisplay.release();
133 virtualDisplay = null;
134 }
135
136 if (mediaProjection != null) {
137 // Unregister the callback before stopping, otherwise the callback recursively
138 // calls this method.
139 mediaProjection.unregisterCallback(mediaProjectionCallback);
140 mediaProjection.stop();
141 mediaProjection = null;
142 }
143 }
144 });
145 }
146
arsanyb75f2542016-08-31 18:50:52 -0700147 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800148 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
149 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700150 public synchronized void dispose() {
151 isDisposed = true;
152 }
153
arsanyb75f2542016-08-31 18:50:52 -0700154 /**
155 * Changes output video format. This method can be used to scale the output
156 * video, or to change orientation when the captured screen is rotated for example.
157 *
158 * @param width new output video width
159 * @param height new output video height
160 * @param ignoredFramerate ignored
161 */
162 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800163 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
164 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700165 public synchronized void changeCaptureFormat(
166 final int width, final int height, final int ignoredFramerate) {
167 checkNotDisposed();
168
169 this.width = width;
170 this.height = height;
171
172 if (virtualDisplay == null) {
173 // Capturer is stopped, the virtual display will be created in startCaptuer().
174 return;
175 }
176
177 // Create a new virtual display on the surfaceTextureHelper thread to avoid interference
178 // with frame processing, which happens on the same thread (we serialize events by running
179 // them on the same thread).
180 ThreadUtils.invokeAtFrontUninterruptibly(surfaceTextureHelper.getHandler(), new Runnable() {
181 @Override
182 public void run() {
183 virtualDisplay.release();
184 createVirtualDisplay();
185 }
186 });
187 }
188
189 private void createVirtualDisplay() {
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200190 surfaceTextureHelper.setTextureSize(width, height);
sakalb6760f92016-09-29 04:12:44 -0700191 virtualDisplay = mediaProjection.createVirtualDisplay("WebRTC_ScreenCapture", width, height,
192 VIRTUAL_DISPLAY_DPI, DISPLAY_FLAGS, new Surface(surfaceTextureHelper.getSurfaceTexture()),
arsanyb75f2542016-08-31 18:50:52 -0700193 null /* callback */, null /* callback handler */);
194 }
195
196 // This is called on the internal looper thread of {@Code SurfaceTextureHelper}.
197 @Override
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200198 public void onFrame(VideoFrame frame) {
arsanyb75f2542016-08-31 18:50:52 -0700199 numCapturedFrames++;
Sami Kalliomäki682dc612018-01-26 11:06:45 +0100200 capturerObserver.onFrameCaptured(frame);
arsanyb75f2542016-08-31 18:50:52 -0700201 }
202
203 @Override
204 public boolean isScreencast() {
205 return true;
206 }
207
208 public long getNumCapturedFrames() {
209 return numCapturedFrames;
210 }
211}