| OLD | NEW |
| 1 /* | 1 /* |
| 2 * libjingle | 2 * libjingle |
| 3 * Copyright 2015 Google Inc. | 3 * Copyright 2015 Google Inc. |
| 4 * | 4 * |
| 5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
| 6 * modification, are permitted provided that the following conditions are met: | 6 * modification, are permitted provided that the following conditions are met: |
| 7 * | 7 * |
| 8 * 1. Redistributions of source code must retain the above copyright notice, | 8 * 1. Redistributions of source code must retain the above copyright notice, |
| 9 * this list of conditions and the following disclaimer. | 9 * this list of conditions and the following disclaimer. |
| 10 * 2. Redistributions in binary form must reproduce the above copyright notice, | 10 * 2. Redistributions in binary form must reproduce the above copyright notice, |
| (...skipping 18 matching lines...) Expand all Loading... |
| 29 | 29 |
| 30 import android.content.Context; | 30 import android.content.Context; |
| 31 import android.graphics.SurfaceTexture; | 31 import android.graphics.SurfaceTexture; |
| 32 import android.hardware.Camera; | 32 import android.hardware.Camera; |
| 33 import android.hardware.Camera.PreviewCallback; | 33 import android.hardware.Camera.PreviewCallback; |
| 34 import android.opengl.GLES11Ext; | 34 import android.opengl.GLES11Ext; |
| 35 import android.opengl.GLES20; | 35 import android.opengl.GLES20; |
| 36 import android.os.Handler; | 36 import android.os.Handler; |
| 37 import android.os.Looper; | 37 import android.os.Looper; |
| 38 import android.os.SystemClock; | 38 import android.os.SystemClock; |
| 39 import android.util.Log; | |
| 40 import android.view.Surface; | 39 import android.view.Surface; |
| 41 import android.view.WindowManager; | 40 import android.view.WindowManager; |
| 42 | 41 |
| 43 import org.json.JSONException; | 42 import org.json.JSONException; |
| 44 import org.webrtc.CameraEnumerationAndroid.CaptureFormat; | 43 import org.webrtc.CameraEnumerationAndroid.CaptureFormat; |
| 44 import org.webrtc.Logging; |
| 45 | 45 |
| 46 import java.io.IOException; | 46 import java.io.IOException; |
| 47 import java.nio.ByteBuffer; | 47 import java.nio.ByteBuffer; |
| 48 import java.util.ArrayList; | 48 import java.util.ArrayList; |
| 49 import java.util.HashMap; | 49 import java.util.HashMap; |
| 50 import java.util.IdentityHashMap; | 50 import java.util.IdentityHashMap; |
| 51 import java.util.List; | 51 import java.util.List; |
| 52 import java.util.Map; | 52 import java.util.Map; |
| 53 import java.util.concurrent.Exchanger; | 53 import java.util.concurrent.Exchanger; |
| 54 import java.util.concurrent.TimeUnit; | 54 import java.util.concurrent.TimeUnit; |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 98 private final Camera.ErrorCallback cameraErrorCallback = | 98 private final Camera.ErrorCallback cameraErrorCallback = |
| 99 new Camera.ErrorCallback() { | 99 new Camera.ErrorCallback() { |
| 100 @Override | 100 @Override |
| 101 public void onError(int error, Camera camera) { | 101 public void onError(int error, Camera camera) { |
| 102 String errorMessage; | 102 String errorMessage; |
| 103 if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { | 103 if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { |
| 104 errorMessage = "Camera server died!"; | 104 errorMessage = "Camera server died!"; |
| 105 } else { | 105 } else { |
| 106 errorMessage = "Camera error: " + error; | 106 errorMessage = "Camera error: " + error; |
| 107 } | 107 } |
| 108 Log.e(TAG, errorMessage); | 108 Logging.e(TAG, errorMessage); |
| 109 if (errorHandler != null) { | 109 if (errorHandler != null) { |
| 110 errorHandler.onCameraError(errorMessage); | 110 errorHandler.onCameraError(errorMessage); |
| 111 } | 111 } |
| 112 } | 112 } |
| 113 }; | 113 }; |
| 114 | 114 |
| 115 // Camera observer - monitors camera framerate and amount of available | 115 // Camera observer - monitors camera framerate and amount of available |
| 116 // camera buffers. Observer is excecuted on camera thread. | 116 // camera buffers. Observer is excecuted on camera thread. |
| 117 private final Runnable cameraObserver = new Runnable() { | 117 private final Runnable cameraObserver = new Runnable() { |
| 118 @Override | 118 @Override |
| 119 public void run() { | 119 public void run() { |
| 120 int cameraFps = (cameraFramesCount * 1000 + CAMERA_OBSERVER_PERIOD_MS / 2) | 120 int cameraFps = (cameraFramesCount * 1000 + CAMERA_OBSERVER_PERIOD_MS / 2) |
| 121 / CAMERA_OBSERVER_PERIOD_MS; | 121 / CAMERA_OBSERVER_PERIOD_MS; |
| 122 double averageCaptureBuffersCount = 0; | 122 double averageCaptureBuffersCount = 0; |
| 123 if (cameraFramesCount > 0) { | 123 if (cameraFramesCount > 0) { |
| 124 averageCaptureBuffersCount = | 124 averageCaptureBuffersCount = |
| 125 (double)captureBuffersCount / cameraFramesCount; | 125 (double)captureBuffersCount / cameraFramesCount; |
| 126 } | 126 } |
| 127 Log.d(TAG, "Camera fps: " + cameraFps + ". CaptureBuffers: " + | 127 Logging.d(TAG, "Camera fps: " + cameraFps + ". CaptureBuffers: " + |
| 128 String.format("%.1f", averageCaptureBuffersCount) + | 128 String.format("%.1f", averageCaptureBuffersCount) + |
| 129 ". Pending buffers: " + videoBuffers.pendingFramesTimeStamps()); | 129 ". Pending buffers: " + videoBuffers.pendingFramesTimeStamps()); |
| 130 if (cameraFramesCount == 0) { | 130 if (cameraFramesCount == 0) { |
| 131 Log.e(TAG, "Camera freezed."); | 131 Logging.e(TAG, "Camera freezed."); |
| 132 if (errorHandler != null) { | 132 if (errorHandler != null) { |
| 133 errorHandler.onCameraError("Camera failure."); | 133 errorHandler.onCameraError("Camera failure."); |
| 134 } | 134 } |
| 135 } else { | 135 } else { |
| 136 cameraFramesCount = 0; | 136 cameraFramesCount = 0; |
| 137 captureBuffersCount = 0; | 137 captureBuffersCount = 0; |
| 138 if (cameraThreadHandler != null) { | 138 if (cameraThreadHandler != null) { |
| 139 cameraThreadHandler.postDelayed(this, CAMERA_OBSERVER_PERIOD_MS); | 139 cameraThreadHandler.postDelayed(this, CAMERA_OBSERVER_PERIOD_MS); |
| 140 } | 140 } |
| 141 } | 141 } |
| (...skipping 19 matching lines...) Expand all Loading... |
| 161 | 161 |
| 162 // Switch camera to the next valid camera id. This can only be called while | 162 // Switch camera to the next valid camera id. This can only be called while |
| 163 // the camera is running. | 163 // the camera is running. |
| 164 // Returns true on success. False if the next camera does not support the | 164 // Returns true on success. False if the next camera does not support the |
| 165 // current resolution. | 165 // current resolution. |
| 166 public synchronized boolean switchCamera(final Runnable switchDoneEvent) { | 166 public synchronized boolean switchCamera(final Runnable switchDoneEvent) { |
| 167 if (Camera.getNumberOfCameras() < 2 ) | 167 if (Camera.getNumberOfCameras() < 2 ) |
| 168 return false; | 168 return false; |
| 169 | 169 |
| 170 if (cameraThreadHandler == null) { | 170 if (cameraThreadHandler == null) { |
| 171 Log.e(TAG, "Calling switchCamera() for stopped camera."); | 171 Logging.e(TAG, "Calling switchCamera() for stopped camera."); |
| 172 return false; | 172 return false; |
| 173 } | 173 } |
| 174 if (pendingCameraSwitch) { | 174 if (pendingCameraSwitch) { |
| 175 // Do not handle multiple camera switch request to avoid blocking | 175 // Do not handle multiple camera switch request to avoid blocking |
| 176 // camera thread by handling too many switch request from a queue. | 176 // camera thread by handling too many switch request from a queue. |
| 177 Log.w(TAG, "Ignoring camera switch request."); | 177 Logging.w(TAG, "Ignoring camera switch request."); |
| 178 return false; | 178 return false; |
| 179 } | 179 } |
| 180 | 180 |
| 181 pendingCameraSwitch = true; | 181 pendingCameraSwitch = true; |
| 182 id = (id + 1) % Camera.getNumberOfCameras(); | 182 id = (id + 1) % Camera.getNumberOfCameras(); |
| 183 cameraThreadHandler.post(new Runnable() { | 183 cameraThreadHandler.post(new Runnable() { |
| 184 @Override public void run() { | 184 @Override public void run() { |
| 185 switchCameraOnCameraThread(switchDoneEvent); | 185 switchCameraOnCameraThread(switchDoneEvent); |
| 186 } | 186 } |
| 187 }); | 187 }); |
| 188 return true; | 188 return true; |
| 189 } | 189 } |
| 190 | 190 |
| 191 // Requests a new output format from the video capturer. Captured frames | 191 // Requests a new output format from the video capturer. Captured frames |
| 192 // by the camera will be scaled/or dropped by the video capturer. | 192 // by the camera will be scaled/or dropped by the video capturer. |
| 193 public synchronized void onOutputFormatRequest( | 193 public synchronized void onOutputFormatRequest( |
| 194 final int width, final int height, final int fps) { | 194 final int width, final int height, final int fps) { |
| 195 if (cameraThreadHandler == null) { | 195 if (cameraThreadHandler == null) { |
| 196 Log.e(TAG, "Calling onOutputFormatRequest() for already stopped camera."); | 196 Logging.e(TAG, "Calling onOutputFormatRequest() for already stopped camera
."); |
| 197 return; | 197 return; |
| 198 } | 198 } |
| 199 cameraThreadHandler.post(new Runnable() { | 199 cameraThreadHandler.post(new Runnable() { |
| 200 @Override public void run() { | 200 @Override public void run() { |
| 201 onOutputFormatRequestOnCameraThread(width, height, fps); | 201 onOutputFormatRequestOnCameraThread(width, height, fps); |
| 202 } | 202 } |
| 203 }); | 203 }); |
| 204 } | 204 } |
| 205 | 205 |
| 206 // Reconfigure the camera to capture in a new format. This should only be call
ed while the camera | 206 // Reconfigure the camera to capture in a new format. This should only be call
ed while the camera |
| 207 // is running. | 207 // is running. |
| 208 public synchronized void changeCaptureFormat( | 208 public synchronized void changeCaptureFormat( |
| 209 final int width, final int height, final int framerate) { | 209 final int width, final int height, final int framerate) { |
| 210 if (cameraThreadHandler == null) { | 210 if (cameraThreadHandler == null) { |
| 211 Log.e(TAG, "Calling changeCaptureFormat() for already stopped camera."); | 211 Logging.e(TAG, "Calling changeCaptureFormat() for already stopped camera."
); |
| 212 return; | 212 return; |
| 213 } | 213 } |
| 214 cameraThreadHandler.post(new Runnable() { | 214 cameraThreadHandler.post(new Runnable() { |
| 215 @Override public void run() { | 215 @Override public void run() { |
| 216 startPreviewOnCameraThread(width, height, framerate); | 216 startPreviewOnCameraThread(width, height, framerate); |
| 217 } | 217 } |
| 218 }); | 218 }); |
| 219 } | 219 } |
| 220 | 220 |
| 221 public synchronized List<CaptureFormat> getSupportedFormats() { | 221 public synchronized List<CaptureFormat> getSupportedFormats() { |
| 222 return CameraEnumerationAndroid.getSupportedFormats(id); | 222 return CameraEnumerationAndroid.getSupportedFormats(id); |
| 223 } | 223 } |
| 224 | 224 |
| 225 // Return a list of timestamps for the frames that have been sent out, but not
returned yet. | 225 // Return a list of timestamps for the frames that have been sent out, but not
returned yet. |
| 226 // Useful for logging and testing. | 226 // Useful for logging and testing. |
| 227 public String pendingFramesTimeStamps() { | 227 public String pendingFramesTimeStamps() { |
| 228 return videoBuffers.pendingFramesTimeStamps(); | 228 return videoBuffers.pendingFramesTimeStamps(); |
| 229 } | 229 } |
| 230 | 230 |
| 231 private VideoCapturerAndroid() { | 231 private VideoCapturerAndroid() { |
| 232 Log.d(TAG, "VideoCapturerAndroid"); | 232 Logging.d(TAG, "VideoCapturerAndroid"); |
| 233 } | 233 } |
| 234 | 234 |
| 235 // Called by native code. | 235 // Called by native code. |
| 236 // Initializes local variables for the camera named |deviceName|. If |deviceNa
me| is empty, the | 236 // Initializes local variables for the camera named |deviceName|. If |deviceNa
me| is empty, the |
| 237 // first available device is used in order to be compatible with the generic V
ideoCapturer class. | 237 // first available device is used in order to be compatible with the generic V
ideoCapturer class. |
| 238 synchronized boolean init(String deviceName) { | 238 synchronized boolean init(String deviceName) { |
| 239 Log.d(TAG, "init: " + deviceName); | 239 Logging.d(TAG, "init: " + deviceName); |
| 240 if (deviceName == null) | 240 if (deviceName == null) |
| 241 return false; | 241 return false; |
| 242 | 242 |
| 243 boolean foundDevice = false; | 243 boolean foundDevice = false; |
| 244 if (deviceName.isEmpty()) { | 244 if (deviceName.isEmpty()) { |
| 245 this.id = 0; | 245 this.id = 0; |
| 246 foundDevice = true; | 246 foundDevice = true; |
| 247 } else { | 247 } else { |
| 248 for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { | 248 for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { |
| 249 String existing_device = CameraEnumerationAndroid.getDeviceName(i); | 249 String existing_device = CameraEnumerationAndroid.getDeviceName(i); |
| (...skipping 24 matching lines...) Expand all Loading... |
| 274 } | 274 } |
| 275 | 275 |
| 276 // Called by native code. Returns true if capturer is started. | 276 // Called by native code. Returns true if capturer is started. |
| 277 // | 277 // |
| 278 // Note that this actually opens the camera, and Camera callbacks run on the | 278 // Note that this actually opens the camera, and Camera callbacks run on the |
| 279 // thread that calls open(), so this is done on the CameraThread. Since the | 279 // thread that calls open(), so this is done on the CameraThread. Since the |
| 280 // API needs a synchronous success return value we wait for the result. | 280 // API needs a synchronous success return value we wait for the result. |
| 281 synchronized void startCapture( | 281 synchronized void startCapture( |
| 282 final int width, final int height, final int framerate, | 282 final int width, final int height, final int framerate, |
| 283 final Context applicationContext, final CapturerObserver frameObserver) { | 283 final Context applicationContext, final CapturerObserver frameObserver) { |
| 284 Log.d(TAG, "startCapture requested: " + width + "x" + height | 284 Logging.d(TAG, "startCapture requested: " + width + "x" + height |
| 285 + "@" + framerate); | 285 + "@" + framerate); |
| 286 if (applicationContext == null) { | 286 if (applicationContext == null) { |
| 287 throw new RuntimeException("applicationContext not set."); | 287 throw new RuntimeException("applicationContext not set."); |
| 288 } | 288 } |
| 289 if (frameObserver == null) { | 289 if (frameObserver == null) { |
| 290 throw new RuntimeException("frameObserver not set."); | 290 throw new RuntimeException("frameObserver not set."); |
| 291 } | 291 } |
| 292 if (cameraThreadHandler != null) { | 292 if (cameraThreadHandler != null) { |
| 293 throw new RuntimeException("Camera has already been started."); | 293 throw new RuntimeException("Camera has already been started."); |
| 294 } | 294 } |
| (...skipping 10 matching lines...) Expand all Loading... |
| 305 }); | 305 }); |
| 306 } | 306 } |
| 307 | 307 |
| 308 private void startCaptureOnCameraThread( | 308 private void startCaptureOnCameraThread( |
| 309 int width, int height, int framerate, CapturerObserver frameObserver, | 309 int width, int height, int framerate, CapturerObserver frameObserver, |
| 310 Context applicationContext) { | 310 Context applicationContext) { |
| 311 Throwable error = null; | 311 Throwable error = null; |
| 312 this.applicationContext = applicationContext; | 312 this.applicationContext = applicationContext; |
| 313 this.frameObserver = frameObserver; | 313 this.frameObserver = frameObserver; |
| 314 try { | 314 try { |
| 315 Log.d(TAG, "Opening camera " + id); | 315 Logging.d(TAG, "Opening camera " + id); |
| 316 camera = Camera.open(id); | 316 camera = Camera.open(id); |
| 317 info = new Camera.CameraInfo(); | 317 info = new Camera.CameraInfo(); |
| 318 Camera.getCameraInfo(id, info); | 318 Camera.getCameraInfo(id, info); |
| 319 // No local renderer (we only care about onPreviewFrame() buffers, not a | 319 // No local renderer (we only care about onPreviewFrame() buffers, not a |
| 320 // directly-displayed UI element). Camera won't capture without | 320 // directly-displayed UI element). Camera won't capture without |
| 321 // setPreview{Texture,Display}, so we create a SurfaceTexture and hand | 321 // setPreview{Texture,Display}, so we create a SurfaceTexture and hand |
| 322 // it over to Camera, but never listen for frame-ready callbacks, | 322 // it over to Camera, but never listen for frame-ready callbacks, |
| 323 // and never call updateTexImage on it. | 323 // and never call updateTexImage on it. |
| 324 try { | 324 try { |
| 325 cameraGlTexture = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_O
ES); | 325 cameraGlTexture = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_O
ES); |
| 326 cameraSurfaceTexture = new SurfaceTexture(cameraGlTexture); | 326 cameraSurfaceTexture = new SurfaceTexture(cameraGlTexture); |
| 327 cameraSurfaceTexture.setOnFrameAvailableListener(null); | 327 cameraSurfaceTexture.setOnFrameAvailableListener(null); |
| 328 | 328 |
| 329 camera.setPreviewTexture(cameraSurfaceTexture); | 329 camera.setPreviewTexture(cameraSurfaceTexture); |
| 330 } catch (IOException e) { | 330 } catch (IOException e) { |
| 331 Log.e(TAG, "setPreviewTexture failed", error); | 331 Logging.e(TAG, "setPreviewTexture failed", error); |
| 332 throw new RuntimeException(e); | 332 throw new RuntimeException(e); |
| 333 } | 333 } |
| 334 | 334 |
| 335 Log.d(TAG, "Camera orientation: " + info.orientation + | 335 Logging.d(TAG, "Camera orientation: " + info.orientation + |
| 336 " .Device orientation: " + getDeviceOrientation()); | 336 " .Device orientation: " + getDeviceOrientation()); |
| 337 camera.setErrorCallback(cameraErrorCallback); | 337 camera.setErrorCallback(cameraErrorCallback); |
| 338 startPreviewOnCameraThread(width, height, framerate); | 338 startPreviewOnCameraThread(width, height, framerate); |
| 339 frameObserver.OnCapturerStarted(true); | 339 frameObserver.OnCapturerStarted(true); |
| 340 | 340 |
| 341 // Start camera observer. | 341 // Start camera observer. |
| 342 cameraFramesCount = 0; | 342 cameraFramesCount = 0; |
| 343 captureBuffersCount = 0; | 343 captureBuffersCount = 0; |
| 344 cameraThreadHandler.postDelayed(cameraObserver, CAMERA_OBSERVER_PERIOD_MS)
; | 344 cameraThreadHandler.postDelayed(cameraObserver, CAMERA_OBSERVER_PERIOD_MS)
; |
| 345 return; | 345 return; |
| 346 } catch (RuntimeException e) { | 346 } catch (RuntimeException e) { |
| 347 error = e; | 347 error = e; |
| 348 } | 348 } |
| 349 Log.e(TAG, "startCapture failed", error); | 349 Logging.e(TAG, "startCapture failed", error); |
| 350 stopCaptureOnCameraThread(); | 350 stopCaptureOnCameraThread(); |
| 351 cameraThreadHandler = null; | 351 cameraThreadHandler = null; |
| 352 frameObserver.OnCapturerStarted(false); | 352 frameObserver.OnCapturerStarted(false); |
| 353 if (errorHandler != null) { | 353 if (errorHandler != null) { |
| 354 errorHandler.onCameraError("Camera can not be started."); | 354 errorHandler.onCameraError("Camera can not be started."); |
| 355 } | 355 } |
| 356 return; | 356 return; |
| 357 } | 357 } |
| 358 | 358 |
| 359 // (Re)start preview with the closest supported format to |width| x |height| @
|framerate|. | 359 // (Re)start preview with the closest supported format to |width| x |height| @
|framerate|. |
| 360 private void startPreviewOnCameraThread(int width, int height, int framerate)
{ | 360 private void startPreviewOnCameraThread(int width, int height, int framerate)
{ |
| 361 Log.d( | 361 Logging.d( |
| 362 TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + "
@" + framerate); | 362 TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + "
@" + framerate); |
| 363 if (camera == null) { | 363 if (camera == null) { |
| 364 Log.e(TAG, "Calling startPreviewOnCameraThread on stopped camera."); | 364 Logging.e(TAG, "Calling startPreviewOnCameraThread on stopped camera."); |
| 365 return; | 365 return; |
| 366 } | 366 } |
| 367 | 367 |
| 368 requestedWidth = width; | 368 requestedWidth = width; |
| 369 requestedHeight = height; | 369 requestedHeight = height; |
| 370 requestedFramerate = framerate; | 370 requestedFramerate = framerate; |
| 371 | 371 |
| 372 // Find closest supported format for |width| x |height| @ |framerate|. | 372 // Find closest supported format for |width| x |height| @ |framerate|. |
| 373 final Camera.Parameters parameters = camera.getParameters(); | 373 final Camera.Parameters parameters = camera.getParameters(); |
| 374 final int[] range = CameraEnumerationAndroid.getFramerateRange(parameters, f
ramerate * 1000); | 374 final int[] range = CameraEnumerationAndroid.getFramerateRange(parameters, f
ramerate * 1000); |
| 375 final Camera.Size previewSize = CameraEnumerationAndroid.getClosestSupported
Size( | 375 final Camera.Size previewSize = CameraEnumerationAndroid.getClosestSupported
Size( |
| 376 parameters.getSupportedPreviewSizes(), width, height); | 376 parameters.getSupportedPreviewSizes(), width, height); |
| 377 final CaptureFormat captureFormat = new CaptureFormat( | 377 final CaptureFormat captureFormat = new CaptureFormat( |
| 378 previewSize.width, previewSize.height, | 378 previewSize.width, previewSize.height, |
| 379 range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], | 379 range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], |
| 380 range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); | 380 range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); |
| 381 | 381 |
| 382 // Check if we are already using this capture format, then we don't need to
do anything. | 382 // Check if we are already using this capture format, then we don't need to
do anything. |
| 383 if (captureFormat.equals(this.captureFormat)) { | 383 if (captureFormat.equals(this.captureFormat)) { |
| 384 return; | 384 return; |
| 385 } | 385 } |
| 386 | 386 |
| 387 // Update camera parameters. | 387 // Update camera parameters. |
| 388 Log.d(TAG, "isVideoStabilizationSupported: " + | 388 Logging.d(TAG, "isVideoStabilizationSupported: " + |
| 389 parameters.isVideoStabilizationSupported()); | 389 parameters.isVideoStabilizationSupported()); |
| 390 if (parameters.isVideoStabilizationSupported()) { | 390 if (parameters.isVideoStabilizationSupported()) { |
| 391 parameters.setVideoStabilization(true); | 391 parameters.setVideoStabilization(true); |
| 392 } | 392 } |
| 393 // Note: setRecordingHint(true) actually decrease frame rate on N5. | 393 // Note: setRecordingHint(true) actually decrease frame rate on N5. |
| 394 // parameters.setRecordingHint(true); | 394 // parameters.setRecordingHint(true); |
| 395 if (captureFormat.maxFramerate > 0) { | 395 if (captureFormat.maxFramerate > 0) { |
| 396 parameters.setPreviewFpsRange(captureFormat.minFramerate, captureFormat.ma
xFramerate); | 396 parameters.setPreviewFpsRange(captureFormat.minFramerate, captureFormat.ma
xFramerate); |
| 397 } | 397 } |
| 398 parameters.setPreviewSize(captureFormat.width, captureFormat.height); | 398 parameters.setPreviewSize(captureFormat.width, captureFormat.height); |
| 399 parameters.setPreviewFormat(captureFormat.imageFormat); | 399 parameters.setPreviewFormat(captureFormat.imageFormat); |
| 400 // Picture size is for taking pictures and not for preview/video, but we nee
d to set it anyway | 400 // Picture size is for taking pictures and not for preview/video, but we nee
d to set it anyway |
| 401 // as a workaround for an aspect ratio problem on Nexus 7. | 401 // as a workaround for an aspect ratio problem on Nexus 7. |
| 402 final Camera.Size pictureSize = CameraEnumerationAndroid.getClosestSupported
Size( | 402 final Camera.Size pictureSize = CameraEnumerationAndroid.getClosestSupported
Size( |
| 403 parameters.getSupportedPictureSizes(), width, height); | 403 parameters.getSupportedPictureSizes(), width, height); |
| 404 parameters.setPictureSize(pictureSize.width, pictureSize.height); | 404 parameters.setPictureSize(pictureSize.width, pictureSize.height); |
| 405 | 405 |
| 406 // Temporarily stop preview if it's already running. | 406 // Temporarily stop preview if it's already running. |
| 407 if (this.captureFormat != null) { | 407 if (this.captureFormat != null) { |
| 408 camera.stopPreview(); | 408 camera.stopPreview(); |
| 409 // Calling |setPreviewCallbackWithBuffer| with null should clear the inter
nal camera buffer | 409 // Calling |setPreviewCallbackWithBuffer| with null should clear the inter
nal camera buffer |
| 410 // queue, but sometimes we receive a frame with the old resolution after t
his call anyway. | 410 // queue, but sometimes we receive a frame with the old resolution after t
his call anyway. |
| 411 camera.setPreviewCallbackWithBuffer(null); | 411 camera.setPreviewCallbackWithBuffer(null); |
| 412 } | 412 } |
| 413 | 413 |
| 414 // (Re)start preview. | 414 // (Re)start preview. |
| 415 Log.d(TAG, "Start capturing: " + captureFormat); | 415 Logging.d(TAG, "Start capturing: " + captureFormat); |
| 416 this.captureFormat = captureFormat; | 416 this.captureFormat = captureFormat; |
| 417 camera.setParameters(parameters); | 417 camera.setParameters(parameters); |
| 418 videoBuffers.queueCameraBuffers(captureFormat.frameSize(), camera); | 418 videoBuffers.queueCameraBuffers(captureFormat.frameSize(), camera); |
| 419 camera.setPreviewCallbackWithBuffer(this); | 419 camera.setPreviewCallbackWithBuffer(this); |
| 420 camera.startPreview(); | 420 camera.startPreview(); |
| 421 } | 421 } |
| 422 | 422 |
| 423 // Called by native code. Returns true when camera is known to be stopped. | 423 // Called by native code. Returns true when camera is known to be stopped. |
| 424 synchronized void stopCapture() throws InterruptedException { | 424 synchronized void stopCapture() throws InterruptedException { |
| 425 if (cameraThreadHandler == null) { | 425 if (cameraThreadHandler == null) { |
| 426 Log.e(TAG, "Calling stopCapture() for already stopped camera."); | 426 Logging.e(TAG, "Calling stopCapture() for already stopped camera."); |
| 427 return; | 427 return; |
| 428 } | 428 } |
| 429 Log.d(TAG, "stopCapture"); | 429 Logging.d(TAG, "stopCapture"); |
| 430 cameraThreadHandler.post(new Runnable() { | 430 cameraThreadHandler.post(new Runnable() { |
| 431 @Override public void run() { | 431 @Override public void run() { |
| 432 stopCaptureOnCameraThread(); | 432 stopCaptureOnCameraThread(); |
| 433 } | 433 } |
| 434 }); | 434 }); |
| 435 cameraThread.join(); | 435 cameraThread.join(); |
| 436 cameraThreadHandler = null; | 436 cameraThreadHandler = null; |
| 437 Log.d(TAG, "stopCapture done"); | 437 Logging.d(TAG, "stopCapture done"); |
| 438 } | 438 } |
| 439 | 439 |
| 440 private void stopCaptureOnCameraThread() { | 440 private void stopCaptureOnCameraThread() { |
| 441 doStopCaptureOnCameraThread(); | 441 doStopCaptureOnCameraThread(); |
| 442 Looper.myLooper().quit(); | 442 Looper.myLooper().quit(); |
| 443 return; | 443 return; |
| 444 } | 444 } |
| 445 | 445 |
| 446 private void doStopCaptureOnCameraThread() { | 446 private void doStopCaptureOnCameraThread() { |
| 447 Log.d(TAG, "stopCaptureOnCameraThread"); | 447 Logging.d(TAG, "stopCaptureOnCameraThread"); |
| 448 if (camera == null) { | 448 if (camera == null) { |
| 449 return; | 449 return; |
| 450 } | 450 } |
| 451 try { | 451 try { |
| 452 cameraThreadHandler.removeCallbacks(cameraObserver); | 452 cameraThreadHandler.removeCallbacks(cameraObserver); |
| 453 Log.d(TAG, "Stop preview."); | 453 Logging.d(TAG, "Stop preview."); |
| 454 camera.stopPreview(); | 454 camera.stopPreview(); |
| 455 camera.setPreviewCallbackWithBuffer(null); | 455 camera.setPreviewCallbackWithBuffer(null); |
| 456 videoBuffers.stopReturnBuffersToCamera(); | 456 videoBuffers.stopReturnBuffersToCamera(); |
| 457 captureFormat = null; | 457 captureFormat = null; |
| 458 | 458 |
| 459 camera.setPreviewTexture(null); | 459 camera.setPreviewTexture(null); |
| 460 cameraSurfaceTexture = null; | 460 cameraSurfaceTexture = null; |
| 461 if (cameraGlTexture != 0) { | 461 if (cameraGlTexture != 0) { |
| 462 GLES20.glDeleteTextures(1, new int[] {cameraGlTexture}, 0); | 462 GLES20.glDeleteTextures(1, new int[] {cameraGlTexture}, 0); |
| 463 cameraGlTexture = 0; | 463 cameraGlTexture = 0; |
| 464 } | 464 } |
| 465 Log.d(TAG, "Release camera."); | 465 Logging.d(TAG, "Release camera."); |
| 466 camera.release(); | 466 camera.release(); |
| 467 camera = null; | 467 camera = null; |
| 468 } catch (IOException e) { | 468 } catch (IOException e) { |
| 469 Log.e(TAG, "Failed to stop camera", e); | 469 Logging.e(TAG, "Failed to stop camera", e); |
| 470 } | 470 } |
| 471 } | 471 } |
| 472 | 472 |
| 473 private void switchCameraOnCameraThread(Runnable switchDoneEvent) { | 473 private void switchCameraOnCameraThread(Runnable switchDoneEvent) { |
| 474 Log.d(TAG, "switchCameraOnCameraThread"); | 474 Logging.d(TAG, "switchCameraOnCameraThread"); |
| 475 | 475 |
| 476 doStopCaptureOnCameraThread(); | 476 doStopCaptureOnCameraThread(); |
| 477 startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramera
te, frameObserver, | 477 startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramera
te, frameObserver, |
| 478 applicationContext); | 478 applicationContext); |
| 479 pendingCameraSwitch = false; | 479 pendingCameraSwitch = false; |
| 480 Log.d(TAG, "switchCameraOnCameraThread done"); | 480 Logging.d(TAG, "switchCameraOnCameraThread done"); |
| 481 if (switchDoneEvent != null) { | 481 if (switchDoneEvent != null) { |
| 482 switchDoneEvent.run(); | 482 switchDoneEvent.run(); |
| 483 } | 483 } |
| 484 } | 484 } |
| 485 | 485 |
| 486 private void onOutputFormatRequestOnCameraThread( | 486 private void onOutputFormatRequestOnCameraThread( |
| 487 int width, int height, int fps) { | 487 int width, int height, int fps) { |
| 488 if (camera == null) { | 488 if (camera == null) { |
| 489 return; | 489 return; |
| 490 } | 490 } |
| 491 Log.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + height + | 491 Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + heigh
t + |
| 492 "@" + fps); | 492 "@" + fps); |
| 493 frameObserver.OnOutputFormatRequest(width, height, fps); | 493 frameObserver.OnOutputFormatRequest(width, height, fps); |
| 494 } | 494 } |
| 495 | 495 |
| 496 void returnBuffer(long timeStamp) { | 496 void returnBuffer(long timeStamp) { |
| 497 videoBuffers.returnBuffer(timeStamp); | 497 videoBuffers.returnBuffer(timeStamp); |
| 498 } | 498 } |
| 499 | 499 |
| 500 private int getDeviceOrientation() { | 500 private int getDeviceOrientation() { |
| 501 int orientation = 0; | 501 int orientation = 0; |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 543 } | 543 } |
| 544 rotation = (info.orientation + rotation) % 360; | 544 rotation = (info.orientation + rotation) % 360; |
| 545 // Mark the frame owning |data| as used. | 545 // Mark the frame owning |data| as used. |
| 546 // Note that since data is directBuffer, | 546 // Note that since data is directBuffer, |
| 547 // data.length >= videoBuffers.frameSize. | 547 // data.length >= videoBuffers.frameSize. |
| 548 if (videoBuffers.reserveByteBuffer(data, captureTimeNs)) { | 548 if (videoBuffers.reserveByteBuffer(data, captureTimeNs)) { |
| 549 cameraFramesCount++; | 549 cameraFramesCount++; |
| 550 frameObserver.OnFrameCaptured(data, videoBuffers.frameSize, captureFormat.
width, | 550 frameObserver.OnFrameCaptured(data, videoBuffers.frameSize, captureFormat.
width, |
| 551 captureFormat.height, rotation, captureTimeNs); | 551 captureFormat.height, rotation, captureTimeNs); |
| 552 } else { | 552 } else { |
| 553 Log.w(TAG, "reserveByteBuffer failed - dropping frame."); | 553 Logging.w(TAG, "reserveByteBuffer failed - dropping frame."); |
| 554 } | 554 } |
| 555 } | 555 } |
| 556 | 556 |
| 557 // runCameraThreadUntilIdle make sure all posted messages to the cameraThread | 557 // runCameraThreadUntilIdle make sure all posted messages to the cameraThread |
| 558 // is processed before returning. It does that by itself posting a message to | 558 // is processed before returning. It does that by itself posting a message to |
| 559 // to the message queue and waits until is has been processed. | 559 // to the message queue and waits until is has been processed. |
| 560 // It is used in tests. | 560 // It is used in tests. |
| 561 void runCameraThreadUntilIdle() { | 561 void runCameraThreadUntilIdle() { |
| 562 if (cameraThreadHandler == null) | 562 if (cameraThreadHandler == null) |
| 563 return; | 563 return; |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 606 synchronized void queueCameraBuffers(int frameSize, Camera camera) { | 606 synchronized void queueCameraBuffers(int frameSize, Camera camera) { |
| 607 this.camera = camera; | 607 this.camera = camera; |
| 608 this.frameSize = frameSize; | 608 this.frameSize = frameSize; |
| 609 | 609 |
| 610 queuedBuffers.clear(); | 610 queuedBuffers.clear(); |
| 611 for (int i = 0; i < numCaptureBuffers; ++i) { | 611 for (int i = 0; i < numCaptureBuffers; ++i) { |
| 612 final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize); | 612 final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize); |
| 613 camera.addCallbackBuffer(buffer.array()); | 613 camera.addCallbackBuffer(buffer.array()); |
| 614 queuedBuffers.put(buffer.array(), buffer); | 614 queuedBuffers.put(buffer.array(), buffer); |
| 615 } | 615 } |
| 616 Log.d(TAG, "queueCameraBuffers enqueued " + numCaptureBuffers | 616 Logging.d(TAG, "queueCameraBuffers enqueued " + numCaptureBuffers |
| 617 + " buffers of size " + frameSize + "."); | 617 + " buffers of size " + frameSize + "."); |
| 618 } | 618 } |
| 619 | 619 |
| 620 synchronized String pendingFramesTimeStamps() { | 620 synchronized String pendingFramesTimeStamps() { |
| 621 List<Long> timeStampsMs = new ArrayList<Long>(); | 621 List<Long> timeStampsMs = new ArrayList<Long>(); |
| 622 for (Long timeStampNs : pendingBuffers.keySet()) { | 622 for (Long timeStampNs : pendingBuffers.keySet()) { |
| 623 timeStampsMs.add(TimeUnit.NANOSECONDS.toMillis(timeStampNs)); | 623 timeStampsMs.add(TimeUnit.NANOSECONDS.toMillis(timeStampNs)); |
| 624 } | 624 } |
| 625 return timeStampsMs.toString(); | 625 return timeStampsMs.toString(); |
| 626 } | 626 } |
| 627 | 627 |
| 628 synchronized void stopReturnBuffersToCamera() { | 628 synchronized void stopReturnBuffersToCamera() { |
| 629 this.camera = null; | 629 this.camera = null; |
| 630 queuedBuffers.clear(); | 630 queuedBuffers.clear(); |
| 631 // Frames in |pendingBuffers| need to be kept alive until they are returne
d. | 631 // Frames in |pendingBuffers| need to be kept alive until they are returne
d. |
| 632 Log.d(TAG, "stopReturnBuffersToCamera called." | 632 Logging.d(TAG, "stopReturnBuffersToCamera called." |
| 633 + (pendingBuffers.isEmpty() ? | 633 + (pendingBuffers.isEmpty() ? |
| 634 " All buffers have been returned." | 634 " All buffers have been returned." |
| 635 : " Pending buffers: " + pendingFramesTimeStamps() + ".")); | 635 : " Pending buffers: " + pendingFramesTimeStamps() + ".")); |
| 636 } | 636 } |
| 637 | 637 |
| 638 synchronized boolean reserveByteBuffer(byte[] data, long timeStamp) { | 638 synchronized boolean reserveByteBuffer(byte[] data, long timeStamp) { |
| 639 final ByteBuffer buffer = queuedBuffers.remove(data); | 639 final ByteBuffer buffer = queuedBuffers.remove(data); |
| 640 if (buffer == null) { | 640 if (buffer == null) { |
| 641 // Frames might be posted to |onPreviewFrame| with the previous format w
hile changing | 641 // Frames might be posted to |onPreviewFrame| with the previous format w
hile changing |
| 642 // capture format in |startPreviewOnCameraThread|. Drop these old frames
. | 642 // capture format in |startPreviewOnCameraThread|. Drop these old frames
. |
| 643 Log.w(TAG, "Received callback buffer from previous configuration with le
ngth: " | 643 Logging.w(TAG, "Received callback buffer from previous configuration wit
h length: " |
| 644 + (data == null ? "null" : data.length)); | 644 + (data == null ? "null" : data.length)); |
| 645 return false; | 645 return false; |
| 646 } | 646 } |
| 647 if (buffer.capacity() != frameSize) { | 647 if (buffer.capacity() != frameSize) { |
| 648 throw new IllegalStateException("Callback buffer has unexpected frame si
ze"); | 648 throw new IllegalStateException("Callback buffer has unexpected frame si
ze"); |
| 649 } | 649 } |
| 650 if (pendingBuffers.containsKey(timeStamp)) { | 650 if (pendingBuffers.containsKey(timeStamp)) { |
| 651 Log.e(TAG, "Timestamp already present in pending buffers - they need to
be unique"); | 651 Logging.e(TAG, "Timestamp already present in pending buffers - they need
to be unique"); |
| 652 return false; | 652 return false; |
| 653 } | 653 } |
| 654 pendingBuffers.put(timeStamp, buffer); | 654 pendingBuffers.put(timeStamp, buffer); |
| 655 if (queuedBuffers.isEmpty()) { | 655 if (queuedBuffers.isEmpty()) { |
| 656 Log.v(TAG, "Camera is running out of capture buffers." | 656 Logging.v(TAG, "Camera is running out of capture buffers." |
| 657 + " Pending buffers: " + pendingFramesTimeStamps()); | 657 + " Pending buffers: " + pendingFramesTimeStamps()); |
| 658 } | 658 } |
| 659 return true; | 659 return true; |
| 660 } | 660 } |
| 661 | 661 |
| 662 synchronized void returnBuffer(long timeStamp) { | 662 synchronized void returnBuffer(long timeStamp) { |
| 663 final ByteBuffer returnedFrame = pendingBuffers.remove(timeStamp); | 663 final ByteBuffer returnedFrame = pendingBuffers.remove(timeStamp); |
| 664 if (returnedFrame == null) { | 664 if (returnedFrame == null) { |
| 665 throw new RuntimeException("unknown data buffer with time stamp " | 665 throw new RuntimeException("unknown data buffer with time stamp " |
| 666 + timeStamp + "returned?!?"); | 666 + timeStamp + "returned?!?"); |
| 667 } | 667 } |
| 668 | 668 |
| 669 if (camera != null && returnedFrame.capacity() == frameSize) { | 669 if (camera != null && returnedFrame.capacity() == frameSize) { |
| 670 camera.addCallbackBuffer(returnedFrame.array()); | 670 camera.addCallbackBuffer(returnedFrame.array()); |
| 671 if (queuedBuffers.isEmpty()) { | 671 if (queuedBuffers.isEmpty()) { |
| 672 Log.v(TAG, "Frame returned when camera is running out of capture" | 672 Logging.v(TAG, "Frame returned when camera is running out of capture" |
| 673 + " buffers for TS " + TimeUnit.NANOSECONDS.toMillis(timeStamp)); | 673 + " buffers for TS " + TimeUnit.NANOSECONDS.toMillis(timeStamp)); |
| 674 } | 674 } |
| 675 queuedBuffers.put(returnedFrame.array(), returnedFrame); | 675 queuedBuffers.put(returnedFrame.array(), returnedFrame); |
| 676 return; | 676 return; |
| 677 } | 677 } |
| 678 | 678 |
| 679 if (returnedFrame.capacity() != frameSize) { | 679 if (returnedFrame.capacity() != frameSize) { |
| 680 Log.d(TAG, "returnBuffer with time stamp " | 680 Logging.d(TAG, "returnBuffer with time stamp " |
| 681 + TimeUnit.NANOSECONDS.toMillis(timeStamp) | 681 + TimeUnit.NANOSECONDS.toMillis(timeStamp) |
| 682 + " called with old frame size, " + returnedFrame.capacity() + "."); | 682 + " called with old frame size, " + returnedFrame.capacity() + "."); |
| 683 // Since this frame has the wrong size, don't requeue it. Frames with th
e correct size are | 683 // Since this frame has the wrong size, don't requeue it. Frames with th
e correct size are |
| 684 // created in queueCameraBuffers so this must be an old buffer. | 684 // created in queueCameraBuffers so this must be an old buffer. |
| 685 return; | 685 return; |
| 686 } | 686 } |
| 687 | 687 |
| 688 Log.d(TAG, "returnBuffer with time stamp " | 688 Logging.d(TAG, "returnBuffer with time stamp " |
| 689 + TimeUnit.NANOSECONDS.toMillis(timeStamp) | 689 + TimeUnit.NANOSECONDS.toMillis(timeStamp) |
| 690 + " called after camera has been stopped."); | 690 + " called after camera has been stopped."); |
| 691 } | 691 } |
| 692 } | 692 } |
| 693 | 693 |
| 694 // Interface used for providing callbacks to an observer. | 694 // Interface used for providing callbacks to an observer. |
| 695 interface CapturerObserver { | 695 interface CapturerObserver { |
| 696 // Notify if the camera have been started successfully or not. | 696 // Notify if the camera have been started successfully or not. |
| 697 // Called on a Java thread owned by VideoCapturerAndroid. | 697 // Called on a Java thread owned by VideoCapturerAndroid. |
| 698 abstract void OnCapturerStarted(boolean success); | 698 abstract void OnCapturerStarted(boolean success); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 734 } | 734 } |
| 735 | 735 |
| 736 private native void nativeCapturerStarted(long nativeCapturer, | 736 private native void nativeCapturerStarted(long nativeCapturer, |
| 737 boolean success); | 737 boolean success); |
| 738 private native void nativeOnFrameCaptured(long nativeCapturer, | 738 private native void nativeOnFrameCaptured(long nativeCapturer, |
| 739 byte[] data, int length, int width, int height, int rotation, long timeS
tamp); | 739 byte[] data, int length, int width, int height, int rotation, long timeS
tamp); |
| 740 private native void nativeOnOutputFormatRequest(long nativeCapturer, | 740 private native void nativeOnOutputFormatRequest(long nativeCapturer, |
| 741 int width, int height, int fps); | 741 int width, int height, int fps); |
| 742 } | 742 } |
| 743 } | 743 } |
| OLD | NEW |