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

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

Powered by Google App Engine
This is Rietveld 408576698