blob: c34a34de021ed0340ca24032eea3172a55186aab [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.camera2.legacy;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.util.Log;
import android.view.Surface;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* This class emulates the functionality of a Camera2 device using a the old Camera class.
*
* <p>
* There are two main components that are used to implement this:
* - A state machine containing valid Camera2 device states ({@link CameraDeviceState}).
* - A message-queue based pipeline that manages an old Camera class, and executes capture and
* configuration requests.
* </p>
*/
public class LegacyCameraDevice implements AutoCloseable {
public static final String DEBUG_PROP = "HAL1ShimLogging";
private final String TAG;
private final int mCameraId;
private final ICameraDeviceCallbacks mDeviceCallbacks;
private final CameraDeviceState mDeviceState = new CameraDeviceState();
private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
private final HandlerThread mResultThread = new HandlerThread("ResultThread");
private final HandlerThread mCallbackHandlerThread = new HandlerThread("CallbackThread");
private final Handler mCallbackHandler;
private final Handler mResultHandler;
private static final int ILLEGAL_VALUE = -1;
private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
if (holder == null) {
return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
ILLEGAL_VALUE, ILLEGAL_VALUE);
}
return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
/*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber());
}
/**
* Listener for the camera device state machine. Calls the appropriate
* {@link ICameraDeviceCallbacks} for each state transition.
*/
private final CameraDeviceState.CameraDeviceStateListener mStateListener =
new CameraDeviceState.CameraDeviceStateListener() {
@Override
public void onError(final int errorCode, RequestHolder holder) {
mIdle.open();
final CaptureResultExtras extras = getExtrasFromRequest(holder);
mResultHandler.post(new Runnable() {
@Override
public void run() {
try {
mDeviceCallbacks.onCameraError(errorCode, extras);
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onCameraError callback: ", e);
}
}
});
}
@Override
public void onConfiguring() {
// Do nothing
}
@Override
public void onIdle() {
mIdle.open();
mResultHandler.post(new Runnable() {
@Override
public void run() {
try {
mDeviceCallbacks.onCameraIdle();
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onCameraIdle callback: ", e);
}
}
});
}
@Override
public void onCaptureStarted(RequestHolder holder) {
final CaptureResultExtras extras = getExtrasFromRequest(holder);
final long timestamp = System.nanoTime();
mResultHandler.post(new Runnable() {
@Override
public void run() {
try {
// TODO: Don't fake timestamp
mDeviceCallbacks.onCaptureStarted(extras, timestamp);
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onCameraError callback: ", e);
}
}
});
}
@Override
public void onCaptureResult(final CameraMetadataNative result, RequestHolder holder) {
final CaptureResultExtras extras = getExtrasFromRequest(holder);
mResultHandler.post(new Runnable() {
@Override
public void run() {
try {
// TODO: Don't fake metadata
mDeviceCallbacks.onResultReceived(result, extras);
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onCameraError callback: ", e);
}
}
});
}
};
private final RequestThreadManager mRequestThreadManager;
/**
* Check if a given surface uses {@link ImageFormat#YUV_420_888} format.
*
* @param s the surface to check.
* @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888}.
*/
static boolean needsConversion(Surface s) {
return LegacyCameraDevice.nativeDetectSurfaceType(s) == ImageFormat.YUV_420_888;
}
/**
* Create a new emulated camera device from a given Camera 1 API camera.
*
* <p>
* The {@link Camera} provided to this constructor must already have been successfully opened,
* and ownership of the provided camera is passed to this object. No further calls to the
* camera methods should be made following this constructor.
* </p>
*
* @param cameraId the id of the camera.
* @param camera an open {@link Camera} device.
* @param callbacks {@link ICameraDeviceCallbacks} callbacks to call for Camera2 API operations.
*/
public LegacyCameraDevice(int cameraId, Camera camera, ICameraDeviceCallbacks callbacks) {
mCameraId = cameraId;
mDeviceCallbacks = callbacks;
TAG = String.format("CameraDevice-%d-LE", mCameraId);
mResultThread.start();
mResultHandler = new Handler(mResultThread.getLooper());
mCallbackHandlerThread.start();
mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper());
mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener);
mRequestThreadManager =
new RequestThreadManager(cameraId, camera, mDeviceState);
mRequestThreadManager.start();
}
/**
* Configure the device with a set of output surfaces.
*
* @param outputs a list of surfaces to set.
* @return an error code for this binder operation, or {@link CameraBinderDecorator.NO_ERROR}
* on success.
*/
public int configureOutputs(List<Surface> outputs) {
int error = mDeviceState.setConfiguring();
if (error == CameraBinderDecorator.NO_ERROR) {
mRequestThreadManager.configure(outputs);
error = mDeviceState.setIdle();
}
return error;
}
/**
* Submit a burst of capture requests.
*
* @param requestList a list of capture requests to execute.
* @param repeating {@code true} if this burst is repeating.
* @param frameNumber an output argument that contains either the frame number of the last frame
* that will be returned for this request, or the frame number of the last
* frame that will be returned for the current repeating request if this
* burst is set to be repeating.
* @return the request id.
*/
public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
/*out*/LongParcelable frameNumber) {
// TODO: validate request here
mIdle.close();
return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
frameNumber);
}
/**
* Submit a single capture request.
*
* @param request the capture request to execute.
* @param repeating {@code true} if this request is repeating.
* @param frameNumber an output argument that contains either the frame number of the last frame
* that will be returned for this request, or the frame number of the last
* frame that will be returned for the current repeating request if this
* request is set to be repeating.
* @return the request id.
*/
public int submitRequest(CaptureRequest request, boolean repeating,
/*out*/LongParcelable frameNumber) {
ArrayList<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
requestList.add(request);
return submitRequestList(requestList, repeating, frameNumber);
}
/**
* Cancel the repeating request with the given request id.
*
* @param requestId the request id of the request to cancel.
* @return the last frame number to be returned from the HAL for the given repeating request, or
* {@code INVALID_FRAME} if none exists.
*/
public long cancelRequest(int requestId) {
return mRequestThreadManager.cancelRepeating(requestId);
}
/**
* Block until the {@link ICameraDeviceCallbacks#onCameraIdle()} callback is received.
*/
public void waitUntilIdle() {
mIdle.block();
}
@Override
public void close() {
mRequestThreadManager.quit();
mCallbackHandlerThread.quitSafely();
mResultThread.quitSafely();
try {
mCallbackHandlerThread.join();
} catch (InterruptedException e) {
Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
mCallbackHandlerThread.getName(), mCallbackHandlerThread.getId()));
}
try {
mResultThread.join();
} catch (InterruptedException e) {
Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
mResultThread.getName(), mResultThread.getId()));
}
// TODO: throw IllegalStateException in every method after close has been called
}
@Override
protected void finalize() throws Throwable {
try {
close();
} catch (CameraRuntimeException e) {
Log.e(TAG, "Got error while trying to finalize, ignoring: " + e.getMessage());
} finally {
super.finalize();
}
}
protected static native int nativeDetectSurfaceType(Surface surface);
protected static native void nativeDetectSurfaceDimens(Surface surface, int[] dimens);
protected static native void nativeConfigureSurface(Surface surface, int width, int height,
int pixelFormat);
protected static native void nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
int height, int pixelFormat);
protected static native void nativeSetSurfaceFormat(Surface surface, int pixelFormat);
protected static native void nativeSetSurfaceDimens(Surface surface, int width, int height);
}