OLD | NEW |
---|---|
1 /* | 1 /* |
2 * libjingle | 2 * libjingle |
3 * Copyright 2015 Google Inc. | 3 * Copyright 2015 Google Inc. |
4 * | 4 * |
5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
6 * modification, are permitted provided that the following conditions are met: | 6 * modification, are permitted provided that the following conditions are met: |
7 * | 7 * |
8 * 1. Redistributions of source code must retain the above copyright notice, | 8 * 1. Redistributions of source code must retain the above copyright notice, |
9 * this list of conditions and the following disclaimer. | 9 * this list of conditions and the following disclaimer. |
10 * 2. Redistributions in binary form must reproduce the above copyright notice, | 10 * 2. Redistributions in binary form must reproduce the above copyright notice, |
(...skipping 16 matching lines...) Expand all Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 } |
OLD | NEW |