blob: 08d34dda8ab3b472b07b83523425969bd76b635e [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
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
sakalb6760f92016-09-29 04:12:44 -070099 public synchronized void startCapture(
100 final int width, final int height, final int ignoredFramerate) {
arsanyb75f2542016-08-31 18:50:52 -0700101 checkNotDisposed();
102
103 this.width = width;
104 this.height = height;
105
106 mediaProjection = mediaProjectionManager.getMediaProjection(
107 Activity.RESULT_OK, mediaProjectionPermissionResultData);
108
109 // Let MediaProjection callback use the SurfaceTextureHelper thread.
110 mediaProjection.registerCallback(mediaProjectionCallback, surfaceTextureHelper.getHandler());
111
112 createVirtualDisplay();
113 capturerObserver.onCapturerStarted(true);
114 surfaceTextureHelper.startListening(ScreenCapturerAndroid.this);
115 }
116
117 @Override
118 public synchronized void stopCapture() {
119 checkNotDisposed();
120 ThreadUtils.invokeAtFrontUninterruptibly(surfaceTextureHelper.getHandler(), new Runnable() {
121 @Override
122 public void run() {
123 surfaceTextureHelper.stopListening();
124 capturerObserver.onCapturerStopped();
125
126 if (virtualDisplay != null) {
127 virtualDisplay.release();
128 virtualDisplay = null;
129 }
130
131 if (mediaProjection != null) {
132 // Unregister the callback before stopping, otherwise the callback recursively
133 // calls this method.
134 mediaProjection.unregisterCallback(mediaProjectionCallback);
135 mediaProjection.stop();
136 mediaProjection = null;
137 }
138 }
139 });
140 }
141
arsanyb75f2542016-08-31 18:50:52 -0700142 @Override
143 public synchronized void dispose() {
144 isDisposed = true;
145 }
146
arsanyb75f2542016-08-31 18:50:52 -0700147 /**
148 * Changes output video format. This method can be used to scale the output
149 * video, or to change orientation when the captured screen is rotated for example.
150 *
151 * @param width new output video width
152 * @param height new output video height
153 * @param ignoredFramerate ignored
154 */
155 @Override
156 public synchronized void changeCaptureFormat(
157 final int width, final int height, final int ignoredFramerate) {
158 checkNotDisposed();
159
160 this.width = width;
161 this.height = height;
162
163 if (virtualDisplay == null) {
164 // Capturer is stopped, the virtual display will be created in startCaptuer().
165 return;
166 }
167
168 // Create a new virtual display on the surfaceTextureHelper thread to avoid interference
169 // with frame processing, which happens on the same thread (we serialize events by running
170 // them on the same thread).
171 ThreadUtils.invokeAtFrontUninterruptibly(surfaceTextureHelper.getHandler(), new Runnable() {
172 @Override
173 public void run() {
174 virtualDisplay.release();
175 createVirtualDisplay();
176 }
177 });
178 }
179
180 private void createVirtualDisplay() {
181 surfaceTextureHelper.getSurfaceTexture().setDefaultBufferSize(width, height);
sakalb6760f92016-09-29 04:12:44 -0700182 virtualDisplay = mediaProjection.createVirtualDisplay("WebRTC_ScreenCapture", width, height,
183 VIRTUAL_DISPLAY_DPI, DISPLAY_FLAGS, new Surface(surfaceTextureHelper.getSurfaceTexture()),
arsanyb75f2542016-08-31 18:50:52 -0700184 null /* callback */, null /* callback handler */);
185 }
186
187 // This is called on the internal looper thread of {@Code SurfaceTextureHelper}.
188 @Override
189 public void onTextureFrameAvailable(int oesTextureId, float[] transformMatrix, long timestampNs) {
190 numCapturedFrames++;
sakalb6760f92016-09-29 04:12:44 -0700191 capturerObserver.onTextureFrameCaptured(
192 width, height, oesTextureId, transformMatrix, 0 /* rotation */, timestampNs);
arsanyb75f2542016-08-31 18:50:52 -0700193 }
194
195 @Override
196 public boolean isScreencast() {
197 return true;
198 }
199
200 public long getNumCapturedFrames() {
201 return numCapturedFrames;
202 }
203}