xref: /aosp_15_r20/external/webrtc/sdk/android/api/org/webrtc/ScreenCapturerAndroid.java (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
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 
11 package org.webrtc;
12 
13 import android.app.Activity;
14 import android.content.Context;
15 import android.content.Intent;
16 import android.hardware.display.DisplayManager;
17 import android.hardware.display.VirtualDisplay;
18 import android.media.projection.MediaProjection;
19 import android.media.projection.MediaProjectionManager;
20 import android.view.Surface;
21 import androidx.annotation.Nullable;
22 
23 /**
24  * An implementation of VideoCapturer to capture the screen content as a video stream.
25  * Capturing is done by {@code MediaProjection} on a {@code SurfaceTexture}. We interact with this
26  * {@code SurfaceTexture} using a {@code SurfaceTextureHelper}.
27  * The {@code SurfaceTextureHelper} is created by the native code and passed to this capturer in
28  * {@code VideoCapturer.initialize()}. On receiving a new frame, this capturer passes it
29  * as a texture to the native code via {@code CapturerObserver.onFrameCaptured()}. This takes
30  * place on the HandlerThread of the given {@code SurfaceTextureHelper}. When done with each frame,
31  * the native code returns the buffer to the  {@code SurfaceTextureHelper} to be used for new
32  * frames. At any time, at most one frame is being processed.
33  */
34 public class ScreenCapturerAndroid implements VideoCapturer, VideoSink {
35   private static final int DISPLAY_FLAGS =
36       DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
37   // DPI for VirtualDisplay, does not seem to matter for us.
38   private static final int VIRTUAL_DISPLAY_DPI = 400;
39 
40   private final Intent mediaProjectionPermissionResultData;
41   private final MediaProjection.Callback mediaProjectionCallback;
42 
43   private int width;
44   private int height;
45   @Nullable private VirtualDisplay virtualDisplay;
46   @Nullable private SurfaceTextureHelper surfaceTextureHelper;
47   @Nullable private CapturerObserver capturerObserver;
48   private long numCapturedFrames;
49   @Nullable private MediaProjection mediaProjection;
50   private boolean isDisposed;
51   @Nullable private MediaProjectionManager mediaProjectionManager;
52 
53   /**
54    * Constructs a new Screen Capturer.
55    *
56    * @param mediaProjectionPermissionResultData the result data of MediaProjection permission
57    *     activity; the calling app must validate that result code is Activity.RESULT_OK before
58    *     calling this method.
59    * @param mediaProjectionCallback MediaProjection callback to implement application specific
60    *     logic in events such as when the user revokes a previously granted capture permission.
61   **/
ScreenCapturerAndroid(Intent mediaProjectionPermissionResultData, MediaProjection.Callback mediaProjectionCallback)62   public ScreenCapturerAndroid(Intent mediaProjectionPermissionResultData,
63       MediaProjection.Callback mediaProjectionCallback) {
64     this.mediaProjectionPermissionResultData = mediaProjectionPermissionResultData;
65     this.mediaProjectionCallback = mediaProjectionCallback;
66   }
67 
checkNotDisposed()68   private void checkNotDisposed() {
69     if (isDisposed) {
70       throw new RuntimeException("capturer is disposed.");
71     }
72   }
73 
74   @Nullable
getMediaProjection()75   public MediaProjection getMediaProjection() {
76     return mediaProjection;
77   }
78 
79   @Override
80   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
81   @SuppressWarnings("NoSynchronizedMethodCheck")
initialize(final SurfaceTextureHelper surfaceTextureHelper, final Context applicationContext, final CapturerObserver capturerObserver)82   public synchronized void initialize(final SurfaceTextureHelper surfaceTextureHelper,
83       final Context applicationContext, final CapturerObserver capturerObserver) {
84     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 
96     mediaProjectionManager = (MediaProjectionManager) applicationContext.getSystemService(
97         Context.MEDIA_PROJECTION_SERVICE);
98   }
99 
100   @Override
101   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
102   @SuppressWarnings("NoSynchronizedMethodCheck")
startCapture( final int width, final int height, final int ignoredFramerate)103   public synchronized void startCapture(
104       final int width, final int height, final int ignoredFramerate) {
105     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
122   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
123   @SuppressWarnings("NoSynchronizedMethodCheck")
stopCapture()124   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 
148   @Override
149   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
150   @SuppressWarnings("NoSynchronizedMethodCheck")
dispose()151   public synchronized void dispose() {
152     isDisposed = true;
153   }
154 
155   /**
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
164   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
165   @SuppressWarnings("NoSynchronizedMethodCheck")
changeCaptureFormat( final int width, final int height, final int ignoredFramerate)166   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 
createVirtualDisplay()190   private void createVirtualDisplay() {
191     surfaceTextureHelper.setTextureSize(width, height);
192     virtualDisplay = mediaProjection.createVirtualDisplay("WebRTC_ScreenCapture", width, height,
193         VIRTUAL_DISPLAY_DPI, DISPLAY_FLAGS, new Surface(surfaceTextureHelper.getSurfaceTexture()),
194         null /* callback */, null /* callback handler */);
195   }
196 
197   // This is called on the internal looper thread of {@Code SurfaceTextureHelper}.
198   @Override
onFrame(VideoFrame frame)199   public void onFrame(VideoFrame frame) {
200     numCapturedFrames++;
201     capturerObserver.onFrameCaptured(frame);
202   }
203 
204   @Override
isScreencast()205   public boolean isScreencast() {
206     return true;
207   }
208 
getNumCapturedFrames()209   public long getNumCapturedFrames() {
210     return numCapturedFrames;
211   }
212 }
213