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.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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 } |
OLD | NEW |