LegacyCameraDevice.java revision 91b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0
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.view.Surface;
33
34import java.util.ArrayList;
35import java.util.List;
36
37import static android.hardware.camera2.utils.CameraBinderDecorator.*;
38
39/**
40 * This class emulates the functionality of a Camera2 device using a the old Camera class.
41 *
42 * <p>
43 * There are two main components that are used to implement this:
44 * - A state machine containing valid Camera2 device states ({@link CameraDeviceState}).
45 * - A message-queue based pipeline that manages an old Camera class, and executes capture and
46 *   configuration requests.
47 * </p>
48 */
49public class LegacyCameraDevice implements AutoCloseable {
50    public static final String DEBUG_PROP = "HAL1ShimLogging";
51    private final String TAG;
52
53    private static final boolean DEBUG = false;
54    private final int mCameraId;
55    private final ICameraDeviceCallbacks mDeviceCallbacks;
56    private final CameraDeviceState mDeviceState = new CameraDeviceState();
57    private List<Surface> mConfiguredSurfaces;
58
59    private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
60
61    private final HandlerThread mResultThread = new HandlerThread("ResultThread");
62    private final HandlerThread mCallbackHandlerThread = new HandlerThread("CallbackThread");
63    private final Handler mCallbackHandler;
64    private final Handler mResultHandler;
65    private static final int ILLEGAL_VALUE = -1;
66
67    private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
68        if (holder == null) {
69            return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
70                    ILLEGAL_VALUE, ILLEGAL_VALUE);
71        }
72        return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
73                /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber());
74    }
75
76    /**
77     * Listener for the camera device state machine.  Calls the appropriate
78     * {@link ICameraDeviceCallbacks} for each state transition.
79     */
80    private final CameraDeviceState.CameraDeviceStateListener mStateListener =
81            new CameraDeviceState.CameraDeviceStateListener() {
82        @Override
83        public void onError(final int errorCode, RequestHolder holder) {
84            mIdle.open();
85            final CaptureResultExtras extras = getExtrasFromRequest(holder);
86            mResultHandler.post(new Runnable() {
87                @Override
88                public void run() {
89                    if (DEBUG) {
90                        Log.d(TAG, "doing onError callback.");
91                    }
92                    try {
93                        mDeviceCallbacks.onCameraError(errorCode, extras);
94                    } catch (RemoteException e) {
95                        throw new IllegalStateException(
96                                "Received remote exception during onCameraError callback: ", e);
97                    }
98                }
99            });
100        }
101
102        @Override
103        public void onConfiguring() {
104            // Do nothing
105            if (DEBUG) {
106                Log.d(TAG, "doing onConfiguring callback.");
107            }
108        }
109
110        @Override
111        public void onIdle() {
112            mIdle.open();
113
114            mResultHandler.post(new Runnable() {
115                @Override
116                public void run() {
117                    if (DEBUG) {
118                        Log.d(TAG, "doing onIdle callback.");
119                    }
120                    try {
121                        mDeviceCallbacks.onCameraIdle();
122                    } catch (RemoteException e) {
123                        throw new IllegalStateException(
124                                "Received remote exception during onCameraIdle callback: ", e);
125                    }
126                }
127            });
128        }
129
130        @Override
131        public void onCaptureStarted(RequestHolder holder) {
132            final CaptureResultExtras extras = getExtrasFromRequest(holder);
133
134            final long timestamp = System.nanoTime();
135            mResultHandler.post(new Runnable() {
136                @Override
137                public void run() {
138                    if (DEBUG) {
139                        Log.d(TAG, "doing onCaptureStarted callback.");
140                    }
141                    try {
142                        // TODO: Don't fake timestamp
143                        mDeviceCallbacks.onCaptureStarted(extras, timestamp);
144                    } catch (RemoteException e) {
145                        throw new IllegalStateException(
146                                "Received remote exception during onCameraError callback: ", e);
147                    }
148                }
149            });
150        }
151
152        @Override
153        public void onCaptureResult(final CameraMetadataNative result, RequestHolder holder) {
154            final CaptureResultExtras extras = getExtrasFromRequest(holder);
155
156            mResultHandler.post(new Runnable() {
157                @Override
158                public void run() {
159                    if (DEBUG) {
160                        Log.d(TAG, "doing onCaptureResult callback.");
161                    }
162                    try {
163                        // TODO: Don't fake metadata
164                        mDeviceCallbacks.onResultReceived(result, extras);
165                    } catch (RemoteException e) {
166                        throw new IllegalStateException(
167                                "Received remote exception during onCameraError callback: ", e);
168                    }
169                }
170            });
171        }
172    };
173
174    private final RequestThreadManager mRequestThreadManager;
175
176    /**
177     * Check if a given surface uses {@link ImageFormat#YUV_420_888} or format that can be readily
178     * converted to this; YV12 and NV21 are the two currently supported formats.
179     *
180     * @param s the surface to check.
181     * @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888} or a compatible
182     *          format.
183     */
184    static boolean needsConversion(Surface s) {
185        int nativeType = LegacyCameraDevice.nativeDetectSurfaceType(s);
186        return nativeType == ImageFormat.YUV_420_888 || nativeType == ImageFormat.YV12 ||
187                nativeType == ImageFormat.NV21;
188    }
189
190    /**
191     * Create a new emulated camera device from a given Camera 1 API camera.
192     *
193     * <p>
194     * The {@link Camera} provided to this constructor must already have been successfully opened,
195     * and ownership of the provided camera is passed to this object.  No further calls to the
196     * camera methods should be made following this constructor.
197     * </p>
198     *
199     * @param cameraId the id of the camera.
200     * @param camera an open {@link Camera} device.
201     * @param callbacks {@link ICameraDeviceCallbacks} callbacks to call for Camera2 API operations.
202     */
203    public LegacyCameraDevice(int cameraId, Camera camera, ICameraDeviceCallbacks callbacks) {
204        mCameraId = cameraId;
205        mDeviceCallbacks = callbacks;
206        TAG = String.format("CameraDevice-%d-LE", mCameraId);
207
208        mResultThread.start();
209        mResultHandler = new Handler(mResultThread.getLooper());
210        mCallbackHandlerThread.start();
211        mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper());
212        mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener);
213        mRequestThreadManager =
214                new RequestThreadManager(cameraId, camera, mDeviceState);
215        mRequestThreadManager.start();
216    }
217
218    /**
219     * Configure the device with a set of output surfaces.
220     *
221     * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
222     *
223     * <p>Every surface in {@code outputs} must be non-{@code null}.</p>
224     *
225     * @param outputs a list of surfaces to set.
226     * @return an error code for this binder operation, or {@link NO_ERROR}
227     *          on success.
228     */
229    public int configureOutputs(List<Surface> outputs) {
230        if (outputs != null) {
231            for (Surface output : outputs) {
232                if (output == null) {
233                    Log.e(TAG, "configureOutputs - null outputs are not allowed");
234                    return BAD_VALUE;
235                }
236            }
237        }
238
239        int error = mDeviceState.setConfiguring();
240        if (error == NO_ERROR) {
241            mRequestThreadManager.configure(outputs);
242            error = mDeviceState.setIdle();
243        }
244
245        // TODO: May also want to check the surfaces more deeply (e.g. state, formats, sizes..)
246        if (error == NO_ERROR) {
247            mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null;
248        }
249
250        return error;
251    }
252
253    /**
254     * Submit a burst of capture requests.
255     *
256     * @param requestList a list of capture requests to execute.
257     * @param repeating {@code true} if this burst is repeating.
258     * @param frameNumber an output argument that contains either the frame number of the last frame
259     *                    that will be returned for this request, or the frame number of the last
260     *                    frame that will be returned for the current repeating request if this
261     *                    burst is set to be repeating.
262     * @return the request id.
263     */
264    public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
265            /*out*/LongParcelable frameNumber) {
266        if (requestList == null || requestList.isEmpty()) {
267            Log.e(TAG, "submitRequestList - Empty/null requests are not allowed");
268            return BAD_VALUE;
269        }
270
271        // Make sure that there all requests have at least 1 surface; all surfaces are non-null
272        for (CaptureRequest request : requestList) {
273            if (request.getTargets().isEmpty()) {
274                Log.e(TAG, "submitRequestList - "
275                        + "Each request must have at least one Surface target");
276                return BAD_VALUE;
277            }
278
279            for (Surface surface : request.getTargets()) {
280                if (surface == null) {
281                    Log.e(TAG, "submitRequestList - Null Surface targets are not allowed");
282                    return BAD_VALUE;
283                } else if (mConfiguredSurfaces == null) {
284                    Log.e(TAG, "submitRequestList - must configure " +
285                            " device with valid surfaces before submitting requests");
286                    return INVALID_OPERATION;
287                } else if (!mConfiguredSurfaces.contains(surface)) {
288                    Log.e(TAG, "submitRequestList - cannot use a surface that wasn't configured");
289                    return BAD_VALUE;
290                }
291            }
292        }
293
294        // TODO: further validation of request here
295        mIdle.close();
296        return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
297                frameNumber);
298    }
299
300    /**
301     * Submit a single capture request.
302     *
303     * @param request the capture request to execute.
304     * @param repeating {@code true} if this request is repeating.
305     * @param frameNumber an output argument that contains either the frame number of the last frame
306     *                    that will be returned for this request, or the frame number of the last
307     *                    frame that will be returned for the current repeating request if this
308     *                    request is set to be repeating.
309     * @return the request id.
310     */
311    public int submitRequest(CaptureRequest request, boolean repeating,
312            /*out*/LongParcelable frameNumber) {
313        ArrayList<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
314        requestList.add(request);
315        return submitRequestList(requestList, repeating, frameNumber);
316    }
317
318    /**
319     * Cancel the repeating request with the given request id.
320     *
321     * @param requestId the request id of the request to cancel.
322     * @return the last frame number to be returned from the HAL for the given repeating request, or
323     *          {@code INVALID_FRAME} if none exists.
324     */
325    public long cancelRequest(int requestId) {
326        return mRequestThreadManager.cancelRepeating(requestId);
327    }
328
329    /**
330     * Block until the {@link ICameraDeviceCallbacks#onCameraIdle()} callback is received.
331     */
332    public void waitUntilIdle()  {
333        mIdle.block();
334    }
335
336    @Override
337    public void close() {
338        mRequestThreadManager.quit();
339        mCallbackHandlerThread.quitSafely();
340        mResultThread.quitSafely();
341
342        try {
343            mCallbackHandlerThread.join();
344        } catch (InterruptedException e) {
345            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
346                    mCallbackHandlerThread.getName(), mCallbackHandlerThread.getId()));
347        }
348
349        try {
350            mResultThread.join();
351        } catch (InterruptedException e) {
352            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
353                    mResultThread.getName(), mResultThread.getId()));
354        }
355
356        // TODO: throw IllegalStateException in every method after close has been called
357    }
358
359    @Override
360    protected void finalize() throws Throwable {
361        try {
362            close();
363        } catch (CameraRuntimeException e) {
364            Log.e(TAG, "Got error while trying to finalize, ignoring: " + e.getMessage());
365        } finally {
366            super.finalize();
367        }
368    }
369
370    protected static native int nativeDetectSurfaceType(Surface surface);
371
372    protected static native void nativeDetectSurfaceDimens(Surface surface, int[] dimens);
373
374    protected static native void nativeConfigureSurface(Surface surface, int width, int height,
375                                                        int pixelFormat);
376
377    protected static native void nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
378                                                    int height, int pixelFormat);
379
380    protected static native void nativeSetSurfaceFormat(Surface surface, int pixelFormat);
381
382    protected static native void nativeSetSurfaceDimens(Surface surface, int width, int height);
383
384}
385