LegacyCameraDevice.java revision 3e4fed203fe7c945c53c6d6bb9f160932a1d15b3
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.CameraBinderDecorator;
27import android.hardware.camera2.utils.CameraRuntimeException;
28import android.os.ConditionVariable;
29import android.os.Handler;
30import android.os.HandlerThread;
31import android.os.RemoteException;
32import android.util.Log;
33import android.view.Surface;
34
35import java.util.ArrayList;
36import java.util.List;
37import java.util.concurrent.atomic.AtomicInteger;
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
58    private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
59
60    private final HandlerThread mResultThread = new HandlerThread("ResultThread");
61    private final HandlerThread mCallbackHandlerThread = new HandlerThread("CallbackThread");
62    private final Handler mCallbackHandler;
63    private final Handler mResultHandler;
64    private static final int ILLEGAL_VALUE = -1;
65
66    private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
67        if (holder == null) {
68            return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
69                    ILLEGAL_VALUE, ILLEGAL_VALUE);
70        }
71        return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
72                /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber());
73    }
74
75    /**
76     * Listener for the camera device state machine.  Calls the appropriate
77     * {@link ICameraDeviceCallbacks} for each state transition.
78     */
79    private final CameraDeviceState.CameraDeviceStateListener mStateListener =
80            new CameraDeviceState.CameraDeviceStateListener() {
81        @Override
82        public void onError(final int errorCode, RequestHolder holder) {
83            mIdle.open();
84            final CaptureResultExtras extras = getExtrasFromRequest(holder);
85            mResultHandler.post(new Runnable() {
86                @Override
87                public void run() {
88                    if (DEBUG) {
89                        Log.d(TAG, "doing onError callback.");
90                    }
91                    try {
92                        mDeviceCallbacks.onCameraError(errorCode, extras);
93                    } catch (RemoteException e) {
94                        throw new IllegalStateException(
95                                "Received remote exception during onCameraError callback: ", e);
96                    }
97                }
98            });
99        }
100
101        @Override
102        public void onConfiguring() {
103            // Do nothing
104            if (DEBUG) {
105                Log.d(TAG, "doing onConfiguring callback.");
106            }
107        }
108
109        @Override
110        public void onIdle() {
111            mIdle.open();
112
113            mResultHandler.post(new Runnable() {
114                @Override
115                public void run() {
116                    if (DEBUG) {
117                        Log.d(TAG, "doing onIdle callback.");
118                    }
119                    try {
120                        mDeviceCallbacks.onCameraIdle();
121                    } catch (RemoteException e) {
122                        throw new IllegalStateException(
123                                "Received remote exception during onCameraIdle callback: ", e);
124                    }
125                }
126            });
127        }
128
129        @Override
130        public void onCaptureStarted(RequestHolder holder) {
131            final CaptureResultExtras extras = getExtrasFromRequest(holder);
132
133            final long timestamp = System.nanoTime();
134            mResultHandler.post(new Runnable() {
135                @Override
136                public void run() {
137                    if (DEBUG) {
138                        Log.d(TAG, "doing onCaptureStarted callback.");
139                    }
140                    try {
141                        // TODO: Don't fake timestamp
142                        mDeviceCallbacks.onCaptureStarted(extras, timestamp);
143                    } catch (RemoteException e) {
144                        throw new IllegalStateException(
145                                "Received remote exception during onCameraError callback: ", e);
146                    }
147                }
148            });
149        }
150
151        @Override
152        public void onCaptureResult(final CameraMetadataNative result, RequestHolder holder) {
153            final CaptureResultExtras extras = getExtrasFromRequest(holder);
154
155            mResultHandler.post(new Runnable() {
156                @Override
157                public void run() {
158                    if (DEBUG) {
159                        Log.d(TAG, "doing onCaptureResult callback.");
160                    }
161                    try {
162                        // TODO: Don't fake metadata
163                        mDeviceCallbacks.onResultReceived(result, extras);
164                    } catch (RemoteException e) {
165                        throw new IllegalStateException(
166                                "Received remote exception during onCameraError callback: ", e);
167                    }
168                }
169            });
170        }
171    };
172
173    private final RequestThreadManager mRequestThreadManager;
174
175    /**
176     * Check if a given surface uses {@link ImageFormat#YUV_420_888} format.
177     *
178     * @param s the surface to check.
179     * @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888}.
180     */
181    static boolean needsConversion(Surface s) {
182        return LegacyCameraDevice.nativeDetectSurfaceType(s) == ImageFormat.YUV_420_888;
183    }
184
185    /**
186     * Create a new emulated camera device from a given Camera 1 API camera.
187     *
188     * <p>
189     * The {@link Camera} provided to this constructor must already have been successfully opened,
190     * and ownership of the provided camera is passed to this object.  No further calls to the
191     * camera methods should be made following this constructor.
192     * </p>
193     *
194     * @param cameraId the id of the camera.
195     * @param camera an open {@link Camera} device.
196     * @param callbacks {@link ICameraDeviceCallbacks} callbacks to call for Camera2 API operations.
197     */
198    public LegacyCameraDevice(int cameraId, Camera camera, ICameraDeviceCallbacks callbacks) {
199        mCameraId = cameraId;
200        mDeviceCallbacks = callbacks;
201        TAG = String.format("CameraDevice-%d-LE", mCameraId);
202
203        mResultThread.start();
204        mResultHandler = new Handler(mResultThread.getLooper());
205        mCallbackHandlerThread.start();
206        mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper());
207        mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener);
208        mRequestThreadManager =
209                new RequestThreadManager(cameraId, camera, mDeviceState);
210        mRequestThreadManager.start();
211    }
212
213    /**
214     * Configure the device with a set of output surfaces.
215     *
216     * @param outputs a list of surfaces to set.
217     * @return an error code for this binder operation, or {@link CameraBinderDecorator.NO_ERROR}
218     *          on success.
219     */
220    public int configureOutputs(List<Surface> outputs) {
221        int error = mDeviceState.setConfiguring();
222        if (error == CameraBinderDecorator.NO_ERROR) {
223            mRequestThreadManager.configure(outputs);
224            error = mDeviceState.setIdle();
225        }
226        return error;
227    }
228
229    /**
230     * Submit a burst of capture requests.
231     *
232     * @param requestList a list of capture requests to execute.
233     * @param repeating {@code true} if this burst is repeating.
234     * @param frameNumber an output argument that contains either the frame number of the last frame
235     *                    that will be returned for this request, or the frame number of the last
236     *                    frame that will be returned for the current repeating request if this
237     *                    burst is set to be repeating.
238     * @return the request id.
239     */
240    public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
241            /*out*/LongParcelable frameNumber) {
242        // TODO: validate request here
243        mIdle.close();
244        return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
245                frameNumber);
246    }
247
248    /**
249     * Submit a single capture request.
250     *
251     * @param request the capture request to execute.
252     * @param repeating {@code true} if this request is repeating.
253     * @param frameNumber an output argument that contains either the frame number of the last frame
254     *                    that will be returned for this request, or the frame number of the last
255     *                    frame that will be returned for the current repeating request if this
256     *                    request is set to be repeating.
257     * @return the request id.
258     */
259    public int submitRequest(CaptureRequest request, boolean repeating,
260            /*out*/LongParcelable frameNumber) {
261        ArrayList<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
262        requestList.add(request);
263        return submitRequestList(requestList, repeating, frameNumber);
264    }
265
266    /**
267     * Cancel the repeating request with the given request id.
268     *
269     * @param requestId the request id of the request to cancel.
270     * @return the last frame number to be returned from the HAL for the given repeating request, or
271     *          {@code INVALID_FRAME} if none exists.
272     */
273    public long cancelRequest(int requestId) {
274        return mRequestThreadManager.cancelRepeating(requestId);
275    }
276
277    /**
278     * Block until the {@link ICameraDeviceCallbacks#onCameraIdle()} callback is received.
279     */
280    public void waitUntilIdle()  {
281        mIdle.block();
282    }
283
284    @Override
285    public void close() {
286        mRequestThreadManager.quit();
287        mCallbackHandlerThread.quitSafely();
288        mResultThread.quitSafely();
289
290        try {
291            mCallbackHandlerThread.join();
292        } catch (InterruptedException e) {
293            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
294                    mCallbackHandlerThread.getName(), mCallbackHandlerThread.getId()));
295        }
296
297        try {
298            mResultThread.join();
299        } catch (InterruptedException e) {
300            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
301                    mResultThread.getName(), mResultThread.getId()));
302        }
303
304        // TODO: throw IllegalStateException in every method after close has been called
305    }
306
307    @Override
308    protected void finalize() throws Throwable {
309        try {
310            close();
311        } catch (CameraRuntimeException e) {
312            Log.e(TAG, "Got error while trying to finalize, ignoring: " + e.getMessage());
313        } finally {
314            super.finalize();
315        }
316    }
317
318    protected static native int nativeDetectSurfaceType(Surface surface);
319
320    protected static native void nativeDetectSurfaceDimens(Surface surface, int[] dimens);
321
322    protected static native void nativeConfigureSurface(Surface surface, int width, int height,
323                                                        int pixelFormat);
324
325    protected static native void nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
326                                                    int height, int pixelFormat);
327
328    protected static native void nativeSetSurfaceFormat(Surface surface, int pixelFormat);
329
330    protected static native void nativeSetSurfaceDimens(Surface surface, int width, int height);
331
332}
333