LegacyCameraDevice.java revision 91838ded36131525312739c0929913b215519c2a
1/*
2 * Copyright (C) 2014 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
17package android.hardware.camera2.legacy;
18
19import android.graphics.ImageFormat;
20import android.graphics.SurfaceTexture;
21import android.hardware.Camera;
22import android.hardware.camera2.CameraCharacteristics;
23import android.hardware.camera2.CaptureRequest;
24import android.hardware.camera2.impl.CaptureResultExtras;
25import android.hardware.camera2.ICameraDeviceCallbacks;
26import android.hardware.camera2.utils.LongParcelable;
27import android.hardware.camera2.impl.CameraMetadataNative;
28import android.hardware.camera2.utils.CameraRuntimeException;
29import android.os.ConditionVariable;
30import android.os.Handler;
31import android.os.HandlerThread;
32import android.os.RemoteException;
33import android.util.Log;
34import android.util.Size;
35import android.view.Surface;
36
37import java.util.ArrayList;
38import java.util.Collection;
39import java.util.List;
40
41import static android.hardware.camera2.legacy.LegacyExceptionUtils.*;
42import static android.hardware.camera2.utils.CameraBinderDecorator.*;
43import static com.android.internal.util.Preconditions.*;
44
45/**
46 * This class emulates the functionality of a Camera2 device using a the old Camera class.
47 *
48 * <p>
49 * There are two main components that are used to implement this:
50 * - A state machine containing valid Camera2 device states ({@link CameraDeviceState}).
51 * - A message-queue based pipeline that manages an old Camera class, and executes capture and
52 *   configuration requests.
53 * </p>
54 */
55public class LegacyCameraDevice implements AutoCloseable {
56    public static final String DEBUG_PROP = "HAL1ShimLogging";
57    private final String TAG;
58
59    private static final boolean DEBUG = false;
60    private final int mCameraId;
61    private final ICameraDeviceCallbacks mDeviceCallbacks;
62    private final CameraDeviceState mDeviceState = new CameraDeviceState();
63    private List<Surface> mConfiguredSurfaces;
64
65    private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
66
67    private final HandlerThread mResultThread = new HandlerThread("ResultThread");
68    private final HandlerThread mCallbackHandlerThread = new HandlerThread("CallbackThread");
69    private final Handler mCallbackHandler;
70    private final Handler mResultHandler;
71    private static final int ILLEGAL_VALUE = -1;
72
73    private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
74        if (holder == null) {
75            return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
76                    ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE);
77        }
78        return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
79                /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(),
80                /*partialResultCount*/1);
81    }
82
83    /**
84     * Listener for the camera device state machine.  Calls the appropriate
85     * {@link ICameraDeviceCallbacks} for each state transition.
86     */
87    private final CameraDeviceState.CameraDeviceStateListener mStateListener =
88            new CameraDeviceState.CameraDeviceStateListener() {
89        @Override
90        public void onError(final int errorCode, RequestHolder holder) {
91            mIdle.open();
92            final CaptureResultExtras extras = getExtrasFromRequest(holder);
93            mResultHandler.post(new Runnable() {
94                @Override
95                public void run() {
96                    if (DEBUG) {
97                        Log.d(TAG, "doing onError callback.");
98                    }
99                    try {
100                        mDeviceCallbacks.onCameraError(errorCode, extras);
101                    } catch (RemoteException e) {
102                        throw new IllegalStateException(
103                                "Received remote exception during onCameraError callback: ", e);
104                    }
105                }
106            });
107        }
108
109        @Override
110        public void onConfiguring() {
111            // Do nothing
112            if (DEBUG) {
113                Log.d(TAG, "doing onConfiguring callback.");
114            }
115        }
116
117        @Override
118        public void onIdle() {
119            mIdle.open();
120
121            mResultHandler.post(new Runnable() {
122                @Override
123                public void run() {
124                    if (DEBUG) {
125                        Log.d(TAG, "doing onIdle callback.");
126                    }
127                    try {
128                        mDeviceCallbacks.onCameraIdle();
129                    } catch (RemoteException e) {
130                        throw new IllegalStateException(
131                                "Received remote exception during onCameraIdle callback: ", e);
132                    }
133                }
134            });
135        }
136
137        @Override
138        public void onCaptureStarted(RequestHolder holder, final long timestamp) {
139            final CaptureResultExtras extras = getExtrasFromRequest(holder);
140
141            mResultHandler.post(new Runnable() {
142                @Override
143                public void run() {
144                    if (DEBUG) {
145                        Log.d(TAG, "doing onCaptureStarted callback.");
146                    }
147                    try {
148                        mDeviceCallbacks.onCaptureStarted(extras, timestamp);
149                    } catch (RemoteException e) {
150                        throw new IllegalStateException(
151                                "Received remote exception during onCameraError callback: ", e);
152                    }
153                }
154            });
155        }
156
157        @Override
158        public void onCaptureResult(final CameraMetadataNative result, RequestHolder holder) {
159            final CaptureResultExtras extras = getExtrasFromRequest(holder);
160
161            mResultHandler.post(new Runnable() {
162                @Override
163                public void run() {
164                    if (DEBUG) {
165                        Log.d(TAG, "doing onCaptureResult callback.");
166                    }
167                    try {
168                        mDeviceCallbacks.onResultReceived(result, extras);
169                    } catch (RemoteException e) {
170                        throw new IllegalStateException(
171                                "Received remote exception during onCameraError callback: ", e);
172                    }
173                }
174            });
175        }
176    };
177
178    private final RequestThreadManager mRequestThreadManager;
179
180    /**
181     * Check if a given surface uses {@link ImageFormat#YUV_420_888} or format that can be readily
182     * converted to this; YV12 and NV21 are the two currently supported formats.
183     *
184     * @param s the surface to check.
185     * @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888} or a compatible
186     *          format.
187     */
188    static boolean needsConversion(Surface s) throws BufferQueueAbandonedException {
189        int nativeType = detectSurfaceType(s);
190        return nativeType == ImageFormat.YUV_420_888 || nativeType == ImageFormat.YV12 ||
191                nativeType == ImageFormat.NV21;
192    }
193
194    /**
195     * Create a new emulated camera device from a given Camera 1 API camera.
196     *
197     * <p>
198     * The {@link Camera} provided to this constructor must already have been successfully opened,
199     * and ownership of the provided camera is passed to this object.  No further calls to the
200     * camera methods should be made following this constructor.
201     * </p>
202     *
203     * @param cameraId the id of the camera.
204     * @param camera an open {@link Camera} device.
205     * @param characteristics the static camera characteristics for this camera device
206     * @param callbacks {@link ICameraDeviceCallbacks} callbacks to call for Camera2 API operations.
207     */
208    public LegacyCameraDevice(int cameraId, Camera camera, CameraCharacteristics characteristics,
209            ICameraDeviceCallbacks callbacks) {
210        mCameraId = cameraId;
211        mDeviceCallbacks = callbacks;
212        TAG = String.format("CameraDevice-%d-LE", mCameraId);
213
214        mResultThread.start();
215        mResultHandler = new Handler(mResultThread.getLooper());
216        mCallbackHandlerThread.start();
217        mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper());
218        mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener);
219        mRequestThreadManager =
220                new RequestThreadManager(cameraId, camera, characteristics, mDeviceState);
221        mRequestThreadManager.start();
222    }
223
224    /**
225     * Configure the device with a set of output surfaces.
226     *
227     * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
228     *
229     * <p>Every surface in {@code outputs} must be non-{@code null}.</p>
230     *
231     * @param outputs a list of surfaces to set.
232     * @return an error code for this binder operation, or {@link NO_ERROR}
233     *          on success.
234     */
235    public int configureOutputs(List<Surface> outputs) {
236        if (outputs != null) {
237            for (Surface output : outputs) {
238                if (output == null) {
239                    Log.e(TAG, "configureOutputs - null outputs are not allowed");
240                    return BAD_VALUE;
241                }
242            }
243        }
244
245        int error = mDeviceState.setConfiguring();
246        if (error == NO_ERROR) {
247            mRequestThreadManager.configure(outputs);
248            error = mDeviceState.setIdle();
249        }
250
251        // TODO: May also want to check the surfaces more deeply (e.g. state, formats, sizes..)
252        if (error == NO_ERROR) {
253            mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null;
254        }
255
256        return error;
257    }
258
259    /**
260     * Submit a burst of capture requests.
261     *
262     * @param requestList a list of capture requests to execute.
263     * @param repeating {@code true} if this burst is repeating.
264     * @param frameNumber an output argument that contains either the frame number of the last frame
265     *                    that will be returned for this request, or the frame number of the last
266     *                    frame that will be returned for the current repeating request if this
267     *                    burst is set to be repeating.
268     * @return the request id.
269     */
270    public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
271            /*out*/LongParcelable frameNumber) {
272        if (requestList == null || requestList.isEmpty()) {
273            Log.e(TAG, "submitRequestList - Empty/null requests are not allowed");
274            return BAD_VALUE;
275        }
276
277        List<Long> surfaceIds = (mConfiguredSurfaces == null) ? new ArrayList<Long>() :
278                getSurfaceIds(mConfiguredSurfaces);
279
280        // Make sure that there all requests have at least 1 surface; all surfaces are non-null
281        for (CaptureRequest request : requestList) {
282            if (request.getTargets().isEmpty()) {
283                Log.e(TAG, "submitRequestList - "
284                        + "Each request must have at least one Surface target");
285                return BAD_VALUE;
286            }
287
288            for (Surface surface : request.getTargets()) {
289                if (surface == null) {
290                    Log.e(TAG, "submitRequestList - Null Surface targets are not allowed");
291                    return BAD_VALUE;
292                } else if (mConfiguredSurfaces == null) {
293                    Log.e(TAG, "submitRequestList - must configure " +
294                            " device with valid surfaces before submitting requests");
295                    return INVALID_OPERATION;
296                } else if (!containsSurfaceId(surface, surfaceIds)) {
297                    Log.e(TAG, "submitRequestList - cannot use a surface that wasn't configured");
298                    return BAD_VALUE;
299                }
300            }
301        }
302
303        // TODO: further validation of request here
304        mIdle.close();
305        return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
306                frameNumber);
307    }
308
309    /**
310     * Submit a single capture request.
311     *
312     * @param request the capture request to execute.
313     * @param repeating {@code true} if this request is repeating.
314     * @param frameNumber an output argument that contains either the frame number of the last frame
315     *                    that will be returned for this request, or the frame number of the last
316     *                    frame that will be returned for the current repeating request if this
317     *                    request is set to be repeating.
318     * @return the request id.
319     */
320    public int submitRequest(CaptureRequest request, boolean repeating,
321            /*out*/LongParcelable frameNumber) {
322        ArrayList<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
323        requestList.add(request);
324        return submitRequestList(requestList, repeating, frameNumber);
325    }
326
327    /**
328     * Cancel the repeating request with the given request id.
329     *
330     * @param requestId the request id of the request to cancel.
331     * @return the last frame number to be returned from the HAL for the given repeating request, or
332     *          {@code INVALID_FRAME} if none exists.
333     */
334    public long cancelRequest(int requestId) {
335        return mRequestThreadManager.cancelRepeating(requestId);
336    }
337
338    /**
339     * Block until the {@link ICameraDeviceCallbacks#onCameraIdle()} callback is received.
340     */
341    public void waitUntilIdle()  {
342        mIdle.block();
343    }
344
345    @Override
346    public void close() {
347        mRequestThreadManager.quit();
348        mCallbackHandlerThread.quitSafely();
349        mResultThread.quitSafely();
350
351        try {
352            mCallbackHandlerThread.join();
353        } catch (InterruptedException e) {
354            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
355                    mCallbackHandlerThread.getName(), mCallbackHandlerThread.getId()));
356        }
357
358        try {
359            mResultThread.join();
360        } catch (InterruptedException e) {
361            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
362                    mResultThread.getName(), mResultThread.getId()));
363        }
364
365        // TODO: throw IllegalStateException in every method after close has been called
366    }
367
368    @Override
369    protected void finalize() throws Throwable {
370        try {
371            close();
372        } catch (CameraRuntimeException e) {
373            Log.e(TAG, "Got error while trying to finalize, ignoring: " + e.getMessage());
374        } finally {
375            super.finalize();
376        }
377    }
378
379    /**
380     * Query the surface for its currently configured default buffer size.
381     * @param surface a non-{@code null} {@code Surface}
382     * @return the width and height of the surface
383     *
384     * @throws NullPointerException if the {@code surface} was {@code null}
385     * @throws IllegalStateException if the {@code surface} was invalid
386     */
387    static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException {
388        checkNotNull(surface);
389
390        int[] dimens = new int[2];
391        LegacyExceptionUtils.throwOnError(nativeDetectSurfaceDimens(surface, /*out*/dimens));
392
393        return new Size(dimens[0], dimens[1]);
394    }
395
396    static int detectSurfaceType(Surface surface) throws BufferQueueAbandonedException {
397        checkNotNull(surface);
398        return LegacyExceptionUtils.throwOnError(nativeDetectSurfaceType(surface));
399    }
400
401    static void configureSurface(Surface surface, int width, int height,
402                                 int pixelFormat) throws BufferQueueAbandonedException {
403        checkNotNull(surface);
404        checkArgumentPositive(width, "width must be positive.");
405        checkArgumentPositive(height, "height must be positive.");
406
407        LegacyExceptionUtils.throwOnError(nativeConfigureSurface(surface, width, height,
408                pixelFormat));
409    }
410
411    static void produceFrame(Surface surface, byte[] pixelBuffer, int width,
412                             int height, int pixelFormat)
413            throws BufferQueueAbandonedException {
414        checkNotNull(surface);
415        checkNotNull(pixelBuffer);
416        checkArgumentPositive(width, "width must be positive.");
417        checkArgumentPositive(height, "height must be positive.");
418
419        LegacyExceptionUtils.throwOnError(nativeProduceFrame(surface, pixelBuffer, width, height,
420                pixelFormat));
421    }
422
423    static void setSurfaceFormat(Surface surface, int pixelFormat)
424            throws BufferQueueAbandonedException {
425        checkNotNull(surface);
426
427        LegacyExceptionUtils.throwOnError(nativeSetSurfaceFormat(surface, pixelFormat));
428    }
429
430    static void setSurfaceDimens(Surface surface, int width, int height)
431            throws BufferQueueAbandonedException {
432        checkNotNull(surface);
433        checkArgumentPositive(width, "width must be positive.");
434        checkArgumentPositive(height, "height must be positive.");
435
436        LegacyExceptionUtils.throwOnError(nativeSetSurfaceDimens(surface, width, height));
437    }
438
439    static long getSurfaceId(Surface surface) {
440        checkNotNull(surface);
441        return nativeGetSurfaceId(surface);
442    }
443
444    static List<Long> getSurfaceIds(Collection<Surface> surfaces) {
445        if (surfaces == null) {
446            throw new NullPointerException("Null argument surfaces");
447        }
448        List<Long> surfaceIds = new ArrayList<>();
449        for (Surface s : surfaces) {
450            long id = getSurfaceId(s);
451            if (id == 0) {
452                throw new IllegalStateException(
453                        "Configured surface had null native GraphicBufferProducer pointer!");
454            }
455            surfaceIds.add(id);
456        }
457        return surfaceIds;
458    }
459
460    static boolean containsSurfaceId(Surface s, List<Long> ids) {
461        long id = getSurfaceId(s);
462        return ids.contains(id);
463    }
464
465    static void setSurfaceOrientation(Surface surface, int facing, int sensorOrientation)
466            throws BufferQueueAbandonedException {
467        checkNotNull(surface);
468        LegacyExceptionUtils.throwOnError(nativeSetSurfaceOrientation(surface, facing,
469                sensorOrientation));
470    }
471
472    static Size getTextureSize(SurfaceTexture surfaceTexture)
473            throws BufferQueueAbandonedException {
474        checkNotNull(surfaceTexture);
475
476        int[] dimens = new int[2];
477        LegacyExceptionUtils.throwOnError(nativeDetectTextureDimens(surfaceTexture,
478                /*out*/dimens));
479
480        return new Size(dimens[0], dimens[1]);
481    }
482
483    static void setNextTimestamp(Surface surface, long timestamp)
484            throws BufferQueueAbandonedException {
485        checkNotNull(surface);
486        LegacyExceptionUtils.throwOnError(nativeSetNextTimestamp(surface, timestamp));
487    }
488
489    private static native int nativeDetectSurfaceType(Surface surface);
490
491    private static native int nativeDetectSurfaceDimens(Surface surface,
492            /*out*/int[/*2*/] dimens);
493
494    private static native int nativeConfigureSurface(Surface surface, int width, int height,
495                                                        int pixelFormat);
496
497    private static native int nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
498                                                    int height, int pixelFormat);
499
500    private static native int nativeSetSurfaceFormat(Surface surface, int pixelFormat);
501
502    private static native int nativeSetSurfaceDimens(Surface surface, int width, int height);
503
504    private static native long nativeGetSurfaceId(Surface surface);
505
506    private static native int nativeSetSurfaceOrientation(Surface surface, int facing,
507                                                             int sensorOrientation);
508
509    private static native int nativeDetectTextureDimens(SurfaceTexture surfaceTexture,
510            /*out*/int[/*2*/] dimens);
511
512    private static native int nativeSetNextTimestamp(Surface surface, long timestamp);
513}
514