blob: 8b69a5123d069266db6865154cfa47db61e77690 [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;
22
23import java.util.ArrayList;
24import java.util.List;
25
26/**
27 * An implementation of VideoCapturer to capture the screen content as a video stream.
28 * Capturing is done by {@code MediaProjection} on a {@code SurfaceTexture}. We interact with this
29 * {@code SurfaceTexture} using a {@code SurfaceTextureHelper}.
30 * The {@code SurfaceTextureHelper} is created by the native code and passed to this capturer in
31 * {@code VideoCapturer.initialize()}. On receiving a new frame, this capturer passes it
32 * as a texture to the native code via {@code CapturerObserver.onTextureFrameCaptured()}. This takes
33 * place on the HandlerThread of the given {@code SurfaceTextureHelper}. When done with each frame,
34 * the native code returns the buffer to the {@code SurfaceTextureHelper} to be used for new
35 * frames. At any time, at most one frame is being processed.
36 */
37@TargetApi(21)
sakalb6760f92016-09-29 04:12:44 -070038public class ScreenCapturerAndroid
39 implements VideoCapturer, SurfaceTextureHelper.OnTextureFrameAvailableListener {
40 private static final int DISPLAY_FLAGS =
41 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
arsanyb75f2542016-08-31 18:50:52 -070042 // DPI for VirtualDisplay, does not seem to matter for us.
43 private static final int VIRTUAL_DISPLAY_DPI = 400;
44
45 private final Intent mediaProjectionPermissionResultData;
46 private final MediaProjection.Callback mediaProjectionCallback;
47
48 private int width;
49 private int height;
50 private VirtualDisplay virtualDisplay;
51 private SurfaceTextureHelper surfaceTextureHelper;
52 private CapturerObserver capturerObserver;
53 private long numCapturedFrames = 0;
54 private MediaProjection mediaProjection;
55 private boolean isDisposed = false;
56 private MediaProjectionManager mediaProjectionManager;
57
58 /**
59 * Constructs a new Screen Capturer.
60 *
61 * @param mediaProjectionPermissionResultData the result data of MediaProjection permission
62 * activity; the calling app must validate that result code is Activity.RESULT_OK before
63 * calling this method.
64 * @param mediaProjectionCallback MediaProjection callback to implement application specific
65 * logic in events such as when the user revokes a previously granted capture permission.
66 **/
sakalb6760f92016-09-29 04:12:44 -070067 public ScreenCapturerAndroid(Intent mediaProjectionPermissionResultData,
arsanyb75f2542016-08-31 18:50:52 -070068 MediaProjection.Callback mediaProjectionCallback) {
69 this.mediaProjectionPermissionResultData = mediaProjectionPermissionResultData;
70 this.mediaProjectionCallback = mediaProjectionCallback;
71 }
72
73 private void checkNotDisposed() {
74 if (isDisposed) {
75 throw new RuntimeException("capturer is disposed.");
76 }
77 }
78
79 @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,
83 final Context applicationContext, final VideoCapturer.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() {
191 surfaceTextureHelper.getSurfaceTexture().setDefaultBufferSize(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
199 public void onTextureFrameAvailable(int oesTextureId, float[] transformMatrix, long timestampNs) {
200 numCapturedFrames++;
sakalb6760f92016-09-29 04:12:44 -0700201 capturerObserver.onTextureFrameCaptured(
202 width, height, oesTextureId, transformMatrix, 0 /* rotation */, timestampNs);
arsanyb75f2542016-08-31 18:50:52 -0700203 }
204
205 @Override
206 public boolean isScreencast() {
207 return true;
208 }
209
210 public long getNumCapturedFrames() {
211 return numCapturedFrames;
212 }
213}