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