blob: 1b5dde74e925c40371006afd34d07b4ff0f9326a [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
30 * as a texture to the native code via {@code CapturerObserver.onTextureFrameCaptured()}. This takes
31 * 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.
34 */
35@TargetApi(21)
sakalb6760f92016-09-29 04:12:44 -070036public class ScreenCapturerAndroid
37 implements VideoCapturer, SurfaceTextureHelper.OnTextureFrameAvailableListener {
38 private static final int DISPLAY_FLAGS =
39 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
arsanyb75f2542016-08-31 18:50:52 -070040 // DPI for VirtualDisplay, does not seem to matter for us.
41 private static final int VIRTUAL_DISPLAY_DPI = 400;
42
43 private final Intent mediaProjectionPermissionResultData;
44 private final MediaProjection.Callback mediaProjectionCallback;
45
46 private int width;
47 private int height;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010048 @Nullable private VirtualDisplay virtualDisplay;
49 @Nullable private SurfaceTextureHelper surfaceTextureHelper;
50 @Nullable private CapturerObserver capturerObserver;
arsanyb75f2542016-08-31 18:50:52 -070051 private long numCapturedFrames = 0;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010052 @Nullable private MediaProjection mediaProjection;
arsanyb75f2542016-08-31 18:50:52 -070053 private boolean isDisposed = false;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010054 @Nullable private MediaProjectionManager mediaProjectionManager;
arsanyb75f2542016-08-31 18:50:52 -070055
56 /**
57 * Constructs a new Screen Capturer.
58 *
59 * @param mediaProjectionPermissionResultData the result data of MediaProjection permission
60 * activity; the calling app must validate that result code is Activity.RESULT_OK before
61 * calling this method.
62 * @param mediaProjectionCallback MediaProjection callback to implement application specific
63 * logic in events such as when the user revokes a previously granted capture permission.
64 **/
sakalb6760f92016-09-29 04:12:44 -070065 public ScreenCapturerAndroid(Intent mediaProjectionPermissionResultData,
arsanyb75f2542016-08-31 18:50:52 -070066 MediaProjection.Callback mediaProjectionCallback) {
67 this.mediaProjectionPermissionResultData = mediaProjectionPermissionResultData;
68 this.mediaProjectionCallback = mediaProjectionCallback;
69 }
70
71 private void checkNotDisposed() {
72 if (isDisposed) {
73 throw new RuntimeException("capturer is disposed.");
74 }
75 }
76
77 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -080078 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
79 @SuppressWarnings("NoSynchronizedMethodCheck")
sakalb6760f92016-09-29 04:12:44 -070080 public synchronized void initialize(final SurfaceTextureHelper surfaceTextureHelper,
81 final Context applicationContext, final VideoCapturer.CapturerObserver capturerObserver) {
arsanyb75f2542016-08-31 18:50:52 -070082 checkNotDisposed();
83
84 if (capturerObserver == null) {
85 throw new RuntimeException("capturerObserver not set.");
86 }
87 this.capturerObserver = capturerObserver;
88
89 if (surfaceTextureHelper == null) {
90 throw new RuntimeException("surfaceTextureHelper not set.");
91 }
92 this.surfaceTextureHelper = surfaceTextureHelper;
93
sakalb6760f92016-09-29 04:12:44 -070094 mediaProjectionManager = (MediaProjectionManager) applicationContext.getSystemService(
95 Context.MEDIA_PROJECTION_SERVICE);
arsanyb75f2542016-08-31 18:50:52 -070096 }
97
arsanyb75f2542016-08-31 18:50:52 -070098 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -080099 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
100 @SuppressWarnings("NoSynchronizedMethodCheck")
sakalb6760f92016-09-29 04:12:44 -0700101 public synchronized void startCapture(
102 final int width, final int height, final int ignoredFramerate) {
arsanyb75f2542016-08-31 18:50:52 -0700103 checkNotDisposed();
104
105 this.width = width;
106 this.height = height;
107
108 mediaProjection = mediaProjectionManager.getMediaProjection(
109 Activity.RESULT_OK, mediaProjectionPermissionResultData);
110
111 // Let MediaProjection callback use the SurfaceTextureHelper thread.
112 mediaProjection.registerCallback(mediaProjectionCallback, surfaceTextureHelper.getHandler());
113
114 createVirtualDisplay();
115 capturerObserver.onCapturerStarted(true);
116 surfaceTextureHelper.startListening(ScreenCapturerAndroid.this);
117 }
118
119 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800120 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
121 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700122 public synchronized void stopCapture() {
123 checkNotDisposed();
124 ThreadUtils.invokeAtFrontUninterruptibly(surfaceTextureHelper.getHandler(), new Runnable() {
125 @Override
126 public void run() {
127 surfaceTextureHelper.stopListening();
128 capturerObserver.onCapturerStopped();
129
130 if (virtualDisplay != null) {
131 virtualDisplay.release();
132 virtualDisplay = null;
133 }
134
135 if (mediaProjection != null) {
136 // Unregister the callback before stopping, otherwise the callback recursively
137 // calls this method.
138 mediaProjection.unregisterCallback(mediaProjectionCallback);
139 mediaProjection.stop();
140 mediaProjection = null;
141 }
142 }
143 });
144 }
145
arsanyb75f2542016-08-31 18:50:52 -0700146 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800147 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
148 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700149 public synchronized void dispose() {
150 isDisposed = true;
151 }
152
arsanyb75f2542016-08-31 18:50:52 -0700153 /**
154 * Changes output video format. This method can be used to scale the output
155 * video, or to change orientation when the captured screen is rotated for example.
156 *
157 * @param width new output video width
158 * @param height new output video height
159 * @param ignoredFramerate ignored
160 */
161 @Override
Mirko Bonadei12251b62017-11-05 19:35:31 -0800162 // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
163 @SuppressWarnings("NoSynchronizedMethodCheck")
arsanyb75f2542016-08-31 18:50:52 -0700164 public synchronized void changeCaptureFormat(
165 final int width, final int height, final int ignoredFramerate) {
166 checkNotDisposed();
167
168 this.width = width;
169 this.height = height;
170
171 if (virtualDisplay == null) {
172 // Capturer is stopped, the virtual display will be created in startCaptuer().
173 return;
174 }
175
176 // Create a new virtual display on the surfaceTextureHelper thread to avoid interference
177 // with frame processing, which happens on the same thread (we serialize events by running
178 // them on the same thread).
179 ThreadUtils.invokeAtFrontUninterruptibly(surfaceTextureHelper.getHandler(), new Runnable() {
180 @Override
181 public void run() {
182 virtualDisplay.release();
183 createVirtualDisplay();
184 }
185 });
186 }
187
188 private void createVirtualDisplay() {
189 surfaceTextureHelper.getSurfaceTexture().setDefaultBufferSize(width, height);
sakalb6760f92016-09-29 04:12:44 -0700190 virtualDisplay = mediaProjection.createVirtualDisplay("WebRTC_ScreenCapture", width, height,
191 VIRTUAL_DISPLAY_DPI, DISPLAY_FLAGS, new Surface(surfaceTextureHelper.getSurfaceTexture()),
arsanyb75f2542016-08-31 18:50:52 -0700192 null /* callback */, null /* callback handler */);
193 }
194
195 // This is called on the internal looper thread of {@Code SurfaceTextureHelper}.
196 @Override
197 public void onTextureFrameAvailable(int oesTextureId, float[] transformMatrix, long timestampNs) {
198 numCapturedFrames++;
Sami Kalliomäki682dc612018-01-26 11:06:45 +0100199 final VideoFrame.Buffer buffer = surfaceTextureHelper.createTextureBuffer(
200 width, height, RendererCommon.convertMatrixToAndroidGraphicsMatrix(transformMatrix));
201 final VideoFrame frame = new VideoFrame(buffer, 0 /* rotation */, timestampNs);
202 capturerObserver.onFrameCaptured(frame);
203 frame.release();
arsanyb75f2542016-08-31 18:50:52 -0700204 }
205
206 @Override
207 public boolean isScreencast() {
208 return true;
209 }
210
211 public long getNumCapturedFrames() {
212 return numCapturedFrames;
213 }
214}