Chromium Code Reviews| OLD | NEW |
|---|---|
| (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.PixelFormat; | |
| 15 import android.hardware.display.DisplayManager; | |
| 16 import android.hardware.display.VirtualDisplay; | |
| 17 import android.media.Image; | |
| 18 import android.media.ImageReader; | |
| 19 import android.media.projection.MediaProjection; | |
| 20 import android.media.projection.MediaProjectionManager; | |
| 21 import android.os.Build; | |
| 22 import android.os.Handler; | |
| 23 import android.os.HandlerThread; | |
| 24 import android.util.DisplayMetrics; | |
| 25 import android.view.Display; | |
| 26 import android.view.Surface; | |
| 27 | |
| 28 import org.chromium.base.ApplicationStatus; | |
| 29 import org.chromium.base.Log; | |
| 30 import org.chromium.base.annotations.CalledByNative; | |
| 31 import org.chromium.base.annotations.JNINamespace; | |
| 32 | |
| 33 import java.nio.ByteBuffer; | |
| 34 | |
| 35 /** | |
| 36 * This class implements Screen Capture using projection API, introduced in Andr oid | |
| 37 * API 21 (L Release). Capture takes place in the current Looper, while pixel | |
| 38 * download takes place in another thread used by ImageReader. | |
| 39 **/ | |
| 40 @JNINamespace("media") | |
| 41 @TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
| 42 public class ScreenCapture extends Fragment { | |
| 43 // Internal class implementing the ImageReader listener. Gets pinged when a | |
| 44 // new frame is been captured and downloaded to memory-backed buffers. | |
| 45 private class CrImageReaderListener implements ImageReader.OnImageAvailableL istener { | |
| 46 @Override | |
| 47 public void onImageAvailable(ImageReader reader) { | |
| 48 synchronized (mCaptureStateLock) { | |
| 49 if (mCaptureState != CaptureState.STARTED) { | |
| 50 Log.e(TAG, "Get captured frame in unexpected state."); | |
| 51 return; | |
| 52 } | |
| 53 } | |
| 54 | |
| 55 Image image = null; | |
| 56 try { | |
| 57 image = reader.acquireLatestImage(); | |
| 58 if (image == null) return; | |
| 59 if (image.getFormat() != PixelFormat.RGBA_8888) { | |
| 60 Log.e(TAG, "Unexpected image format: " + image.getFormat() + " or #planes: " | |
| 61 + image.getPlanes().length); | |
| 62 return; | |
| 63 } | |
| 64 | |
| 65 Image.Plane[] planes = image.getPlanes(); | |
| 66 ByteBuffer buffer = (ByteBuffer) planes[0].getBuffer(); | |
| 67 buffer.get(mCapturedData); | |
|
miu
2016/05/10 00:29:00
This is copying the image data into a separate byt
braveyao
2016/05/18 00:41:46
Done.
| |
| 68 | |
| 69 nativeOnFrameAvailable(mNativeScreenCaptureMachineAndroid, mCapt uredData, | |
| 70 image.getCropRect().width(), image.getCropRect().height( ), | |
|
miu
2016/05/10 00:29:00
More information needs to be passed to the native
braveyao
2016/05/18 00:41:46
Yes you're right. I meet a case that stride is dif
| |
| 71 image.getTimestamp()); | |
| 72 } catch (IllegalStateException ex) { | |
| 73 Log.e(TAG, "acquireLatestImage():" + ex); | |
| 74 return; | |
| 75 } finally { | |
| 76 if (image != null) image.close(); | |
| 77 } | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 private class MediaProjectionCallback extends MediaProjection.Callback { | |
| 82 @Override | |
| 83 public void onStop() { | |
| 84 changeCaptureStateAndNotify(CaptureState.STOPPED); | |
| 85 mMediaProjection = null; | |
| 86 if (mVirtualDisplay == null) return; | |
| 87 mVirtualDisplay.release(); | |
| 88 mVirtualDisplay = null; | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 private byte[] mCapturedData; | |
| 93 private final Context mContext; | |
| 94 | |
| 95 private MediaProjection mMediaProjection; | |
| 96 private MediaProjectionManager mMediaProjectionManager; | |
| 97 private VirtualDisplay mVirtualDisplay; | |
| 98 private static final int REQUEST_MEDIA_PROJECTION = 1; | |
| 99 | |
| 100 private Surface mSurface; | |
| 101 private ImageReader mImageReader = null; | |
| 102 private int mScreenDensity; | |
| 103 private int mWidth; | |
| 104 private int mHeight; | |
| 105 private int mResultCode; | |
| 106 private Intent mResultData; | |
| 107 | |
| 108 // Native callback context variable. | |
| 109 private final long mNativeScreenCaptureMachineAndroid; | |
| 110 | |
| 111 private static enum CaptureState { ATTACHED, ALLOWED, STARTED, STOPPING, STO PPED } | |
| 112 private CaptureState mCaptureState = CaptureState.STOPPED; | |
| 113 private final Object mCaptureStateLock = new Object(); | |
| 114 | |
| 115 private static final String TAG = "ScreenCaptureMachine"; | |
| 116 | |
| 117 ScreenCapture(Context context, long nativeScreenCaptureMachineAndroid) { | |
| 118 mContext = context; | |
| 119 mNativeScreenCaptureMachineAndroid = nativeScreenCaptureMachineAndroid; | |
| 120 | |
| 121 Activity activity = ApplicationStatus.getLastTrackedFocusedActivity(); | |
| 122 if (activity == null) { | |
| 123 Log.e(TAG, "activity is null"); | |
| 124 return; | |
| 125 } | |
| 126 | |
| 127 try { | |
| 128 FragmentManager fragmentManager = activity.getFragmentManager(); | |
| 129 FragmentTransaction fragmentTransaction = fragmentManager.beginTrans action(); | |
| 130 fragmentTransaction.add(this, "screencapture"); | |
| 131 fragmentTransaction.commit(); | |
| 132 } catch (Exception e) { | |
| 133 Log.e(TAG, "ScreenCaptureExcaption " + e); | |
| 134 } | |
| 135 | |
| 136 try { | |
| 137 DisplayMetrics metrics = new DisplayMetrics(); | |
| 138 Display display = activity.getWindowManager().getDefaultDisplay(); | |
| 139 display.getMetrics(metrics); | |
| 140 mScreenDensity = metrics.densityDpi; | |
| 141 } catch (Exception e) { | |
| 142 Log.e(TAG, "ScreenCaptureExcaption " + e); | |
| 143 } | |
| 144 } | |
| 145 | |
| 146 // Factory methods. | |
| 147 @CalledByNative | |
| 148 static ScreenCapture createScreenCaptureMachine( | |
| 149 Context context, long nativeScreenCaptureMachineAndroid) { | |
| 150 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | |
| 151 return new ScreenCapture(context, nativeScreenCaptureMachineAndroid) ; | |
| 152 } | |
| 153 return null; | |
| 154 } | |
| 155 | |
| 156 @Override | |
| 157 public void onAttach(Context context) { | |
| 158 super.onAttach(context); | |
| 159 Log.d(TAG, "onAttach"); | |
| 160 changeCaptureStateAndNotify(CaptureState.ATTACHED); | |
| 161 } | |
| 162 | |
| 163 // This method was deprecated in API level 23 by onAttach(Context). | |
| 164 // TODO(braveyao): remove this method after the minSdkVersion of chromium is 23. | |
| 165 @SuppressWarnings("deprecation") | |
| 166 @Override | |
| 167 public void onAttach(Activity activity) { | |
| 168 super.onAttach(activity); | |
| 169 Log.d(TAG, "onAttach"); | |
| 170 changeCaptureStateAndNotify(CaptureState.ATTACHED); | |
| 171 } | |
| 172 | |
| 173 @Override | |
| 174 public void onDetach() { | |
| 175 super.onDetach(); | |
| 176 Log.d(TAG, "onDetach"); | |
| 177 stopCapture(); | |
| 178 } | |
| 179 | |
| 180 @CalledByNative | |
| 181 public boolean startPrompt(int width, int height) { | |
| 182 Log.d(TAG, "startPrompt"); | |
| 183 synchronized (mCaptureStateLock) { | |
| 184 while (mCaptureState != CaptureState.ATTACHED) { | |
| 185 try { | |
| 186 mCaptureStateLock.wait(); | |
| 187 } catch (InterruptedException ex) { | |
| 188 Log.e(TAG, "ScreenCaptureException: " + ex); | |
| 189 } | |
| 190 } | |
| 191 } | |
| 192 | |
| 193 try { | |
| 194 mWidth = width; | |
| 195 mHeight = height; | |
| 196 int expectedFrameSize = mWidth * mHeight * 4; | |
| 197 mCapturedData = new byte[expectedFrameSize]; | |
| 198 | |
| 199 mMediaProjectionManager = (MediaProjectionManager) mContext.getSyste mService( | |
| 200 Context.MEDIA_PROJECTION_SERVICE); | |
| 201 if (mMediaProjectionManager == null) { | |
| 202 Log.e(TAG, "mMediaProjectionManager is null"); | |
| 203 return false; | |
| 204 } | |
| 205 | |
| 206 startActivityForResult( | |
| 207 mMediaProjectionManager.createScreenCaptureIntent(), REQUEST _MEDIA_PROJECTION); | |
| 208 } catch (Exception e) { | |
| 209 Log.e(TAG, "ScreenCaptureException " + e); | |
| 210 return false; | |
| 211 } | |
| 212 return true; | |
| 213 } | |
| 214 | |
| 215 @Override | |
| 216 public void onActivityResult(int requestCode, int resultCode, Intent data) { | |
| 217 if (requestCode == REQUEST_MEDIA_PROJECTION) { | |
| 218 boolean result; | |
| 219 if (resultCode != Activity.RESULT_OK) { | |
| 220 result = false; | |
| 221 } else { | |
| 222 result = true; | |
| 223 mResultCode = resultCode; | |
| 224 mResultData = data; | |
| 225 changeCaptureStateAndNotify(CaptureState.ALLOWED); | |
| 226 } | |
| 227 nativeOnActivityResult(mNativeScreenCaptureMachineAndroid, result); | |
|
mcasas
2016/05/04 19:56:48
s/result/resultCode == Activity.RESULT_OK/ ?
braveyao
2016/05/18 00:41:46
Done.
| |
| 228 } | |
| 229 } | |
| 230 | |
| 231 @CalledByNative | |
| 232 public void startCapture() { | |
| 233 Log.d(TAG, "startCapture"); | |
| 234 synchronized (mCaptureStateLock) { | |
| 235 if (mCaptureState != CaptureState.ALLOWED) { | |
| 236 Log.e(TAG, "startCapture() invoked without user permission."); | |
| 237 return; | |
| 238 } | |
| 239 } | |
| 240 mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCod e, mResultData); | |
| 241 if (mMediaProjection == null) { | |
| 242 Log.e(TAG, "mMediaProjection is null"); | |
| 243 return; | |
| 244 } | |
| 245 mMediaProjection.registerCallback(new MediaProjectionCallback(), null); | |
| 246 | |
| 247 final int maxImages = 2; | |
| 248 mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat.RGBA _8888, maxImages); | |
| 249 mSurface = mImageReader.getSurface(); | |
| 250 HandlerThread thread = new HandlerThread("Screen"); | |
| 251 thread.start(); | |
| 252 final Handler backgroundHandler = new Handler(thread.getLooper()); | |
| 253 final CrImageReaderListener imageReaderListener = new CrImageReaderListe ner(); | |
| 254 mImageReader.setOnImageAvailableListener(imageReaderListener, background Handler); | |
| 255 | |
| 256 mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenCapture", mWidth, mHeight, | |
| 257 mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mSurface, null, | |
| 258 null); | |
| 259 | |
| 260 changeCaptureStateAndNotify(CaptureState.STARTED); | |
| 261 } | |
| 262 | |
| 263 @CalledByNative | |
| 264 public void stopCapture() { | |
| 265 Log.d(TAG, "stopCapture"); | |
| 266 synchronized (mCaptureStateLock) { | |
| 267 if (mMediaProjection != null && mCaptureState == CaptureState.STARTE D) { | |
| 268 mMediaProjection.stop(); | |
| 269 changeCaptureStateAndNotify(CaptureState.STOPPING); | |
| 270 } | |
| 271 | |
| 272 while (mCaptureState != CaptureState.STOPPED) { | |
| 273 try { | |
| 274 mCaptureStateLock.wait(); | |
| 275 } catch (InterruptedException ex) { | |
| 276 Log.e(TAG, "ScreenCaptureEvent: " + ex); | |
| 277 } | |
| 278 } | |
| 279 } | |
| 280 } | |
| 281 | |
| 282 private void changeCaptureStateAndNotify(CaptureState state) { | |
| 283 synchronized (mCaptureStateLock) { | |
| 284 mCaptureState = state; | |
| 285 mCaptureStateLock.notifyAll(); | |
| 286 } | |
| 287 } | |
| 288 | |
| 289 // Method for ScreenCapture implementations to call back native code. | |
| 290 private native void nativeOnFrameAvailable(long nativeScreenCaptureMachineAn droid, byte[] data, | |
| 291 int cropWidth, int cropHeight, long timestamp); | |
| 292 | |
| 293 // Method for ScreenCapture implementations to call back native code. | |
| 294 private native void nativeOnActivityResult( | |
| 295 long nativeScreenCaptureMachineAndroid, boolean result); | |
| 296 } | |
| OLD | NEW |