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

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@s 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.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 HandlerThread cameraThread;
78 private Handler cameraThreadHandler; 74 private final Handler cameraThreadHandler;
79 // |cameraSurfaceTexture| is used with setPreviewTexture. Must be a member, se e issue webrtc:5021. 75 // |cameraSurfaceTexture| is used with setPreviewTexture. Must be a member, se e issue webrtc:5021.
80 private SurfaceTexture cameraSurfaceTexture; 76 private SurfaceTexture cameraSurfaceTexture;
81 private Context applicationContext; 77 private Context applicationContext;
78 // Synchronization lock for |id|.
79 private final Object cameraIdLock = new Object();
82 private int id; 80 private int id;
83 private Camera.CameraInfo info; 81 private Camera.CameraInfo info;
84 private int cameraGlTexture = 0; 82 private int cameraGlTexture = 0;
85 private final FramePool videoBuffers = new FramePool(); 83 private final FramePool videoBuffers;
86 // Remember the requested format in case we want to switch cameras. 84 // Remember the requested format in case we want to switch cameras.
87 private int requestedWidth; 85 private int requestedWidth;
88 private int requestedHeight; 86 private int requestedHeight;
89 private int requestedFramerate; 87 private int requestedFramerate;
90 // The capture format will be the closest supported format to the requested fo rmat. 88 // The capture format will be the closest supported format to the requested fo rmat.
91 private CaptureFormat captureFormat; 89 private CaptureFormat captureFormat;
92 private int cameraFramesCount; 90 private int cameraFramesCount;
93 private int captureBuffersCount; 91 private int captureBuffersCount;
92 private final Object pendingCameraSwitchLock = new Object();
94 private volatile boolean pendingCameraSwitch; 93 private volatile boolean pendingCameraSwitch;
95 private CapturerObserver frameObserver = null; 94 private CapturerObserver frameObserver = null;
96 private CameraErrorHandler errorHandler = null; 95 private CameraErrorHandler errorHandler = null;
97 96
98 // Camera error callback. 97 // Camera error callback.
99 private final Camera.ErrorCallback cameraErrorCallback = 98 private final Camera.ErrorCallback cameraErrorCallback =
100 new Camera.ErrorCallback() { 99 new Camera.ErrorCallback() {
101 @Override 100 @Override
102 public void onError(int error, Camera camera) { 101 public void onError(int error, Camera camera) {
103 String errorMessage; 102 String errorMessage;
(...skipping 25 matching lines...) Expand all
129 String.format("%.1f", averageCaptureBuffersCount) + 128 String.format("%.1f", averageCaptureBuffersCount) +
130 ". Pending buffers: " + videoBuffers.pendingFramesTimeStamps()); 129 ". Pending buffers: " + videoBuffers.pendingFramesTimeStamps());
131 if (cameraFramesCount == 0) { 130 if (cameraFramesCount == 0) {
132 Logging.e(TAG, "Camera freezed."); 131 Logging.e(TAG, "Camera freezed.");
133 if (errorHandler != null) { 132 if (errorHandler != null) {
134 errorHandler.onCameraError("Camera failure."); 133 errorHandler.onCameraError("Camera failure.");
135 } 134 }
136 } else { 135 } else {
137 cameraFramesCount = 0; 136 cameraFramesCount = 0;
138 captureBuffersCount = 0; 137 captureBuffersCount = 0;
139 if (cameraThreadHandler != null) { 138 cameraThreadHandler.postDelayed(this, CAMERA_OBSERVER_PERIOD_MS);
140 cameraThreadHandler.postDelayed(this, CAMERA_OBSERVER_PERIOD_MS);
141 }
142 } 139 }
143 } 140 }
144 }; 141 };
145 142
146 // Camera error handler - invoked when camera stops receiving frames 143 // Camera error handler - invoked when camera stops receiving frames
147 // or any camera exception happens on camera thread. 144 // or any camera exception happens on camera thread.
148 public static interface CameraErrorHandler { 145 public static interface CameraErrorHandler {
149 public void onCameraError(String errorDescription); 146 public void onCameraError(String errorDescription);
150 } 147 }
151 148
149 // Camera switch handler - one of these functions are invoked with the result of switchCamera().
150 // The callback may be called on an arbitrary thread.
151 public interface CameraSwitchHandler {
152 // Invoked on success. |isFrontCamera| is true if the new camera is front fa cing.
153 void onCameraSwitchDone(boolean isFrontCamera);
154 // Invoked on failure, e.g. camera is stopped or only one camera available.
155 void onCameraSwitchError(String errorDescription);
156 }
157
152 public static VideoCapturerAndroid create(String name, 158 public static VideoCapturerAndroid create(String name,
153 CameraErrorHandler errorHandler) { 159 CameraErrorHandler errorHandler) {
154 VideoCapturer capturer = VideoCapturer.create(name); 160 VideoCapturer capturer = VideoCapturer.create(name);
155 if (capturer != null) { 161 if (capturer != null) {
156 VideoCapturerAndroid capturerAndroid = (VideoCapturerAndroid) capturer; 162 VideoCapturerAndroid capturerAndroid = (VideoCapturerAndroid) capturer;
157 capturerAndroid.errorHandler = errorHandler; 163 capturerAndroid.errorHandler = errorHandler;
158 return capturerAndroid; 164 return capturerAndroid;
159 } 165 }
160 return null; 166 return null;
161 } 167 }
162 168
163 // Switch camera to the next valid camera id. This can only be called while 169 // Switch camera to the next valid camera id. This can only be called while
164 // the camera is running. 170 // the camera is running.
165 // Returns true on success. False if the next camera does not support the 171 public void switchCamera(final CameraSwitchHandler handler) {
166 // current resolution. 172 if (Camera.getNumberOfCameras() < 2) {
167 public synchronized boolean switchCamera(final Runnable switchDoneEvent) { 173 if (handler != null) {
168 if (Camera.getNumberOfCameras() < 2 ) 174 handler.onCameraSwitchError("No camera to switch to.");
169 return false; 175 }
170 176 return;
171 if (cameraThreadHandler == null) {
172 Logging.e(TAG, "Calling switchCamera() for stopped camera.");
173 return false;
174 } 177 }
175 if (pendingCameraSwitch) { 178 synchronized (pendingCameraSwitchLock) {
176 // Do not handle multiple camera switch request to avoid blocking 179 if (pendingCameraSwitch) {
177 // camera thread by handling too many switch request from a queue. 180 // Do not handle multiple camera switch request to avoid blocking
178 Logging.w(TAG, "Ignoring camera switch request."); 181 // camera thread by handling too many switch request from a queue.
179 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;
180 } 189 }
181
182 pendingCameraSwitch = true;
183 id = (id + 1) % Camera.getNumberOfCameras();
184 cameraThreadHandler.post(new Runnable() { 190 cameraThreadHandler.post(new Runnable() {
185 @Override public void run() { 191 @Override public void run() {
186 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 }
187 } 205 }
188 }); 206 });
189 return true;
190 } 207 }
191 208
192 // Requests a new output format from the video capturer. Captured frames 209 // Requests a new output format from the video capturer. Captured frames
193 // 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.
194 public synchronized void onOutputFormatRequest( 211 // TODO(magjed/perkj): Document what this function does. Change name?
195 final int width, final int height, final int fps) { 212 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() { 213 cameraThreadHandler.post(new Runnable() {
201 @Override public void run() { 214 @Override public void run() {
202 onOutputFormatRequestOnCameraThread(width, height, fps); 215 onOutputFormatRequestOnCameraThread(width, height, fps);
203 } 216 }
204 }); 217 });
205 } 218 }
206 219
207 // Reconfigure the camera to capture in a new format. This should only be call ed while the camera 220 // Reconfigure the camera to capture in a new format. This should only be call ed while the camera
208 // is running. 221 // is running.
209 public synchronized void changeCaptureFormat( 222 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() { 223 cameraThreadHandler.post(new Runnable() {
216 @Override public void run() { 224 @Override public void run() {
217 startPreviewOnCameraThread(width, height, framerate); 225 startPreviewOnCameraThread(width, height, framerate);
218 } 226 }
219 }); 227 });
220 } 228 }
221 229
222 public synchronized List<CaptureFormat> getSupportedFormats() { 230 // Helper function to retrieve the current camera id synchronously. Note that the camera id might
223 return CameraEnumerationAndroid.getSupportedFormats(id); 231 // change at any point by switchCamera() calls.
232 private int getCurrentCameraId() {
233 synchronized (cameraIdLock) {
234 return id;
235 }
224 } 236 }
225 237
226 // Return a list of timestamps for the frames that have been sent out, but not returned yet. 238 public List<CaptureFormat> getSupportedFormats() {
227 // Useful for logging and testing. 239 return CameraEnumerationAndroid.getSupportedFormats(getCurrentCameraId());
228 public String pendingFramesTimeStamps() { 240 }
229 return videoBuffers.pendingFramesTimeStamps(); 241
242 // Called from native code.
243 private String getSupportedFormatsAsJson() throws JSONException {
244 return CameraEnumerationAndroid.getSupportedFormatsAsJson(getCurrentCameraId ());
230 } 245 }
231 246
232 private VideoCapturerAndroid() { 247 private VideoCapturerAndroid() {
233 Logging.d(TAG, "VideoCapturerAndroid"); 248 Logging.d(TAG, "VideoCapturerAndroid");
249 cameraThread = new HandlerThread(TAG);
250 cameraThread.start();
251 cameraThreadHandler = new Handler(cameraThread.getLooper());
252 videoBuffers = new FramePool(cameraThread);
253 }
254
255 private void checkIsOnCameraThread() {
256 if (Thread.currentThread() != cameraThread) {
257 throw new IllegalStateException("Wrong thread");
258 }
234 } 259 }
235 260
236 // Called by native code. 261 // Called by native code.
237 // Initializes local variables for the camera named |deviceName|. If |deviceNa me| is empty, the 262 // 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. 263 // first available device is used in order to be compatible with the generic V ideoCapturer class.
239 synchronized boolean init(String deviceName) { 264 boolean init(String deviceName) {
240 Logging.d(TAG, "init: " + deviceName); 265 Logging.d(TAG, "init: " + deviceName);
241 if (deviceName == null) 266 if (deviceName == null)
242 return false; 267 return false;
243 268
244 boolean foundDevice = false;
245 if (deviceName.isEmpty()) { 269 if (deviceName.isEmpty()) {
246 this.id = 0; 270 synchronized (cameraIdLock) {
247 foundDevice = true; 271 this.id = 0;
272 }
273 return true;
248 } else { 274 } else {
249 for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { 275 for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
250 String existing_device = CameraEnumerationAndroid.getDeviceName(i); 276 if (deviceName.equals(CameraEnumerationAndroid.getDeviceName(i))) {
251 if (existing_device != null && deviceName.equals(existing_device)) { 277 synchronized (cameraIdLock) {
252 this.id = i; 278 this.id = i;
253 foundDevice = true; 279 }
280 return true;
254 } 281 }
255 } 282 }
256 } 283 }
257 return foundDevice; 284 return false;
258 } 285 }
259 286
260 String getSupportedFormatsAsJson() throws JSONException { 287 // Called by native code to quit the camera thread. This needs to be done manu ally, otherwise the
261 return CameraEnumerationAndroid.getSupportedFormatsAsJson(id); 288 // thread and handler will not be garbage collected.
289 private void release() {
290 if (isReleased()) {
291 throw new IllegalStateException("Already released");
292 }
293 cameraThreadHandler.post(new Runnable() {
294 @Override
295 public void run() {
296 if (camera != null) {
297 throw new IllegalStateException("Release called while camera is runnin g");
298 }
299 if (videoBuffers.pendingFramesCount() != 0) {
300 throw new IllegalStateException("Release called with pending frames le ft");
301 }
302 }
303 });
304 cameraThread.quitSafely();
305 ThreadUtils.joinUninterruptibly(cameraThread);
306 cameraThread = null;
262 } 307 }
263 308
264 private class CameraThread extends Thread { 309 // Used for testing purposes to check if release() has been called.
265 private Exchanger<Handler> handlerExchanger; 310 public boolean isReleased() {
266 public CameraThread(Exchanger<Handler> handlerExchanger) { 311 return (cameraThread == null);
267 this.handlerExchanger = handlerExchanger;
268 }
269
270 @Override public void run() {
271 Looper.prepare();
272 exchange(handlerExchanger, new Handler());
273 Looper.loop();
274 }
275 } 312 }
276 313
277 // Called by native code. Returns true if capturer is started. 314 // Called by native code.
278 // 315 //
279 // Note that this actually opens the camera, and Camera callbacks run on the 316 // 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. 317 // thread that calls open(), so this is done on the CameraThread.
281 synchronized void startCapture( 318 void startCapture(
282 final int width, final int height, final int framerate, 319 final int width, final int height, final int framerate,
283 final Context applicationContext, final CapturerObserver frameObserver) { 320 final Context applicationContext, final CapturerObserver frameObserver) {
284 Logging.d(TAG, "startCapture requested: " + width + "x" + height 321 Logging.d(TAG, "startCapture requested: " + width + "x" + height
285 + "@" + framerate); 322 + "@" + framerate);
286 if (applicationContext == null) { 323 if (applicationContext == null) {
287 throw new RuntimeException("applicationContext not set."); 324 throw new RuntimeException("applicationContext not set.");
288 } 325 }
289 if (frameObserver == null) { 326 if (frameObserver == null) {
290 throw new RuntimeException("frameObserver not set."); 327 throw new RuntimeException("frameObserver not set.");
291 } 328 }
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() { 329 cameraThreadHandler.post(new Runnable() {
301 @Override public void run() { 330 @Override public void run() {
302 startCaptureOnCameraThread(width, height, framerate, frameObserver, 331 startCaptureOnCameraThread(width, height, framerate, frameObserver,
303 applicationContext); 332 applicationContext);
304 } 333 }
305 }); 334 });
306 } 335 }
307 336
308 private void startCaptureOnCameraThread( 337 private void startCaptureOnCameraThread(
309 int width, int height, int framerate, CapturerObserver frameObserver, 338 int width, int height, int framerate, CapturerObserver frameObserver,
310 Context applicationContext) { 339 Context applicationContext) {
311 Throwable error = null; 340 Throwable error = null;
341 checkIsOnCameraThread();
342 if (camera != null) {
343 throw new RuntimeException("Camera has already been started.");
344 }
312 this.applicationContext = applicationContext; 345 this.applicationContext = applicationContext;
313 this.frameObserver = frameObserver; 346 this.frameObserver = frameObserver;
314 try { 347 try {
315 Logging.d(TAG, "Opening camera " + id); 348 synchronized (cameraIdLock) {
316 camera = Camera.open(id); 349 Logging.d(TAG, "Opening camera " + id);
317 info = new Camera.CameraInfo(); 350 camera = Camera.open(id);
318 Camera.getCameraInfo(id, info); 351 info = new Camera.CameraInfo();
352 Camera.getCameraInfo(id, info);
353 }
319 // No local renderer (we only care about onPreviewFrame() buffers, not a 354 // No local renderer (we only care about onPreviewFrame() buffers, not a
320 // directly-displayed UI element). Camera won't capture without 355 // directly-displayed UI element). Camera won't capture without
321 // setPreview{Texture,Display}, so we create a SurfaceTexture and hand 356 // setPreview{Texture,Display}, so we create a SurfaceTexture and hand
322 // it over to Camera, but never listen for frame-ready callbacks, 357 // it over to Camera, but never listen for frame-ready callbacks,
323 // and never call updateTexImage on it. 358 // and never call updateTexImage on it.
324 try { 359 try {
325 cameraGlTexture = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_O ES); 360 cameraGlTexture = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_O ES);
326 cameraSurfaceTexture = new SurfaceTexture(cameraGlTexture); 361 cameraSurfaceTexture = new SurfaceTexture(cameraGlTexture);
327 cameraSurfaceTexture.setOnFrameAvailableListener(null); 362 cameraSurfaceTexture.setOnFrameAvailableListener(null);
328 camera.setPreviewTexture(cameraSurfaceTexture); 363 camera.setPreviewTexture(cameraSurfaceTexture);
(...skipping 11 matching lines...) Expand all
340 // Start camera observer. 375 // Start camera observer.
341 cameraFramesCount = 0; 376 cameraFramesCount = 0;
342 captureBuffersCount = 0; 377 captureBuffersCount = 0;
343 cameraThreadHandler.postDelayed(cameraObserver, CAMERA_OBSERVER_PERIOD_MS) ; 378 cameraThreadHandler.postDelayed(cameraObserver, CAMERA_OBSERVER_PERIOD_MS) ;
344 return; 379 return;
345 } catch (RuntimeException e) { 380 } catch (RuntimeException e) {
346 error = e; 381 error = e;
347 } 382 }
348 Logging.e(TAG, "startCapture failed", error); 383 Logging.e(TAG, "startCapture failed", error);
349 stopCaptureOnCameraThread(); 384 stopCaptureOnCameraThread();
350 cameraThreadHandler = null;
351 frameObserver.OnCapturerStarted(false); 385 frameObserver.OnCapturerStarted(false);
352 if (errorHandler != null) { 386 if (errorHandler != null) {
353 errorHandler.onCameraError("Camera can not be started."); 387 errorHandler.onCameraError("Camera can not be started.");
354 } 388 }
355 return; 389 return;
356 } 390 }
357 391
358 // (Re)start preview with the closest supported format to |width| x |height| @ |framerate|. 392 // (Re)start preview with the closest supported format to |width| x |height| @ |framerate|.
359 private void startPreviewOnCameraThread(int width, int height, int framerate) { 393 private void startPreviewOnCameraThread(int width, int height, int framerate) {
394 checkIsOnCameraThread();
360 Logging.d( 395 Logging.d(
361 TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + " @" + framerate); 396 TAG, "startPreviewOnCameraThread requested: " + width + "x" + height + " @" + framerate);
362 if (camera == null) { 397 if (camera == null) {
363 Logging.e(TAG, "Calling startPreviewOnCameraThread on stopped camera."); 398 Logging.e(TAG, "Calling startPreviewOnCameraThread on stopped camera.");
364 return; 399 return;
365 } 400 }
366 401
367 requestedWidth = width; 402 requestedWidth = width;
368 requestedHeight = height; 403 requestedHeight = height;
369 requestedFramerate = framerate; 404 requestedFramerate = framerate;
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
413 // (Re)start preview. 448 // (Re)start preview.
414 Logging.d(TAG, "Start capturing: " + captureFormat); 449 Logging.d(TAG, "Start capturing: " + captureFormat);
415 this.captureFormat = captureFormat; 450 this.captureFormat = captureFormat;
416 camera.setParameters(parameters); 451 camera.setParameters(parameters);
417 videoBuffers.queueCameraBuffers(captureFormat.frameSize(), camera); 452 videoBuffers.queueCameraBuffers(captureFormat.frameSize(), camera);
418 camera.setPreviewCallbackWithBuffer(this); 453 camera.setPreviewCallbackWithBuffer(this);
419 camera.startPreview(); 454 camera.startPreview();
420 } 455 }
421 456
422 // Called by native code. Returns true when camera is known to be stopped. 457 // Called by native code. Returns true when camera is known to be stopped.
423 synchronized void stopCapture() throws InterruptedException { 458 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"); 459 Logging.d(TAG, "stopCapture");
460 final CountDownLatch barrier = new CountDownLatch(1);
429 cameraThreadHandler.post(new Runnable() { 461 cameraThreadHandler.post(new Runnable() {
430 @Override public void run() { 462 @Override public void run() {
431 stopCaptureOnCameraThread(); 463 stopCaptureOnCameraThread();
464 barrier.countDown();
432 } 465 }
433 }); 466 });
434 cameraThread.join(); 467 barrier.await();
435 cameraThreadHandler = null;
436 Logging.d(TAG, "stopCapture done"); 468 Logging.d(TAG, "stopCapture done");
437 } 469 }
438 470
439 private void stopCaptureOnCameraThread() { 471 private void stopCaptureOnCameraThread() {
440 doStopCaptureOnCameraThread(); 472 checkIsOnCameraThread();
441 Looper.myLooper().quit();
442 return;
443 }
444
445 private void doStopCaptureOnCameraThread() {
446 Logging.d(TAG, "stopCaptureOnCameraThread"); 473 Logging.d(TAG, "stopCaptureOnCameraThread");
447 if (camera == null) { 474 if (camera == null) {
475 Logging.e(TAG, "Calling stopCapture() for already stopped camera.");
448 return; 476 return;
449 } 477 }
450 478
451 cameraThreadHandler.removeCallbacks(cameraObserver); 479 cameraThreadHandler.removeCallbacks(cameraObserver);
452 Logging.d(TAG, "Stop preview."); 480 Logging.d(TAG, "Stop preview.");
453 camera.stopPreview(); 481 camera.stopPreview();
454 camera.setPreviewCallbackWithBuffer(null); 482 camera.setPreviewCallbackWithBuffer(null);
455 videoBuffers.stopReturnBuffersToCamera(); 483 videoBuffers.stopReturnBuffersToCamera();
456 captureFormat = null; 484 captureFormat = null;
457 485
458 if (cameraGlTexture != 0) { 486 if (cameraGlTexture != 0) {
459 GLES20.glDeleteTextures(1, new int[] {cameraGlTexture}, 0); 487 GLES20.glDeleteTextures(1, new int[] {cameraGlTexture}, 0);
460 cameraGlTexture = 0; 488 cameraGlTexture = 0;
461 } 489 }
462 Logging.d(TAG, "Release camera."); 490 Logging.d(TAG, "Release camera.");
463 camera.release(); 491 camera.release();
464 camera = null; 492 camera = null;
493 cameraSurfaceTexture.release();
465 cameraSurfaceTexture = null; 494 cameraSurfaceTexture = null;
466 } 495 }
467 496
468 private void switchCameraOnCameraThread(Runnable switchDoneEvent) { 497 private void switchCameraOnCameraThread() {
498 checkIsOnCameraThread();
469 Logging.d(TAG, "switchCameraOnCameraThread"); 499 Logging.d(TAG, "switchCameraOnCameraThread");
470 500 stopCaptureOnCameraThread();
471 doStopCaptureOnCameraThread(); 501 synchronized (cameraIdLock) {
502 id = (id + 1) % Camera.getNumberOfCameras();
503 }
472 startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramera te, frameObserver, 504 startCaptureOnCameraThread(requestedWidth, requestedHeight, requestedFramera te, frameObserver,
473 applicationContext); 505 applicationContext);
474 pendingCameraSwitch = false;
475 Logging.d(TAG, "switchCameraOnCameraThread done"); 506 Logging.d(TAG, "switchCameraOnCameraThread done");
476 if (switchDoneEvent != null) {
477 switchDoneEvent.run();
478 }
479 } 507 }
480 508
481 private void onOutputFormatRequestOnCameraThread( 509 private void onOutputFormatRequestOnCameraThread(int width, int height, int fp s) {
482 int width, int height, int fps) { 510 checkIsOnCameraThread();
483 if (camera == null) { 511 if (camera == null) {
512 Logging.e(TAG, "Calling onOutputFormatRequest() on stopped camera.");
484 return; 513 return;
485 } 514 }
486 Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + heigh t + 515 Logging.d(TAG, "onOutputFormatRequestOnCameraThread: " + width + "x" + heigh t +
487 "@" + fps); 516 "@" + fps);
488 frameObserver.OnOutputFormatRequest(width, height, fps); 517 frameObserver.OnOutputFormatRequest(width, height, fps);
489 } 518 }
490 519
491 void returnBuffer(long timeStamp) { 520 public void returnBuffer(final long timeStamp) {
492 videoBuffers.returnBuffer(timeStamp); 521 cameraThreadHandler.post(new Runnable() {
522 @Override public void run() {
523 videoBuffers.returnBuffer(timeStamp);
524 }
525 });
493 } 526 }
494 527
495 private int getDeviceOrientation() { 528 private int getDeviceOrientation() {
496 int orientation = 0; 529 int orientation = 0;
497 530
498 WindowManager wm = (WindowManager) applicationContext.getSystemService( 531 WindowManager wm = (WindowManager) applicationContext.getSystemService(
499 Context.WINDOW_SERVICE); 532 Context.WINDOW_SERVICE);
500 switch(wm.getDefaultDisplay().getRotation()) { 533 switch(wm.getDefaultDisplay().getRotation()) {
501 case Surface.ROTATION_90: 534 case Surface.ROTATION_90:
502 orientation = 90; 535 orientation = 90;
503 break; 536 break;
504 case Surface.ROTATION_180: 537 case Surface.ROTATION_180:
505 orientation = 180; 538 orientation = 180;
506 break; 539 break;
507 case Surface.ROTATION_270: 540 case Surface.ROTATION_270:
508 orientation = 270; 541 orientation = 270;
509 break; 542 break;
510 case Surface.ROTATION_0: 543 case Surface.ROTATION_0:
511 default: 544 default:
512 orientation = 0; 545 orientation = 0;
513 break; 546 break;
514 } 547 }
515 return orientation; 548 return orientation;
516 } 549 }
517 550
518 // Called on cameraThread so must not "synchronized". 551 // Called on cameraThread so must not "synchronized".
519 @Override 552 @Override
520 public void onPreviewFrame(byte[] data, Camera callbackCamera) { 553 public void onPreviewFrame(byte[] data, Camera callbackCamera) {
521 if (Thread.currentThread() != cameraThread) { 554 checkIsOnCameraThread();
522 throw new RuntimeException("Camera callback not on camera thread?!?");
523 }
524 if (camera == null) { 555 if (camera == null) {
525 return; 556 return;
526 } 557 }
527 if (camera != callbackCamera) { 558 if (camera != callbackCamera) {
528 throw new RuntimeException("Unexpected camera in callback!"); 559 throw new RuntimeException("Unexpected camera in callback!");
529 } 560 }
530 561
531 final long captureTimeNs = 562 final long captureTimeNs =
532 TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime()); 563 TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
533 564
534 captureBuffersCount += videoBuffers.numCaptureBuffersAvailable(); 565 captureBuffersCount += videoBuffers.numCaptureBuffersAvailable();
535 int rotation = getDeviceOrientation(); 566 int rotation = getDeviceOrientation();
536 if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { 567 if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
537 rotation = 360 - rotation; 568 rotation = 360 - rotation;
538 } 569 }
539 rotation = (info.orientation + rotation) % 360; 570 rotation = (info.orientation + rotation) % 360;
540 // Mark the frame owning |data| as used. 571 // Mark the frame owning |data| as used.
541 // Note that since data is directBuffer, 572 // Note that since data is directBuffer,
542 // data.length >= videoBuffers.frameSize. 573 // data.length >= videoBuffers.frameSize.
543 if (videoBuffers.reserveByteBuffer(data, captureTimeNs)) { 574 if (videoBuffers.reserveByteBuffer(data, captureTimeNs)) {
544 cameraFramesCount++; 575 cameraFramesCount++;
545 frameObserver.OnFrameCaptured(data, videoBuffers.frameSize, captureFormat. width, 576 frameObserver.OnFrameCaptured(data, videoBuffers.frameSize, captureFormat. width,
546 captureFormat.height, rotation, captureTimeNs); 577 captureFormat.height, rotation, captureTimeNs);
547 } else { 578 } else {
548 Logging.w(TAG, "reserveByteBuffer failed - dropping frame."); 579 Logging.w(TAG, "reserveByteBuffer failed - dropping frame.");
549 } 580 }
550 } 581 }
551 582
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 583 // 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 584 // 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. 585 // not thread-safe, and enforces single thread use.
582 private static class FramePool { 586 private static class FramePool {
587 // Thread that all calls should be made on.
588 private final Thread thread;
583 // Arbitrary queue depth. Higher number means more memory allocated & held, 589 // Arbitrary queue depth. Higher number means more memory allocated & held,
584 // lower number means more sensitivity to processing time in the client (and 590 // 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). 591 // potentially stalling the capturer if it runs out of buffers to write to).
586 private static final int numCaptureBuffers = 3; 592 private static final int numCaptureBuffers = 3;
587 // This container tracks the buffers added as camera callback buffers. It is needed for finding 593 // This container tracks the buffers added as camera callback buffers. It is needed for finding
588 // the corresponding ByteBuffer given a byte[]. 594 // the corresponding ByteBuffer given a byte[].
589 private final Map<byte[], ByteBuffer> queuedBuffers = new IdentityHashMap<by te[], ByteBuffer>(); 595 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 596 // 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. 597 // 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>(); 598 private final Map<Long, ByteBuffer> pendingBuffers = new HashMap<Long, ByteB uffer>();
593 private int frameSize = 0; 599 private int frameSize = 0;
594 private Camera camera; 600 private Camera camera;
595 601
596 synchronized int numCaptureBuffersAvailable() { 602 public FramePool(Thread thread) {
603 this.thread = thread;
604 }
605
606 private void checkIsOnValidThread() {
607 if (Thread.currentThread() != thread) {
608 throw new IllegalStateException("Wrong thread");
609 }
610 }
611
612 public int numCaptureBuffersAvailable() {
613 checkIsOnValidThread();
597 return queuedBuffers.size(); 614 return queuedBuffers.size();
598 } 615 }
599 616
600 // Discards previous queued buffers and adds new callback buffers to camera. 617 // Discards previous queued buffers and adds new callback buffers to camera.
601 synchronized void queueCameraBuffers(int frameSize, Camera camera) { 618 public void queueCameraBuffers(int frameSize, Camera camera) {
619 checkIsOnValidThread();
602 this.camera = camera; 620 this.camera = camera;
603 this.frameSize = frameSize; 621 this.frameSize = frameSize;
604 622
605 queuedBuffers.clear(); 623 queuedBuffers.clear();
606 for (int i = 0; i < numCaptureBuffers; ++i) { 624 for (int i = 0; i < numCaptureBuffers; ++i) {
607 final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize); 625 final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize);
608 camera.addCallbackBuffer(buffer.array()); 626 camera.addCallbackBuffer(buffer.array());
609 queuedBuffers.put(buffer.array(), buffer); 627 queuedBuffers.put(buffer.array(), buffer);
610 } 628 }
611 Logging.d(TAG, "queueCameraBuffers enqueued " + numCaptureBuffers 629 Logging.d(TAG, "queueCameraBuffers enqueued " + numCaptureBuffers
612 + " buffers of size " + frameSize + "."); 630 + " buffers of size " + frameSize + ".");
613 } 631 }
614 632
615 synchronized String pendingFramesTimeStamps() { 633 // Return number of pending frames that have not been returned.
634 public int pendingFramesCount() {
635 checkIsOnValidThread();
636 return pendingBuffers.size();
637 }
638
639 public String pendingFramesTimeStamps() {
640 checkIsOnValidThread();
616 List<Long> timeStampsMs = new ArrayList<Long>(); 641 List<Long> timeStampsMs = new ArrayList<Long>();
617 for (Long timeStampNs : pendingBuffers.keySet()) { 642 for (Long timeStampNs : pendingBuffers.keySet()) {
618 timeStampsMs.add(TimeUnit.NANOSECONDS.toMillis(timeStampNs)); 643 timeStampsMs.add(TimeUnit.NANOSECONDS.toMillis(timeStampNs));
619 } 644 }
620 return timeStampsMs.toString(); 645 return timeStampsMs.toString();
621 } 646 }
622 647
623 synchronized void stopReturnBuffersToCamera() { 648 public void stopReturnBuffersToCamera() {
649 checkIsOnValidThread();
624 this.camera = null; 650 this.camera = null;
625 queuedBuffers.clear(); 651 queuedBuffers.clear();
626 // Frames in |pendingBuffers| need to be kept alive until they are returne d. 652 // Frames in |pendingBuffers| need to be kept alive until they are returne d.
627 Logging.d(TAG, "stopReturnBuffersToCamera called." 653 Logging.d(TAG, "stopReturnBuffersToCamera called."
628 + (pendingBuffers.isEmpty() ? 654 + (pendingBuffers.isEmpty() ?
629 " All buffers have been returned." 655 " All buffers have been returned."
630 : " Pending buffers: " + pendingFramesTimeStamps() + ".")); 656 : " Pending buffers: " + pendingFramesTimeStamps() + "."));
631 } 657 }
632 658
633 synchronized boolean reserveByteBuffer(byte[] data, long timeStamp) { 659 public boolean reserveByteBuffer(byte[] data, long timeStamp) {
660 checkIsOnValidThread();
634 final ByteBuffer buffer = queuedBuffers.remove(data); 661 final ByteBuffer buffer = queuedBuffers.remove(data);
635 if (buffer == null) { 662 if (buffer == null) {
636 // Frames might be posted to |onPreviewFrame| with the previous format w hile changing 663 // Frames might be posted to |onPreviewFrame| with the previous format w hile changing
637 // capture format in |startPreviewOnCameraThread|. Drop these old frames . 664 // capture format in |startPreviewOnCameraThread|. Drop these old frames .
638 Logging.w(TAG, "Received callback buffer from previous configuration wit h length: " 665 Logging.w(TAG, "Received callback buffer from previous configuration wit h length: "
639 + (data == null ? "null" : data.length)); 666 + (data == null ? "null" : data.length));
640 return false; 667 return false;
641 } 668 }
642 if (buffer.capacity() != frameSize) { 669 if (buffer.capacity() != frameSize) {
643 throw new IllegalStateException("Callback buffer has unexpected frame si ze"); 670 throw new IllegalStateException("Callback buffer has unexpected frame si ze");
644 } 671 }
645 if (pendingBuffers.containsKey(timeStamp)) { 672 if (pendingBuffers.containsKey(timeStamp)) {
646 Logging.e(TAG, "Timestamp already present in pending buffers - they need to be unique"); 673 Logging.e(TAG, "Timestamp already present in pending buffers - they need to be unique");
647 return false; 674 return false;
648 } 675 }
649 pendingBuffers.put(timeStamp, buffer); 676 pendingBuffers.put(timeStamp, buffer);
650 if (queuedBuffers.isEmpty()) { 677 if (queuedBuffers.isEmpty()) {
651 Logging.v(TAG, "Camera is running out of capture buffers." 678 Logging.v(TAG, "Camera is running out of capture buffers."
652 + " Pending buffers: " + pendingFramesTimeStamps()); 679 + " Pending buffers: " + pendingFramesTimeStamps());
653 } 680 }
654 return true; 681 return true;
655 } 682 }
656 683
657 synchronized void returnBuffer(long timeStamp) { 684 public void returnBuffer(long timeStamp) {
685 checkIsOnValidThread();
658 final ByteBuffer returnedFrame = pendingBuffers.remove(timeStamp); 686 final ByteBuffer returnedFrame = pendingBuffers.remove(timeStamp);
659 if (returnedFrame == null) { 687 if (returnedFrame == null) {
660 throw new RuntimeException("unknown data buffer with time stamp " 688 throw new RuntimeException("unknown data buffer with time stamp "
661 + timeStamp + "returned?!?"); 689 + timeStamp + "returned?!?");
662 } 690 }
663 691
664 if (camera != null && returnedFrame.capacity() == frameSize) { 692 if (camera != null && returnedFrame.capacity() == frameSize) {
665 camera.addCallbackBuffer(returnedFrame.array()); 693 camera.addCallbackBuffer(returnedFrame.array());
666 if (queuedBuffers.isEmpty()) { 694 if (queuedBuffers.isEmpty()) {
667 Logging.v(TAG, "Frame returned when camera is running out of capture" 695 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 } 757 }
730 758
731 private native void nativeCapturerStarted(long nativeCapturer, 759 private native void nativeCapturerStarted(long nativeCapturer,
732 boolean success); 760 boolean success);
733 private native void nativeOnFrameCaptured(long nativeCapturer, 761 private native void nativeOnFrameCaptured(long nativeCapturer,
734 byte[] data, int length, int width, int height, int rotation, long timeS tamp); 762 byte[] data, int length, int width, int height, int rotation, long timeS tamp);
735 private native void nativeOnOutputFormatRequest(long nativeCapturer, 763 private native void nativeOnOutputFormatRequest(long nativeCapturer,
736 int width, int height, int fps); 764 int width, int height, int fps);
737 } 765 }
738 } 766 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698