• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.testingcamera2;
18 
19 import android.content.Context;
20 import android.graphics.ImageFormat;
21 import android.hardware.camera2.CameraAccessException;
22 import android.hardware.camera2.CameraDevice;
23 import android.hardware.camera2.CameraManager;
24 import android.hardware.camera2.CameraMetadata;
25 import android.hardware.camera2.CameraCharacteristics;
26 import android.hardware.camera2.CaptureRequest;
27 import android.hardware.camera2.Size;
28 import android.media.Image;
29 import android.media.ImageReader;
30 import android.os.Handler;
31 import android.os.HandlerThread;
32 import android.os.Looper;
33 import android.os.Message;
34 import android.util.Log;
35 import android.view.Surface;
36 import android.view.SurfaceHolder;
37 
38 import com.android.ex.camera2.blocking.BlockingCameraManager;
39 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
40 import com.android.ex.camera2.blocking.BlockingStateListener;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.List;
45 
46 /**
47  * A camera controller class that runs in its own thread, to
48  * move camera ops off the UI. Generally thread-safe.
49  */
50 public class CameraOps {
51 
52     private static final String TAG = "CameraOps";
53 
54     private final HandlerThread mOpsThread;
55     private final Handler mOpsHandler;
56 
57     private final CameraManager mCameraManager;
58     private final BlockingCameraManager mBlockingCameraManager;
59     private final BlockingStateListener mDeviceListener =
60             new BlockingStateListener();
61 
62     private CameraDevice mCamera;
63 
64     private ImageReader mCaptureReader;
65     private CameraCharacteristics mCameraCharacteristics;
66 
67     private int mEncodingBitRate;
68 
69     private CaptureRequest.Builder mPreviewRequestBuilder;
70     private CaptureRequest.Builder mRecordingRequestBuilder;
71     List<Surface> mOutputSurfaces = new ArrayList<Surface>(2);
72     private Surface mPreviewSurface;
73     // How many JPEG buffers do we want to hold on to at once
74     private static final int MAX_CONCURRENT_JPEGS = 2;
75 
76     private static final int STATUS_ERROR = 0;
77     private static final int STATUS_UNINITIALIZED = 1;
78     private static final int STATUS_OK = 2;
79     // low encoding bitrate(bps), used by small resolution like 640x480.
80     private static final int ENC_BIT_RATE_LOW = 2000000;
81     // high encoding bitrate(bps), used by large resolution like 1080p.
82     private static final int ENC_BIT_RATE_HIGH = 10000000;
83     private static final Size DEFAULT_SIZE = new Size(640, 480);
84     private static final Size HIGH_RESOLUTION_SIZE = new Size(1920, 1080);
85 
86     private static final long IDLE_WAIT_MS = 2000;
87     // General short wait timeout for most state transitions
88     private static final long STATE_WAIT_MS = 500;
89 
90     private int mStatus = STATUS_UNINITIALIZED;
91 
92     CameraRecordingStream mRecordingStream;
93 
checkOk()94     private void checkOk() {
95         if (mStatus < STATUS_OK) {
96             throw new IllegalStateException(String.format("Device not OK: %d", mStatus ));
97         }
98     }
99 
CameraOps(Context ctx)100     private CameraOps(Context ctx) throws ApiFailureException {
101         mCameraManager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
102         if (mCameraManager == null) {
103             throw new ApiFailureException("Can't connect to camera manager!");
104         }
105         mBlockingCameraManager = new BlockingCameraManager(mCameraManager);
106 
107         mOpsThread = new HandlerThread("CameraOpsThread");
108         mOpsThread.start();
109         mOpsHandler = new Handler(mOpsThread.getLooper());
110 
111         mRecordingStream = new CameraRecordingStream();
112         mStatus = STATUS_OK;
113     }
114 
create(Context ctx)115     static public CameraOps create(Context ctx) throws ApiFailureException {
116         return new CameraOps(ctx);
117     }
118 
getDevices()119     public String[] getDevices() throws ApiFailureException{
120         checkOk();
121         try {
122             return mCameraManager.getCameraIdList();
123         } catch (CameraAccessException e) {
124             throw new ApiFailureException("Can't query device set", e);
125         }
126     }
127 
registerCameraListener(CameraManager.AvailabilityListener listener)128     public void registerCameraListener(CameraManager.AvailabilityListener listener)
129             throws ApiFailureException {
130         checkOk();
131         mCameraManager.addAvailabilityListener(listener, mOpsHandler);
132     }
133 
getCameraCharacteristics()134     public CameraCharacteristics getCameraCharacteristics() {
135         checkOk();
136         if (mCameraCharacteristics == null) {
137             throw new IllegalStateException("CameraCharacteristics is not available");
138         }
139         return mCameraCharacteristics;
140     }
141 
closeDevice()142     public void closeDevice()
143             throws ApiFailureException {
144         checkOk();
145         mCameraCharacteristics = null;
146 
147         if (mCamera == null) return;
148 
149         try {
150             mCamera.close();
151         } catch (Exception e) {
152             throw new ApiFailureException("can't close device!", e);
153         }
154 
155         mCamera = null;
156     }
157 
minimalOpenCamera()158     private void minimalOpenCamera() throws ApiFailureException {
159         if (mCamera == null) {
160             try {
161                 String[] devices = mCameraManager.getCameraIdList();
162                 if (devices == null || devices.length == 0) {
163                     throw new ApiFailureException("no devices");
164                 }
165                 mCamera = mBlockingCameraManager.openCamera(devices[0],
166                         mDeviceListener, mOpsHandler);
167                 mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCamera.getId());
168             } catch (CameraAccessException e) {
169                 throw new ApiFailureException("open failure", e);
170             } catch (BlockingOpenException e) {
171                 throw new ApiFailureException("open async failure", e);
172             }
173         }
174 
175         mStatus = STATUS_OK;
176     }
177 
configureOutputs(List<Surface> outputs)178     private void configureOutputs(List<Surface> outputs) throws CameraAccessException {
179         mCamera.configureOutputs(outputs);
180         mDeviceListener.waitForState(BlockingStateListener.STATE_BUSY,
181                 STATE_WAIT_MS);
182         mDeviceListener.waitForState(BlockingStateListener.STATE_IDLE,
183                 IDLE_WAIT_MS);
184     }
185 
186     /**
187      * Set up SurfaceView dimensions for camera preview
188      */
minimalPreviewConfig(SurfaceHolder previewHolder)189     public void minimalPreviewConfig(SurfaceHolder previewHolder) throws ApiFailureException {
190 
191         minimalOpenCamera();
192         try {
193             CameraCharacteristics properties =
194                     mCameraManager.getCameraCharacteristics(mCamera.getId());
195 
196             Size[] previewSizes = null;
197             Size sz = DEFAULT_SIZE;
198             if (properties != null) {
199                 previewSizes = properties.get(
200                         CameraCharacteristics.SCALER_AVAILABLE_PROCESSED_SIZES);
201             }
202 
203             if (previewSizes != null && previewSizes.length != 0 &&
204                     Arrays.asList(previewSizes).contains(HIGH_RESOLUTION_SIZE)) {
205                 sz = HIGH_RESOLUTION_SIZE;
206             }
207             Log.i(TAG, "Set preview size to " + sz.toString());
208             previewHolder.setFixedSize(sz.getWidth(), sz.getHeight());
209             mPreviewSurface = previewHolder.getSurface();
210         }  catch (CameraAccessException e) {
211             throw new ApiFailureException("Error setting up minimal preview", e);
212         }
213     }
214 
215 
216     /**
217      * Update current preview with manual control inputs.
218      */
updatePreview(CameraControls manualCtrl)219     public void updatePreview(CameraControls manualCtrl) {
220         updateCaptureRequest(mPreviewRequestBuilder, manualCtrl);
221 
222         try {
223             // TODO: add capture result listener
224             mCamera.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
225         } catch (CameraAccessException e) {
226             Log.e(TAG, "Update camera preview failed");
227         }
228     }
229 
230     /**
231      * Configure streams and run minimal preview
232      */
minimalPreview(SurfaceHolder previewHolder)233     public void minimalPreview(SurfaceHolder previewHolder) throws ApiFailureException {
234 
235         minimalOpenCamera();
236 
237         if (mPreviewSurface == null) {
238             throw new ApiFailureException("Preview surface is not created");
239         }
240         try {
241             List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
242             outputSurfaces.add(mPreviewSurface);
243 
244             configureOutputs(outputSurfaces);
245 
246             CaptureRequest.Builder previewBuilder;
247             mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
248 
249             mPreviewRequestBuilder.addTarget(mPreviewSurface);
250 
251             mCamera.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
252         } catch (CameraAccessException e) {
253             throw new ApiFailureException("Error setting up minimal preview", e);
254         }
255     }
256 
minimalJpegCapture(final CaptureListener listener, CaptureResultListener l, Handler h, CameraControls cameraControl)257     public void minimalJpegCapture(final CaptureListener listener, CaptureResultListener l,
258             Handler h, CameraControls cameraControl) throws ApiFailureException {
259         minimalOpenCamera();
260 
261         try {
262             CameraCharacteristics properties =
263                     mCameraManager.getCameraCharacteristics(mCamera.getId());
264             Size[] jpegSizes = null;
265             if (properties != null) {
266                 jpegSizes = properties.get(
267                         CameraCharacteristics.SCALER_AVAILABLE_JPEG_SIZES);
268             }
269             int width = 640;
270             int height = 480;
271 
272             if (jpegSizes != null && jpegSizes.length > 0) {
273                 width = jpegSizes[0].getWidth();
274                 height = jpegSizes[0].getHeight();
275             }
276 
277             if (mCaptureReader == null || mCaptureReader.getWidth() != width ||
278                     mCaptureReader.getHeight() != height) {
279                 if (mCaptureReader != null) {
280                     mCaptureReader.close();
281                 }
282                 mCaptureReader = ImageReader.newInstance(width, height,
283                         ImageFormat.JPEG, MAX_CONCURRENT_JPEGS);
284             }
285 
286             List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
287             outputSurfaces.add(mCaptureReader.getSurface());
288 
289             configureOutputs(outputSurfaces);
290 
291             CaptureRequest.Builder captureBuilder =
292                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
293 
294             captureBuilder.addTarget(mCaptureReader.getSurface());
295 
296             updateCaptureRequest(captureBuilder, cameraControl);
297 
298             ImageReader.OnImageAvailableListener readerListener =
299                     new ImageReader.OnImageAvailableListener() {
300                 @Override
301                 public void onImageAvailable(ImageReader reader) {
302                     Image i = null;
303                     try {
304                         i = reader.acquireNextImage();
305                         listener.onCaptureAvailable(i);
306                     } finally {
307                         if (i != null) {
308                             i.close();
309                         }
310                     }
311                 }
312             };
313             mCaptureReader.setOnImageAvailableListener(readerListener, h);
314 
315             mCamera.capture(captureBuilder.build(), l, mOpsHandler);
316         } catch (CameraAccessException e) {
317             throw new ApiFailureException("Error in minimal JPEG capture", e);
318         }
319     }
320 
startRecording(boolean useMediaCodec)321     public void startRecording(boolean useMediaCodec) throws ApiFailureException {
322         minimalOpenCamera();
323         Size recordingSize = getRecordingSize();
324         try {
325             if (mRecordingRequestBuilder == null) {
326                 mRecordingRequestBuilder =
327                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
328             }
329             // Setup output stream first
330             mRecordingStream.configure(recordingSize, useMediaCodec, mEncodingBitRate);
331             mRecordingStream.onConfiguringOutputs(mOutputSurfaces, /* detach */false);
332             mRecordingStream.onConfiguringRequest(mRecordingRequestBuilder, /* detach */false);
333 
334             // TODO: For preview, create preview stream class, and do the same thing like recording.
335             mOutputSurfaces.add(mPreviewSurface);
336             mRecordingRequestBuilder.addTarget(mPreviewSurface);
337 
338             // Start camera streaming and recording.
339             configureOutputs(mOutputSurfaces);
340             mCamera.setRepeatingRequest(mRecordingRequestBuilder.build(), null, null);
341             mRecordingStream.start();
342         } catch (CameraAccessException e) {
343             throw new ApiFailureException("Error start recording", e);
344         }
345     }
346 
stopRecording()347     public void stopRecording() throws ApiFailureException {
348         try {
349             /**
350              * <p>
351              * Only stop camera recording stream.
352              * </p>
353              * <p>
354              * FIXME: There is a race condition to be fixed in CameraDevice.
355              * Basically, when stream closes, encoder and its surface is
356              * released, while it still takes some time for camera to finish the
357              * output to that surface. Then it cause camera in bad state.
358              * </p>
359              */
360             mRecordingStream.onConfiguringRequest(mRecordingRequestBuilder, /* detach */true);
361             mRecordingStream.onConfiguringOutputs(mOutputSurfaces, /* detach */true);
362 
363             // Remove recording surface before calling RecordingStream.stop,
364             // since that invalidates the surface.
365             configureOutputs(mOutputSurfaces);
366 
367             mRecordingStream.stop();
368 
369             mCamera.setRepeatingRequest(mRecordingRequestBuilder.build(), null, null);
370         } catch (CameraAccessException e) {
371             throw new ApiFailureException("Error stop recording", e);
372         }
373     }
374 
getRecordingSize()375     private Size getRecordingSize() throws ApiFailureException {
376         try {
377             CameraCharacteristics properties =
378                     mCameraManager.getCameraCharacteristics(mCamera.getId());
379 
380             Size[] recordingSizes = null;
381             if (properties != null) {
382                 recordingSizes = properties.get(
383                         CameraCharacteristics.SCALER_AVAILABLE_PROCESSED_SIZES);
384             }
385 
386             mEncodingBitRate = ENC_BIT_RATE_LOW;
387             if (recordingSizes == null || recordingSizes.length == 0) {
388                 Log.w(TAG, "Unable to get recording sizes, default to 640x480");
389                 return DEFAULT_SIZE;
390             } else {
391                 /**
392                  * TODO: create resolution selection widget on UI, then use the
393                  * select size. For now, return HIGH_RESOLUTION_SIZE if it
394                  * exists in the processed size list, otherwise return default
395                  * size
396                  */
397                 if (Arrays.asList(recordingSizes).contains(HIGH_RESOLUTION_SIZE)) {
398                     mEncodingBitRate = ENC_BIT_RATE_HIGH;
399                     return HIGH_RESOLUTION_SIZE;
400                 } else {
401                     // Fallback to default size when HD size is not found.
402                     Log.w(TAG,
403                             "Unable to find the requested size " + HIGH_RESOLUTION_SIZE.toString()
404                             + " Fallback to " + DEFAULT_SIZE.toString());
405                     return DEFAULT_SIZE;
406                 }
407             }
408         } catch (CameraAccessException e) {
409             throw new ApiFailureException("Error setting up video recording", e);
410         }
411     }
412 
updateCaptureRequest(CaptureRequest.Builder builder, CameraControls cameraControl)413     private void updateCaptureRequest(CaptureRequest.Builder builder, CameraControls cameraControl) {
414         if (cameraControl != null) {
415             // Update the manual control metadata for capture request
416             // Disable 3A routines.
417             if (cameraControl.isManualControlEnabled()) {
418                 Log.e(TAG, "update request: " + cameraControl.getSensitivity());
419                 builder.set(CaptureRequest.CONTROL_MODE,
420                         CameraMetadata.CONTROL_MODE_OFF);
421                 builder.set(CaptureRequest.SENSOR_SENSITIVITY,
422                         cameraControl.getSensitivity());
423                 builder.set(CaptureRequest.SENSOR_FRAME_DURATION,
424                         cameraControl.getFrameDuration());
425                 builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME,
426                         cameraControl.getExposure());
427             } else {
428                 builder.set(CaptureRequest.CONTROL_MODE,
429                         CameraMetadata.CONTROL_MODE_AUTO);
430             }
431         }
432     }
433 
434     public interface CaptureListener {
onCaptureAvailable(Image capture)435         void onCaptureAvailable(Image capture);
436     }
437 
438     public static abstract class CaptureResultListener extends CameraDevice.CaptureListener {}
439 }
440