Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(414)

Side by Side Diff: media/capture/content/android/java/src/org/chromium/media/ScreenCapture.java

Issue 2125973002: Reland: ScreenCapture for Android phase1, part I (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: add dependency to mojo/interfaces:image_capture Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.media;
6
7 import android.annotation.TargetApi;
8 import android.app.Activity;
9 import android.app.Fragment;
10 import android.app.FragmentManager;
11 import android.app.FragmentTransaction;
12 import android.content.Context;
13 import android.content.Intent;
14 import android.graphics.ImageFormat;
15 import android.graphics.PixelFormat;
16 import android.hardware.display.DisplayManager;
17 import android.hardware.display.VirtualDisplay;
18 import android.media.Image;
19 import android.media.ImageReader;
20 import android.media.projection.MediaProjection;
21 import android.media.projection.MediaProjectionManager;
22 import android.os.Build;
23 import android.os.Handler;
24 import android.os.HandlerThread;
25 import android.util.DisplayMetrics;
26 import android.view.Display;
27 import android.view.Surface;
28
29 import org.chromium.base.ApplicationStatus;
30 import org.chromium.base.Log;
31 import org.chromium.base.annotations.CalledByNative;
32 import org.chromium.base.annotations.JNINamespace;
33
34 import java.nio.ByteBuffer;
35
36 /**
37 * This class implements Screen Capture using projection API, introduced in Andr oid
38 * API 21 (L Release). Capture takes place in the current Looper, while pixel
39 * download takes place in another thread used by ImageReader.
40 **/
41 @JNINamespace("media")
42 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
43 public class ScreenCapture extends Fragment {
44 private static final String TAG = "ScreenCaptureMachine";
45
46 private static final int REQUEST_MEDIA_PROJECTION = 1;
47
48 // Native callback context variable.
49 private final long mNativeScreenCaptureMachineAndroid;
50 private final Context mContext;
51
52 private static enum CaptureState { ATTACHED, ALLOWED, STARTED, STOPPING, STO PPED }
53 private final Object mCaptureStateLock = new Object();
54 private CaptureState mCaptureState = CaptureState.STOPPED;
55
56 private MediaProjection mMediaProjection;
57 private MediaProjectionManager mMediaProjectionManager;
58 private VirtualDisplay mVirtualDisplay;
59 private Surface mSurface;
60 private ImageReader mImageReader = null;
61 private HandlerThread mThread;
62 private Handler mBackgroundHandler;
63
64 private int mScreenDensity;
65 private int mWidth;
66 private int mHeight;
67 private int mFormat;
68 private int mResultCode;
69 private Intent mResultData;
70
71 ScreenCapture(Context context, long nativeScreenCaptureMachineAndroid) {
72 mContext = context;
73 mNativeScreenCaptureMachineAndroid = nativeScreenCaptureMachineAndroid;
74
75 Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
76 if (activity == null) {
77 Log.e(TAG, "activity is null");
78 return;
79 }
80
81 FragmentManager fragmentManager = activity.getFragmentManager();
82 FragmentTransaction fragmentTransaction = fragmentManager.beginTransacti on();
83 fragmentTransaction.add(this, "screencapture");
84
85 try {
86 fragmentTransaction.commit();
87 } catch (RuntimeException e) {
88 Log.e(TAG, "ScreenCaptureExcaption " + e);
89 }
90
91 DisplayMetrics metrics = new DisplayMetrics();
92 Display display = activity.getWindowManager().getDefaultDisplay();
93 display.getMetrics(metrics);
94 mScreenDensity = metrics.densityDpi;
95 }
96
97 // Factory method.
98 @CalledByNative
99 static ScreenCapture createScreenCaptureMachine(
100 Context context, long nativeScreenCaptureMachineAndroid) {
101 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
102 return new ScreenCapture(context, nativeScreenCaptureMachineAndroid) ;
103 }
104 return null;
105 }
106
107 // Internal class implementing the ImageReader listener. Gets pinged when a
108 // new frame is been captured and downloaded to memory-backed buffers.
109 // TODO(braveyao): This is very similar as the one in VideoCaptureCamera2. T ry to see
110 // if we can extend from it, https://crbug.com/487935.
111 private class CrImageReaderListener implements ImageReader.OnImageAvailableL istener {
112 @Override
113 public void onImageAvailable(ImageReader reader) {
114 synchronized (mCaptureStateLock) {
115 if (mCaptureState != CaptureState.STARTED) {
116 Log.e(TAG, "Get captured frame in unexpected state.");
117 return;
118 }
119 }
120
121 try (Image image = reader.acquireLatestImage()) {
122 if (image == null) return;
123 if (reader.getWidth() != image.getWidth()
124 || reader.getHeight() != image.getHeight()) {
125 Log.e(TAG, "ImageReader size (" + reader.getWidth() + "x" + reader.getHeight()
126 + ") did not match Image size (" + image.get Width() + "x"
127 + image.getHeight() + ")");
128 throw new IllegalStateException();
129 }
130
131 switch (image.getFormat()) {
132 case PixelFormat.RGBA_8888:
133 if (image.getPlanes().length != 1) {
134 Log.e(TAG, "Unexpected image planes for RGBA_8888 fo rmat: "
135 + image.getPlanes().length);
136 throw new IllegalStateException();
137 }
138
139 nativeOnRGBAFrameAvailable(mNativeScreenCaptureMachineAn droid,
140 image.getPlanes()[0].getBuffer(),
141 image.getPlanes()[0].getRowStride(), image.getCr opRect().left,
142 image.getCropRect().top, image.getCropRect().wid th(),
143 image.getCropRect().height(), image.getTimestamp ());
144 break;
145 case ImageFormat.YUV_420_888:
146 if (image.getPlanes().length != 3) {
147 Log.e(TAG, "Unexpected image planes for YUV_420_888 format: "
148 + image.getPlanes().length);
149 throw new IllegalStateException();
150 }
151
152 // The pixel stride of Y plane is always 1. The U/V plan es are guaranteed
153 // to have the same row stride and pixel stride.
154 nativeOnI420FrameAvailable(mNativeScreenCaptureMachineAn droid,
155 image.getPlanes()[0].getBuffer(),
156 image.getPlanes()[0].getRowStride(),
157 image.getPlanes()[1].getBuffer(), image.getPlane s()[2].getBuffer(),
158 image.getPlanes()[1].getRowStride(),
159 image.getPlanes()[1].getPixelStride(), image.get CropRect().left,
160 image.getCropRect().top, image.getCropRect().wid th(),
161 image.getCropRect().height(), image.getTimestamp ());
162 break;
163 default:
164 Log.e(TAG, "Unexpected image format: " + image.getFormat ());
165 throw new IllegalStateException();
166 }
167 } catch (IllegalStateException ex) {
168 Log.e(TAG, "acquireLatestImage():" + ex);
169 } catch (UnsupportedOperationException ex) {
170 Log.i(TAG, "acquireLatestImage():" + ex);
171 // YUV_420_888 is the preference, but not all devices support it ,
172 // fall-back to RGBA_8888 then.
173 mImageReader.close();
174 mVirtualDisplay.release();
175
176 mFormat = PixelFormat.RGBA_8888;
177 createImageReaderWithFormat(mFormat);
178 createVirtualDisplay();
179 }
180 }
181 }
182
183 private class MediaProjectionCallback extends MediaProjection.Callback {
184 @Override
185 public void onStop() {
186 changeCaptureStateAndNotify(CaptureState.STOPPED);
187 mMediaProjection = null;
188 if (mVirtualDisplay == null) return;
189 mVirtualDisplay.release();
190 mVirtualDisplay = null;
191 }
192 }
193
194 @Override
195 public void onAttach(Context context) {
196 super.onAttach(context);
197 Log.d(TAG, "onAttach");
198 changeCaptureStateAndNotify(CaptureState.ATTACHED);
199 }
200
201 // This method was deprecated in API level 23 by onAttach(Context).
202 // TODO(braveyao): remove this method after the minSdkVersion of chrome is 2 3,
203 // https://crbug.com/614172.
204 @SuppressWarnings("deprecation")
205 @Override
206 public void onAttach(Activity activity) {
207 super.onAttach(activity);
208 Log.d(TAG, "onAttach");
209 changeCaptureStateAndNotify(CaptureState.ATTACHED);
210 }
211
212 @Override
213 public void onDetach() {
214 super.onDetach();
215 Log.d(TAG, "onDetach");
216 stopCapture();
217 }
218
219 @CalledByNative
220 public boolean startPrompt(int width, int height) {
221 Log.d(TAG, "startPrompt");
222 synchronized (mCaptureStateLock) {
223 while (mCaptureState != CaptureState.ATTACHED) {
224 try {
225 mCaptureStateLock.wait();
226 } catch (InterruptedException ex) {
227 Log.e(TAG, "ScreenCaptureException: " + ex);
228 }
229 }
230 }
231
232 mWidth = width;
233 mHeight = height;
234
235 mMediaProjectionManager = (MediaProjectionManager) mContext.getSystemSer vice(
236 Context.MEDIA_PROJECTION_SERVICE);
237 if (mMediaProjectionManager == null) {
238 Log.e(TAG, "mMediaProjectionManager is null");
239 return false;
240 }
241
242 try {
243 startActivityForResult(
244 mMediaProjectionManager.createScreenCaptureIntent(), REQUEST _MEDIA_PROJECTION);
245 } catch (android.content.ActivityNotFoundException e) {
246 Log.e(TAG, "ScreenCaptureException " + e);
247 return false;
248 }
249 return true;
250 }
251
252 @Override
253 public void onActivityResult(int requestCode, int resultCode, Intent data) {
254 if (requestCode != REQUEST_MEDIA_PROJECTION) return;
255
256 if (resultCode == Activity.RESULT_OK) {
257 mResultCode = resultCode;
258 mResultData = data;
259 changeCaptureStateAndNotify(CaptureState.ALLOWED);
260 }
261 nativeOnActivityResult(
262 mNativeScreenCaptureMachineAndroid, resultCode == Activity.RESUL T_OK);
263 }
264
265 @CalledByNative
266 public void startCapture() {
267 Log.d(TAG, "startCapture");
268 synchronized (mCaptureStateLock) {
269 if (mCaptureState != CaptureState.ALLOWED) {
270 Log.e(TAG, "startCapture() invoked without user permission.");
271 return;
272 }
273 }
274 mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCod e, mResultData);
275 if (mMediaProjection == null) {
276 Log.e(TAG, "mMediaProjection is null");
277 return;
278 }
279 mMediaProjection.registerCallback(new MediaProjectionCallback(), null);
280
281 mThread = new HandlerThread("ScreenCapture");
282 mThread.start();
283 mBackgroundHandler = new Handler(mThread.getLooper());
284
285 // On Android M and above, YUV420 is prefered. Some Android L devices wi ll silently
286 // fail with YUV420, so keep with RGBA_8888 on L.
287 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
288 mFormat = PixelFormat.RGBA_8888;
289 } else {
290 mFormat = ImageFormat.YUV_420_888;
291 }
292 createImageReaderWithFormat(mFormat);
293 createVirtualDisplay();
294
295 changeCaptureStateAndNotify(CaptureState.STARTED);
296 }
297
298 @CalledByNative
299 public void stopCapture() {
300 Log.d(TAG, "stopCapture");
301 synchronized (mCaptureStateLock) {
302 if (mMediaProjection != null && mCaptureState == CaptureState.STARTE D) {
303 mMediaProjection.stop();
304 changeCaptureStateAndNotify(CaptureState.STOPPING);
305 }
306
307 while (mCaptureState != CaptureState.STOPPED) {
308 try {
309 mCaptureStateLock.wait();
310 } catch (InterruptedException ex) {
311 Log.e(TAG, "ScreenCaptureEvent: " + ex);
312 }
313 }
314 }
315 }
316
317 private void createImageReaderWithFormat(int format) {
318 final int maxImages = 2;
319 mImageReader = ImageReader.newInstance(mWidth, mHeight, format, maxImage s);
320 mSurface = mImageReader.getSurface();
321 final CrImageReaderListener imageReaderListener = new CrImageReaderListe ner();
322 mImageReader.setOnImageAvailableListener(imageReaderListener, mBackgroun dHandler);
323 }
324
325 private void createVirtualDisplay() {
326 mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenCapture", mWidth, mHeight,
327 mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mSurface, null,
328 null);
329 }
330
331 private void changeCaptureStateAndNotify(CaptureState state) {
332 synchronized (mCaptureStateLock) {
333 mCaptureState = state;
334 mCaptureStateLock.notifyAll();
335 }
336 }
337
338 // Method for ScreenCapture implementations to call back native code.
339 private native void nativeOnRGBAFrameAvailable(long nativeScreenCaptureMachi neAndroid,
340 ByteBuffer buf, int left, int top, int width, int height, int rowStr ide,
341 long timestamp);
342 // Method for ScreenCapture implementations to call back native code.
343 private native void nativeOnI420FrameAvailable(long nativeScreenCaptureMachi neAndroid,
344 ByteBuffer yBuffer, int yStride, ByteBuffer uBuffer, ByteBuffer vBuf fer,
345 int uvRowStride, int uvPixelStride, int left, int top, int width, in t height,
346 long timestamp);
347 // Method for ScreenCapture implementations to call back native code.
348 private native void nativeOnActivityResult(
349 long nativeScreenCaptureMachineAndroid, boolean result);
350 }
OLDNEW
« no previous file with comments | « media/capture/content/android/BUILD.gn ('k') | media/capture/content/android/screen_capture_jni_registrar.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698