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

Side by Side Diff: talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java

Issue 1350863002: VideoCapturerAndroid: Fix threading issues (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Addressing hbos@ comments Created 5 years, 2 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
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 16 matching lines...) Expand all
27 27
28 package org.webrtc; 28 package org.webrtc;
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.HandlerThread;
38 import android.os.SystemClock; 38 import android.os.SystemClock;
39 import android.view.Surface; 39 import android.view.Surface;
40 import android.view.WindowManager; 40 import android.view.WindowManager;
41 41
42 import org.json.JSONException; 42 import org.json.JSONException;
43 import org.webrtc.CameraEnumerationAndroid.CaptureFormat; 43 import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
44 import org.webrtc.Logging; 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.Callable;
54 import java.util.concurrent.CountDownLatch;
55 import java.util.concurrent.ExecutionException;
56 import java.util.concurrent.FutureTask;
54 import java.util.concurrent.TimeUnit; 57 import java.util.concurrent.TimeUnit;
55 58
56 // Android specific implementation of VideoCapturer. 59 // Android specific implementation of VideoCapturer.
57 // An instance of this class can be created by an application using 60 // An instance of this class can be created by an application using
58 // VideoCapturerAndroid.create(); 61 // VideoCapturerAndroid.create();
59 // This class extends VideoCapturer with a method to easily switch between the 62 // This class extends VideoCapturer with a method to easily switch between the
60 // front and back camera. It also provides methods for enumerating valid device 63 // front and back camera. It also provides methods for enumerating valid device
61 // names. 64 // names.
62 // 65 //
63 // Threading notes: this class is called from C++ code, and from Camera 66 // Threading notes: this class is called from C++ code, Android Camera callbacks , and possibly
64 // Java callbacks. Since these calls happen on different threads, 67 // arbitrary Java threads. All public entry points are thread safe, and delegate the work to the
65 // the entry points to this class are all synchronized. This shouldn't present 68 // camera thread. The internal *OnCameraThread() methods must check |camera| for null to check if
66 // a performance bottleneck because only onPreviewFrame() is called more than 69 // the camera has been stopped.
67 // once (and is called serially on a single thread), so the lock should be
68 // uncontended. Note that each of these synchronized methods must check
69 // |camera| for null to account for having possibly waited for stopCapture() to
70 // complete.
71 @SuppressWarnings("deprecation") 70 @SuppressWarnings("deprecation")
72 public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba ck { 71 public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba ck {
73 private final static String TAG = "VideoCapturerAndroid"; 72 private final static String TAG = "VideoCapturerAndroid";
74 private final static int CAMERA_OBSERVER_PERIOD_MS = 5000; 73 private final static int CAMERA_OBSERVER_PERIOD_MS = 5000;
75 74
76 private Camera camera; // Only non-null while capturing. 75 private Camera camera; // Only non-null while capturing.
77 private CameraThread cameraThread; 76 private final HandlerThread cameraThread;
78 private Handler cameraThreadHandler; 77 private final Handler cameraThreadHandler;
79 // |cameraSurfaceTexture| is used with setPreviewTexture. Must be a member, se e issue webrtc:5021. 78 // |cameraSurfaceTexture| is used with setPreviewTexture. Must be a member, se e issue webrtc:5021.
80 private SurfaceTexture cameraSurfaceTexture; 79 private SurfaceTexture cameraSurfaceTexture;
81 private Context applicationContext; 80 private Context applicationContext;
81 // Synchronization lock for |id| in getCurrentCameraId() and switchCameraOnCam eraThread().
82 private final Object cameraIdLock = new Object();
82 private int id; 83 private int id;
83 private Camera.CameraInfo info; 84 private Camera.CameraInfo info;
84 private int cameraGlTexture = 0; 85 private int cameraGlTexture = 0;
85 private final FramePool videoBuffers = new FramePool(); 86 private final FramePool videoBuffers;
86 // Remember the requested format in case we want to switch cameras. 87 // Remember the requested format in case we want to switch cameras.
87 private int requestedWidth; 88 private int requestedWidth;
88 private int requestedHeight; 89 private int requestedHeight;
89 private int requestedFramerate; 90 private int requestedFramerate;
90 // The capture format will be the closest supported format to the requested fo rmat. 91 // The capture format will be the closest supported format to the requested fo rmat.
91 private CaptureFormat captureFormat; 92 private CaptureFormat captureFormat;
92 private int cameraFramesCount; 93 private int cameraFramesCount;
93 private int captureBuffersCount; 94 private int captureBuffersCount;
95 private final Object pendingCameraSwitchLock = new Object();
94 private volatile boolean pendingCameraSwitch; 96 private volatile boolean pendingCameraSwitch;
95 private CapturerObserver frameObserver = null; 97 private CapturerObserver frameObserver = null;
96 private CameraErrorHandler errorHandler = null; 98 private CameraErrorHandler errorHandler = null;
97 99
98 // Camera error callback. 100 // Camera error callback.
99 private final Camera.ErrorCallback cameraErrorCallback = 101 private final Camera.ErrorCallback cameraErrorCallback =
100 new Camera.ErrorCallback() { 102 new Camera.ErrorCallback() {
101 @Override 103 @Override
102 public void onError(int error, Camera camera) { 104 public void onError(int error, Camera camera) {
103 String errorMessage; 105 String errorMessage;
(...skipping 25 matching lines...) Expand all
129 String.format("%.1f", averageCaptureBuffersCount) + 131 String.format("%.1f", averageCaptureBuffersCount) +
130 ". Pending buffers: " + videoBuffers.pendingFramesTimeStamps()); 132 ". Pending buffers: " + videoBuffers.pendingFramesTimeStamps());
131 if (cameraFramesCount == 0) { 133 if (cameraFramesCount == 0) {
132 Logging.e(TAG, "Camera freezed."); 134 Logging.e(TAG, "Camera freezed.");
133 if (errorHandler != null) { 135 if (errorHandler != null) {
134 errorHandler.onCameraError("Camera failure."); 136 errorHandler.onCameraError("Camera failure.");
135 } 137 }
136 } else { 138 } else {
137 cameraFramesCount = 0; 139 cameraFramesCount = 0;
138 captureBuffersCount = 0; 140 captureBuffersCount = 0;
139 if (cameraThreadHandler != null) { 141 cameraThreadHandler.postDelayed(this, CAMERA_OBSERVER_PERIOD_MS);
140 cameraThreadHandler.postDelayed(this, CAMERA_OBSERVER_PERIOD_MS);
141 }
142 } 142 }
143 } 143 }
144 }; 144 };
145 145
146 // Camera error handler - invoked when camera stops receiving frames 146 // Camera error handler - invoked when camera stops receiving frames
147 // or any camera exception happens on camera thread. 147 // or any camera exception happens on camera thread.
148 public static interface CameraErrorHandler { 148 public static interface CameraErrorHandler {
149 public void onCameraError(String errorDescription); 149 public void onCameraError(String errorDescription);
150 } 150 }
151 151
152 // Camera switch handler - one of these functions are invoked with the result of switchCamera().
153 // The callback may be called on an arbitrary thread.
154 public interface CameraSwitchHandler {
155 // Invoked on success. |isFrontCamera| is true if the new camera is front fa cing.
156 void onCameraSwitchDone(boolean isFrontCamera);
157 // Invoked on failure, e.g. camera is stopped or only one camera available.
158 void onCameraSwitchError(String errorDescription);
159 }
160
152 public static VideoCapturerAndroid create(String name, 161 public static VideoCapturerAndroid create(String name,
153 CameraErrorHandler errorHandler) { 162 CameraErrorHandler errorHandler) {
154 VideoCapturer capturer = VideoCapturer.create(name); 163 VideoCapturer capturer = VideoCapturer.create(name);
155 if (capturer != null) { 164 if (capturer != null) {
156 VideoCapturerAndroid capturerAndroid = (VideoCapturerAndroid) capturer; 165 VideoCapturerAndroid capturerAndroid = (VideoCapturerAndroid) capturer;
157 capturerAndroid.errorHandler = errorHandler; 166 capturerAndroid.errorHandler = errorHandler;
158 return capturerAndroid; 167 return capturerAndroid;
159 } 168 }
160 return null; 169 return null;
161 } 170 }
162 171
163 // Switch camera to the next valid camera id. This can only be called while 172 // Switch camera to the next valid camera id. This can only be called while
164 // the camera is running. 173 // the camera is running.
165 // Returns true on success. False if the next camera does not support the 174 public void switchCamera(final CameraSwitchHandler handler) {
166 // current resolution. 175 if (Camera.getNumberOfCameras() < 2) {
167 public synchronized boolean switchCamera(final Runnable switchDoneEvent) { 176 if (handler != null) {
168 if (Camera.getNumberOfCameras() < 2 ) 177 handler.onCameraSwitchError("No camera to switch to.");
169 return false; 178 }
170 179 return;
171 if (cameraThreadHandler == null) {
172 Logging.e(TAG, "Calling switchCamera() for stopped camera.");
173 return false;
174 } 180 }
175 if (pendingCameraSwitch) { 181 synchronized (pendingCameraSwitchLock) {
176 // Do not handle multiple camera switch request to avoid blocking 182 if (pendingCameraSwitch) {
177 // camera thread by handling too many switch request from a queue. 183 // Do not handle multiple camera switch request to avoid blocking
178 Logging.w(TAG, "Ignoring camera switch request."); 184 // camera thread by handling too many switch request from a queue.
179 return false; 185 Logging.w(TAG, "Ignoring camera switch request.");
186 if (handler != null) {
187 handler.onCameraSwitchError("Pending camera switch already in progress .");
188 }
189 return;
190 }
191 pendingCameraSwitch = true;
180 } 192 }
181
182 pendingCameraSwitch = true;
183 id = (id + 1) % Camera.getNumberOfCameras();
184 cameraThreadHandler.post(new Runnable() { 193 cameraThreadHandler.post(new Runnable() {
185 @Override public void run() { 194 @Override public void run() {
186 switchCameraOnCameraThread(switchDoneEvent); 195 if (camera == null) {
196 if (handler != null) {
197 handler.onCameraSwitchError("Camera is stopped.");
198 }
199 return;
200 }
201 switchCameraOnCameraThread();
202 synchronized (pendingCameraSwitchLock) {
203 pendingCameraSwitch = false;
204 }
205 if (handler != null) {
206 handler.onCameraSwitchDone(info.facing == Camera.CameraInfo.CAMERA_FAC ING_FRONT);
207 }
187 } 208 }
188 }); 209 });
189 return true;
190 } 210 }
191 211
192 // Requests a new output format from the video capturer. Captured frames 212 // Requests a new output format from the video capturer. Captured frames
193 // by the camera will be scaled/or dropped by the video capturer. 213 // by the camera will be scaled/or dropped by the video capturer.
194 public synchronized void onOutputFormatRequest( 214 // TODO(magjed/perkj): Document what this function does. Change name?
195 final int width, final int height, final int fps) { 215 public void onOutputFormatRequest(final int width, final int height, final int fps) {
196 if (cameraThreadHandler == null) {
197 Logging.e(TAG, "Calling onOutputFormatRequest() for already stopped camera .");
198 return;
199 }
200 cameraThreadHandler.post(new Runnable() { 216 cameraThreadHandler.post(new Runnable() {
201 @Override public void run() { 217 @Override public void run() {
202 onOutputFormatRequestOnCameraThread(width, height, fps); 218 onOutputFormatRequestOnCameraThread(width, height, fps);
203 } 219 }
204 }); 220 });
205 } 221 }
206 222
207 // Reconfigure the camera to capture in a new format. This should only be call ed while the camera 223 // Reconfigure the camera to capture in a new format. This should only be call ed while the camera
208 // is running. 224 // is running.
209 public synchronized void changeCaptureFormat( 225 public void changeCaptureFormat(final int width, final int height, final int f ramerate) {
210 final int width, final int height, final int framerate) {
211 if (cameraThreadHandler == null) {
212 Logging.e(TAG, "Calling changeCaptureFormat() for already stopped camera." );
213 return;
214 }
215 cameraThreadHandler.post(new Runnable() { 226 cameraThreadHandler.post(new Runnable() {
216 @Override public void run() { 227 @Override public void run() {
217 startPreviewOnCameraThread(width, height, framerate); 228 startPreviewOnCameraThread(width, height, framerate);
218 } 229 }
219 }); 230 });
220 } 231 }
221 232
222 public synchronized List<CaptureFormat> getSupportedFormats() { 233 // Helper function to retrieve the current camera id synchronously. Note that the camera id might
223 return CameraEnumerationAndroid.getSupportedFormats(id); 234 // change at any point by switchCamera() calls.
235 private int getCurrentCameraId() {
236 synchronized (cameraIdLock) {
237 return id;
238 }
hbos 2015/09/22 09:12:46 If |id| is volatile you would not have to lock her
magjed_webrtc 2015/09/22 15:04:21 Acknowledged.
224 } 239 }
225 240
226 // Return a list of timestamps for the frames that have been sent out, but not returned yet. 241 public List<CaptureFormat> getSupportedFormats() {
227 // Useful for logging and testing. 242 return CameraEnumerationAndroid.getSupportedFormats(getCurrentCameraId());
228 public String pendingFramesTimeStamps() { 243 }
229 return videoBuffers.pendingFramesTimeStamps(); 244
245 // Called from native code.
246 private String getSupportedFormatsAsJson() throws JSONException {
247 return CameraEnumerationAndroid.getSupportedFormatsAsJson(getCurrentCameraId ());
248 }
249
250 // Return number of frames that have been sent out, but not returned yet. Usef ul for logging and
251 // testing. This function blocks until the camera thread has time to process t he request.
252 public int pendingFramesCount() throws InterruptedException {
253 final FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable< Integer>() {
254 @Override
255 public Integer call() {
256 return videoBuffers.pendingFramesCount();
257 }
258 });
259 cameraThreadHandler.post(futureTask);
260 try {
261 return futureTask.get();
262 } catch (ExecutionException e) {
263 throw new RuntimeException("FramePool.pendingFramesCount() threw exception : " + e);
264 }
230 } 265 }
231 266
232 private VideoCapturerAndroid() { 267 private VideoCapturerAndroid() {
233 Logging.d(TAG, "VideoCapturerAndroid"); 268 Logging.d(TAG, "VideoCapturerAndroid");
269 cameraThread = new HandlerThread(TAG);
270 cameraThread.start();
271 cameraThreadHandler = new Handler(cameraThread.getLooper());
272 videoBuffers = new FramePool(cameraThread);
234 } 273 }
235 274
236 // Called by native code. 275 // Called by native code.
237 // Initializes local variables for the camera named |deviceName|. If |deviceNa me| is empty, the 276 // Initializes local variables for the camera named |deviceName|. If |deviceNa me| is empty, the
238 // first available device is used in order to be compatible with the generic V ideoCapturer class. 277 // first available device is used in order to be compatible with the generic V ideoCapturer class.
239 synchronized boolean init(String deviceName) { 278 boolean init(String deviceName) {
240 Logging.d(TAG, "init: " + deviceName); 279 Logging.d(TAG, "init: " + deviceName);
241 if (deviceName == null) 280 if (deviceName == null)
242 return false; 281 return false;
243 282
244 boolean foundDevice = false;
245 if (deviceName.isEmpty()) { 283 if (deviceName.isEmpty()) {
246 this.id = 0; 284 this.id = 0;
hbos 2015/09/22 09:12:46 If on camera thread, rename to initOnCameraThread
magjed_webrtc 2015/09/22 15:04:21 It is not on camera thread, but it will be called
247 foundDevice = true; 285 return true;
248 } else { 286 } else {
249 for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { 287 for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
250 String existing_device = CameraEnumerationAndroid.getDeviceName(i); 288 if (deviceName.equals(CameraEnumerationAndroid.getDeviceName(i))) {
251 if (existing_device != null && deviceName.equals(existing_device)) {
252 this.id = i; 289 this.id = i;
253 foundDevice = true; 290 return true;
254 } 291 }
255 } 292 }
256 } 293 }
257 return foundDevice; 294 return false;
258 } 295 }
259 296
260 String getSupportedFormatsAsJson() throws JSONException { 297 // Called by native code to quit the camera thread. This needs to be done manu ally, otherwise the
261 return CameraEnumerationAndroid.getSupportedFormatsAsJson(id); 298 // thread and handler will not be garbage collected.
299 private void release() {
300 cameraThread.quit();
262 } 301 }
263 302
264 private class CameraThread extends Thread { 303 // Called by native code.
265 private Exchanger<Handler> handlerExchanger;
266 public CameraThread(Exchanger<Handler> handlerExchanger) {
267 this.handlerExchanger = handlerExchanger;
268 }
269
270 @Override public void run() {
271 Looper.prepare();
272 exchange(handlerExchanger, new Handler());
273 Looper.loop();
274 }
275 }
276
277 // Called by native code. Returns true if capturer is started.
278 // 304 //
279 // Note that this actually opens the camera, and Camera callbacks run on the 305 // Note that this actually opens the camera, and Camera callbacks run on the
280 // thread that calls open(), so this is done on the CameraThread. 306 // thread that calls open(), so this is done on the CameraThread.
281 synchronized void startCapture( 307 void startCapture(
282 final int width, final int height, final int framerate, 308 final int width, final int height, final int framerate,
283 final Context applicationContext, final CapturerObserver frameObserver) { 309 final Context applicationContext, final CapturerObserver frameObserver) {
284 Logging.d(TAG, "startCapture requested: " + width + "x" + height 310 Logging.d(TAG, "startCapture requested: " + width + "x" + height
285 + "@" + framerate); 311 + "@" + framerate);
286 if (applicationContext == null) { 312 if (applicationContext == null) {
287 throw new RuntimeException("applicationContext not set."); 313 throw new RuntimeException("applicationContext not set.");
288 } 314 }
289 if (frameObserver == null) { 315 if (frameObserver == null) {
290 throw new RuntimeException("frameObserver not set."); 316 throw new RuntimeException("frameObserver not set.");
291 } 317 }
292 if (cameraThreadHandler != null) {
293 throw new RuntimeException("Camera has already been started.");
294 }
295
296 Exchanger<Handler> handlerExchanger = new Exchanger<Handler>();
297 cameraThread = new CameraThread(handlerExchanger);
298 cameraThread.start();
299 cameraThreadHandler = exchange(handlerExchanger, null);
300 cameraThreadHandler.post(new Runnable() { 318 cameraThreadHandler.post(new Runnable() {
301 @Override public void run() { 319 @Override public void run() {
302 startCaptureOnCameraThread(width, height, framerate, frameObserver, 320 startCaptureOnCameraThread(width, height, framerate, frameObserver,
303 applicationContext); 321 applicationContext);
304 } 322 }
305 }); 323 });
306 } 324 }
307 325
308 private void startCaptureOnCameraThread( 326 private void startCaptureOnCameraThread(
309 int width, int height, int framerate, CapturerObserver frameObserver, 327 int width, int height, int framerate, CapturerObserver frameObserver,
310 Context applicationContext) { 328 Context applicationContext) {
311 Throwable error = null; 329 Throwable error = null;
330 if (camera != null) {
331 throw new RuntimeException("Camera has already been started.");
332 }
312 this.applicationContext = applicationContext; 333 this.applicationContext = applicationContext;
313 this.frameObserver = frameObserver; 334 this.frameObserver = frameObserver;
314 try { 335 try {
315 Logging.d(TAG, "Opening camera " + id); 336 Logging.d(TAG, "Opening camera " + id);
316 camera = Camera.open(id); 337 camera = Camera.open(id);
317 info = new Camera.CameraInfo(); 338 info = new Camera.CameraInfo();
318 Camera.getCameraInfo(id, info); 339 Camera.getCameraInfo(id, info);
319 // No local renderer (we only care about onPreviewFrame() buffers, not a 340 // No local renderer (we only care about onPreviewFrame() buffers, not a
320 // directly-displayed UI element). Camera won't capture without 341 // directly-displayed UI element). Camera won't capture without
321 // setPreview{Texture,Display}, so we create a SurfaceTexture and hand 342 // setPreview{Texture,Display}, so we create a SurfaceTexture and hand
(...skipping 18 matching lines...) Expand all
340 // Start camera observer. 361 // Start camera observer.
341 cameraFramesCount = 0; 362 cameraFramesCount = 0;
342 captureBuffersCount = 0; 363 captureBuffersCount = 0;
343 cameraThreadHandler.postDelayed(cameraObserver, CAMERA_OBSERVER_PERIOD_MS) ; 364 cameraThreadHandler.postDelayed(cameraObserver, CAMERA_OBSERVER_PERIOD_MS) ;
344 return; 365 return;
345 } catch (RuntimeException e) { 366 } catch (RuntimeException e) {
346 error = e; 367 error = e;
347 } 368 }
348 Logging.e(TAG, "startCapture failed", error); 369 Logging.e(TAG, "startCapture failed", error);
349 stopCaptureOnCameraThread(); 370 stopCaptureOnCameraThread();
350 cameraThreadHandler = null;
351 frameObserver.OnCapturerStarted(false); 371 frameObserver.OnCapturerStarted(false);
352 if (errorHandler != null) { 372 if (errorHandler != null) {
353 errorHandler.onCameraError("Camera can not be started."); 373 errorHandler.onCameraError("Camera can not be started.");
354 } 374 }
355 return; 375 return;
356 } 376 }
357 377
358 // (Re)start preview with the closest supported format to |width| x |height| @ |framerate|. 378 // (Re)start preview with the closest supported format to |width| x |height| @ |framerate|.
359 private void startPreviewOnCameraThread(int width, int height, int framerate) { 379 private void startPreviewOnCameraThread(int width, int height, int framerate) {
360 Logging.d( 380 Logging.d(
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
413 // (Re)start preview. 433 // (Re)start preview.
414 Logging.d(TAG, "Start capturing: " + captureFormat); 434 Logging.d(TAG, "Start capturing: " + captureFormat);
415 this.captureFormat = captureFormat; 435 this.captureFormat = captureFormat;
416 camera.setParameters(parameters); 436 camera.setParameters(parameters);
417 videoBuffers.queueCameraBuffers(captureFormat.frameSize(), camera); 437 videoBuffers.queueCameraBuffers(captureFormat.frameSize(), camera);
418 camera.setPreviewCallbackWithBuffer(this); 438 camera.setPreviewCallbackWithBuffer(this);
419 camera.startPreview(); 439 camera.startPreview();
420 } 440 }
421 441
422 // Called by native code. Returns true when camera is known to be stopped. 442 // Called by native code. Returns true when camera is known to be stopped.
423 synchronized void stopCapture() throws InterruptedException { 443 void stopCapture() throws InterruptedException {
424 if (cameraThreadHandler == null) {
425 Logging.e(TAG, "Calling stopCapture() for already stopped camera.");
426 return;
427 }
428 Logging.d(TAG, "stopCapture"); 444 Logging.d(TAG, "stopCapture");
445 final CountDownLatch barrier = new CountDownLatch(1);
429 cameraThreadHandler.post(new Runnable() { 446 cameraThreadHandler.post(new Runnable() {
430 @Override public void run() { 447 @Override public void run() {
431 stopCaptureOnCameraThread(); 448 stopCaptureOnCameraThread();
449 barrier.countDown();
432 } 450 }
433 }); 451 });
434 cameraThread.join(); 452 barrier.await();
435 cameraThreadHandler = null;
436 Logging.d(TAG, "stopCapture done"); 453 Logging.d(TAG, "stopCapture done");
437 } 454 }
438 455
439 private void stopCaptureOnCameraThread() { 456 private void stopCaptureOnCameraThread() {
440 doStopCaptureOnCameraThread();
441 Looper.myLooper().quit();
442 return;
443 }
444
445 private void doStopCaptureOnCameraThread() {
446 Logging.d(TAG, "stopCaptureOnCameraThread"); 457 Logging.d(TAG, "stopCaptureOnCameraThread");
447 if (camera == null) { 458 if (camera == null) {
459 Logging.e(TAG, "Calling stopCapture() for already stopped camera.");
448 return; 460 return;
449 } 461 }
450 462
451 cameraThreadHandler.removeCallbacks(cameraObserver); 463 cameraThreadHandler.removeCallbacks(cameraObserver);
452 Logging.d(TAG, "Stop preview."); 464 Logging.d(TAG, "Stop preview.");
453 camera.stopPreview(); 465 camera.stopPreview();
454 camera.setPreviewCallbackWithBuffer(null); 466 camera.setPreviewCallbackWithBuffer(null);
455 videoBuffers.stopReturnBuffersToCamera(); 467 videoBuffers.stopReturnBuffersToCamera();
456 captureFormat = null; 468 captureFormat = null;
457 469
458 if (cameraGlTexture != 0) { 470 if (cameraGlTexture != 0) {
459 GLES20.glDeleteTextures(1, new int[] {cameraGlTexture}, 0); 471 GLES20.glDeleteTextures(1, new int[] {cameraGlTexture}, 0);
460 cameraGlTexture = 0; 472 cameraGlTexture = 0;
461 } 473 }
462 Logging.d(TAG, "Release camera."); 474 Logging.d(TAG, "Release camera.");
463 camera.release(); 475 camera.release();
464 camera = null; 476 camera = null;
465 cameraSurfaceTexture = null; 477 cameraSurfaceTexture = null;
hbos 2015/09/22 09:12:46 Does cameraSurfaceTexture not have to be released
magjed_webrtc 2015/09/22 15:04:21 Let's try. What can possibly go wrong?
hbos 2015/09/23 07:37:36 YOLO. Random opengl thread crashes incoming. Prob
466 } 478 }
467 479
468 private void switchCameraOnCameraThread(Runnable switchDoneEvent) { 480 private void switchCameraOnCameraThread() {
469 Logging.d(TAG, "switchCameraOnCameraThread"); 481 Logging.d(TAG, "switchCameraOnCameraThread");
470 482 stopCaptureOnCameraThread();
471 doStopCaptureOnCameraThread(); 483 synchronized (cameraIdLock) {
484 id = (id + 1) % Camera.getNumberOfCameras();
485 }
472 startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramera te, frameObserver, 486 startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramera te, frameObserver,
473 applicationContext); 487 applicationContext);
474 pendingCameraSwitch = false;
475 Logging.d(TAG, "switchCameraOnCameraThread done"); 488 Logging.d(TAG, "switchCameraOnCameraThread done");
476 if (switchDoneEvent != null) {
477 switchDoneEvent.run();
478 }
479 } 489 }
480 490
481 private void onOutputFormatRequestOnCameraThread( 491 private void onOutputFormatRequestOnCameraThread(int width, int height, int fp s) {
482 int width, int height, int fps) {
483 if (camera == null) { 492 if (camera == null) {
493 Logging.e(TAG, "Calling onOutputFormatRequest() on stopped camera.");
484 return; 494 return;
485 } 495 }
486 Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + heigh t + 496 Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + heigh t +
487 "@" + fps); 497 "@" + fps);
488 frameObserver.OnOutputFormatRequest(width, height, fps); 498 frameObserver.OnOutputFormatRequest(width, height, fps);
489 } 499 }
490 500
491 void returnBuffer(long timeStamp) { 501 public void returnBuffer(final long timeStamp) {
492 videoBuffers.returnBuffer(timeStamp); 502 cameraThreadHandler.post(new Runnable() {
503 @Override public void run() {
504 videoBuffers.returnBuffer(timeStamp);
505 }
506 });
493 } 507 }
494 508
495 private int getDeviceOrientation() { 509 private int getDeviceOrientation() {
496 int orientation = 0; 510 int orientation = 0;
497 511
498 WindowManager wm = (WindowManager) applicationContext.getSystemService( 512 WindowManager wm = (WindowManager) applicationContext.getSystemService(
499 Context.WINDOW_SERVICE); 513 Context.WINDOW_SERVICE);
500 switch(wm.getDefaultDisplay().getRotation()) { 514 switch(wm.getDefaultDisplay().getRotation()) {
501 case Surface.ROTATION_90: 515 case Surface.ROTATION_90:
502 orientation = 90; 516 orientation = 90;
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
542 // data.length >= videoBuffers.frameSize. 556 // data.length >= videoBuffers.frameSize.
543 if (videoBuffers.reserveByteBuffer(data, captureTimeNs)) { 557 if (videoBuffers.reserveByteBuffer(data, captureTimeNs)) {
544 cameraFramesCount++; 558 cameraFramesCount++;
545 frameObserver.OnFrameCaptured(data, videoBuffers.frameSize, captureFormat. width, 559 frameObserver.OnFrameCaptured(data, videoBuffers.frameSize, captureFormat. width,
546 captureFormat.height, rotation, captureTimeNs); 560 captureFormat.height, rotation, captureTimeNs);
547 } else { 561 } else {
548 Logging.w(TAG, "reserveByteBuffer failed - dropping frame."); 562 Logging.w(TAG, "reserveByteBuffer failed - dropping frame.");
549 } 563 }
550 } 564 }
551 565
552 // runCameraThreadUntilIdle make sure all posted messages to the cameraThread
553 // is processed before returning. It does that by itself posting a message to
554 // to the message queue and waits until is has been processed.
555 // It is used in tests.
556 void runCameraThreadUntilIdle() {
557 if (cameraThreadHandler == null)
558 return;
559 final Exchanger<Boolean> result = new Exchanger<Boolean>();
560 cameraThreadHandler.post(new Runnable() {
561 @Override public void run() {
562 exchange(result, true); // |true| is a dummy here.
563 }
564 });
565 exchange(result, false); // |false| is a dummy value here.
566 return;
567 }
568
569 // Exchanges |value| with |exchanger|, converting InterruptedExceptions to
570 // RuntimeExceptions (since we expect never to see these).
571 private static <T> T exchange(Exchanger<T> exchanger, T value) {
572 try {
573 return exchanger.exchange(value);
574 } catch (InterruptedException e) {
575 throw new RuntimeException(e);
576 }
577 }
578
579 // Class used for allocating and bookkeeping video frames. All buffers are 566 // Class used for allocating and bookkeeping video frames. All buffers are
580 // direct allocated so that they can be directly used from native code. This c lass is 567 // direct allocated so that they can be directly used from native code. This c lass is
581 // synchronized and can be called from multiple threads. 568 // not thread-safe, and enforces single thread use.
582 private static class FramePool { 569 private static class FramePool {
570 // Thread that all calls should be made on.
571 private final Thread thread;
583 // Arbitrary queue depth. Higher number means more memory allocated & held, 572 // Arbitrary queue depth. Higher number means more memory allocated & held,
584 // lower number means more sensitivity to processing time in the client (and 573 // lower number means more sensitivity to processing time in the client (and
585 // potentially stalling the capturer if it runs out of buffers to write to). 574 // potentially stalling the capturer if it runs out of buffers to write to).
586 private static final int numCaptureBuffers = 3; 575 private static final int numCaptureBuffers = 3;
587 // This container tracks the buffers added as camera callback buffers. It is needed for finding 576 // This container tracks the buffers added as camera callback buffers. It is needed for finding
588 // the corresponding ByteBuffer given a byte[]. 577 // the corresponding ByteBuffer given a byte[].
589 private final Map<byte[], ByteBuffer> queuedBuffers = new IdentityHashMap<by te[], ByteBuffer>(); 578 private final Map<byte[], ByteBuffer> queuedBuffers = new IdentityHashMap<by te[], ByteBuffer>();
590 // This container tracks the frames that have been sent but not returned. It is needed for 579 // This container tracks the frames that have been sent but not returned. It is needed for
591 // keeping the buffers alive and for finding the corresponding ByteBuffer gi ven a timestamp. 580 // keeping the buffers alive and for finding the corresponding ByteBuffer gi ven a timestamp.
592 private final Map<Long, ByteBuffer> pendingBuffers = new HashMap<Long, ByteB uffer>(); 581 private final Map<Long, ByteBuffer> pendingBuffers = new HashMap<Long, ByteB uffer>();
593 private int frameSize = 0; 582 private int frameSize = 0;
594 private Camera camera; 583 private Camera camera;
595 584
596 synchronized int numCaptureBuffersAvailable() { 585 public FramePool(Thread thread) {
586 this.thread = thread;
587 }
588
589 private void checkIsOnValidThread() {
hbos 2015/09/22 09:12:46 Optional: Could we have a checkIsOnCameraThread li
magjed_webrtc 2015/09/22 15:04:21 Done.
590 if (Thread.currentThread() != thread) {
591 throw new IllegalStateException("Wrong thread");
592 }
593 }
594
595 public int numCaptureBuffersAvailable() {
596 checkIsOnValidThread();
597 return queuedBuffers.size(); 597 return queuedBuffers.size();
598 } 598 }
599 599
600 // Discards previous queued buffers and adds new callback buffers to camera. 600 // Discards previous queued buffers and adds new callback buffers to camera.
601 synchronized void queueCameraBuffers(int frameSize, Camera camera) { 601 public void queueCameraBuffers(int frameSize, Camera camera) {
602 checkIsOnValidThread();
602 this.camera = camera; 603 this.camera = camera;
603 this.frameSize = frameSize; 604 this.frameSize = frameSize;
604 605
605 queuedBuffers.clear(); 606 queuedBuffers.clear();
606 for (int i = 0; i < numCaptureBuffers; ++i) { 607 for (int i = 0; i < numCaptureBuffers; ++i) {
607 final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize); 608 final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize);
608 camera.addCallbackBuffer(buffer.array()); 609 camera.addCallbackBuffer(buffer.array());
609 queuedBuffers.put(buffer.array(), buffer); 610 queuedBuffers.put(buffer.array(), buffer);
610 } 611 }
611 Logging.d(TAG, "queueCameraBuffers enqueued " + numCaptureBuffers 612 Logging.d(TAG, "queueCameraBuffers enqueued " + numCaptureBuffers
612 + " buffers of size " + frameSize + "."); 613 + " buffers of size " + frameSize + ".");
613 } 614 }
614 615
615 synchronized String pendingFramesTimeStamps() { 616 // Return number of pending frames that have not been returned.
617 public int pendingFramesCount() {
618 checkIsOnValidThread();
619 return pendingBuffers.size();
620 }
621
622 public String pendingFramesTimeStamps() {
623 checkIsOnValidThread();
616 List<Long> timeStampsMs = new ArrayList<Long>(); 624 List<Long> timeStampsMs = new ArrayList<Long>();
617 for (Long timeStampNs : pendingBuffers.keySet()) { 625 for (Long timeStampNs : pendingBuffers.keySet()) {
618 timeStampsMs.add(TimeUnit.NANOSECONDS.toMillis(timeStampNs)); 626 timeStampsMs.add(TimeUnit.NANOSECONDS.toMillis(timeStampNs));
619 } 627 }
620 return timeStampsMs.toString(); 628 return timeStampsMs.toString();
621 } 629 }
622 630
623 synchronized void stopReturnBuffersToCamera() { 631 public void stopReturnBuffersToCamera() {
632 checkIsOnValidThread();
624 this.camera = null; 633 this.camera = null;
625 queuedBuffers.clear(); 634 queuedBuffers.clear();
626 // Frames in |pendingBuffers| need to be kept alive until they are returne d. 635 // Frames in |pendingBuffers| need to be kept alive until they are returne d.
627 Logging.d(TAG, "stopReturnBuffersToCamera called." 636 Logging.d(TAG, "stopReturnBuffersToCamera called."
628 + (pendingBuffers.isEmpty() ? 637 + (pendingBuffers.isEmpty() ?
629 " All buffers have been returned." 638 " All buffers have been returned."
630 : " Pending buffers: " + pendingFramesTimeStamps() + ".")); 639 : " Pending buffers: " + pendingFramesTimeStamps() + "."));
631 } 640 }
632 641
633 synchronized boolean reserveByteBuffer(byte[] data, long timeStamp) { 642 public boolean reserveByteBuffer(byte[] data, long timeStamp) {
643 checkIsOnValidThread();
634 final ByteBuffer buffer = queuedBuffers.remove(data); 644 final ByteBuffer buffer = queuedBuffers.remove(data);
635 if (buffer == null) { 645 if (buffer == null) {
636 // Frames might be posted to |onPreviewFrame| with the previous format w hile changing 646 // Frames might be posted to |onPreviewFrame| with the previous format w hile changing
637 // capture format in |startPreviewOnCameraThread|. Drop these old frames . 647 // capture format in |startPreviewOnCameraThread|. Drop these old frames .
638 Logging.w(TAG, "Received callback buffer from previous configuration wit h length: " 648 Logging.w(TAG, "Received callback buffer from previous configuration wit h length: "
639 + (data == null ? "null" : data.length)); 649 + (data == null ? "null" : data.length));
640 return false; 650 return false;
641 } 651 }
642 if (buffer.capacity() != frameSize) { 652 if (buffer.capacity() != frameSize) {
643 throw new IllegalStateException("Callback buffer has unexpected frame si ze"); 653 throw new IllegalStateException("Callback buffer has unexpected frame si ze");
644 } 654 }
645 if (pendingBuffers.containsKey(timeStamp)) { 655 if (pendingBuffers.containsKey(timeStamp)) {
646 Logging.e(TAG, "Timestamp already present in pending buffers - they need to be unique"); 656 Logging.e(TAG, "Timestamp already present in pending buffers - they need to be unique");
647 return false; 657 return false;
648 } 658 }
649 pendingBuffers.put(timeStamp, buffer); 659 pendingBuffers.put(timeStamp, buffer);
650 if (queuedBuffers.isEmpty()) { 660 if (queuedBuffers.isEmpty()) {
651 Logging.v(TAG, "Camera is running out of capture buffers." 661 Logging.v(TAG, "Camera is running out of capture buffers."
652 + " Pending buffers: " + pendingFramesTimeStamps()); 662 + " Pending buffers: " + pendingFramesTimeStamps());
653 } 663 }
654 return true; 664 return true;
655 } 665 }
656 666
657 synchronized void returnBuffer(long timeStamp) { 667 public void returnBuffer(long timeStamp) {
668 checkIsOnValidThread();
658 final ByteBuffer returnedFrame = pendingBuffers.remove(timeStamp); 669 final ByteBuffer returnedFrame = pendingBuffers.remove(timeStamp);
659 if (returnedFrame == null) { 670 if (returnedFrame == null) {
660 throw new RuntimeException("unknown data buffer with time stamp " 671 throw new RuntimeException("unknown data buffer with time stamp "
661 + timeStamp + "returned?!?"); 672 + timeStamp + "returned?!?");
662 } 673 }
663 674
664 if (camera != null && returnedFrame.capacity() == frameSize) { 675 if (camera != null && returnedFrame.capacity() == frameSize) {
665 camera.addCallbackBuffer(returnedFrame.array()); 676 camera.addCallbackBuffer(returnedFrame.array());
666 if (queuedBuffers.isEmpty()) { 677 if (queuedBuffers.isEmpty()) {
667 Logging.v(TAG, "Frame returned when camera is running out of capture" 678 Logging.v(TAG, "Frame returned when camera is running out of capture"
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
729 } 740 }
730 741
731 private native void nativeCapturerStarted(long nativeCapturer, 742 private native void nativeCapturerStarted(long nativeCapturer,
732 boolean success); 743 boolean success);
733 private native void nativeOnFrameCaptured(long nativeCapturer, 744 private native void nativeOnFrameCaptured(long nativeCapturer,
734 byte[] data, int length, int width, int height, int rotation, long timeS tamp); 745 byte[] data, int length, int width, int height, int rotation, long timeS tamp);
735 private native void nativeOnOutputFormatRequest(long nativeCapturer, 746 private native void nativeOnOutputFormatRequest(long nativeCapturer,
736 int width, int height, int fps); 747 int width, int height, int fps);
737 } 748 }
738 } 749 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698