Merge "Camera: Remove all camera HALv1 code"

This commit is contained in:
Eino-Ville Talvala
2020-07-13 17:12:42 +00:00
committed by Android (Google) Code Review
36 changed files with 352 additions and 12157 deletions

View File

@@ -42424,10 +42424,6 @@ Landroid/hardware/camera2/impl/CameraMetadataNative$Key;
Landroid/hardware/camera2/impl/CameraMetadataNative;
Landroid/hardware/camera2/impl/GetCommand;
Landroid/hardware/camera2/impl/SetCommand;
Landroid/hardware/camera2/legacy/LegacyCameraDevice;
Landroid/hardware/camera2/legacy/LegacyExceptionUtils$BufferQueueAbandonedException;
Landroid/hardware/camera2/legacy/LegacyMetadataMapper;
Landroid/hardware/camera2/legacy/PerfMeasurement;
Landroid/hardware/camera2/marshal/MarshalHelpers;
Landroid/hardware/camera2/marshal/MarshalQueryable;
Landroid/hardware/camera2/marshal/MarshalRegistry$MarshalToken;

View File

@@ -2297,10 +2297,6 @@ android.hardware.camera2.impl.CameraMetadataNative$Key
android.hardware.camera2.impl.CameraMetadataNative
android.hardware.camera2.impl.GetCommand
android.hardware.camera2.impl.SetCommand
android.hardware.camera2.legacy.LegacyCameraDevice
android.hardware.camera2.legacy.LegacyExceptionUtils$BufferQueueAbandonedException
android.hardware.camera2.legacy.LegacyMetadataMapper
android.hardware.camera2.legacy.PerfMeasurement
android.hardware.camera2.marshal.MarshalHelpers
android.hardware.camera2.marshal.MarshalQueryable
android.hardware.camera2.marshal.MarshalRegistry$MarshalToken

View File

@@ -249,14 +249,10 @@ public class Camera {
public static final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
/**
* A constant meaning the normal camera connect/open will be used.
* Camera HAL device API version 3.0
* @hide
*/
private static final int CAMERA_HAL_API_VERSION_NORMAL_CONNECT = -2;
/**
* Used to indicate HAL version un-specified.
*/
private static final int CAMERA_HAL_API_VERSION_UNSPECIFIED = -1;
public static final int CAMERA_HAL_API_VERSION_3_0 = 0x300;
/**
* Hardware face detection. It does not use much CPU.
@@ -427,7 +423,7 @@ public class Camera {
* Creates a new Camera object to access a particular hardware camera with
* given hal API version. If the same camera is opened by other applications
* or the hal API version is not supported by this device, this will throw a
* RuntimeException.
* RuntimeException. As of Android 12, HAL version 1 is no longer supported.
* <p>
* You must call {@link #release()} when you are done using the camera,
* otherwise it will remain locked and be unavailable to other applications.
@@ -463,49 +459,14 @@ public class Camera {
*/
@UnsupportedAppUsage
public static Camera openLegacy(int cameraId, int halVersion) {
if (halVersion < CAMERA_HAL_API_VERSION_1_0) {
throw new IllegalArgumentException("Invalid HAL version " + halVersion);
if (halVersion < CAMERA_HAL_API_VERSION_3_0) {
throw new IllegalArgumentException("Unsupported HAL version " + halVersion);
}
return new Camera(cameraId, halVersion);
return new Camera(cameraId);
}
/**
* Create a legacy camera object.
*
* @param cameraId The hardware camera to access, between 0 and
* {@link #getNumberOfCameras()}-1.
* @param halVersion The HAL API version this camera device to be opened as.
*/
private Camera(int cameraId, int halVersion) {
int err = cameraInitVersion(cameraId, halVersion);
if (checkInitErrors(err)) {
if (err == -EACCES) {
throw new RuntimeException("Fail to connect to camera service");
} else if (err == -ENODEV) {
throw new RuntimeException("Camera initialization failed");
} else if (err == -ENOSYS) {
throw new RuntimeException("Camera initialization failed because some methods"
+ " are not implemented");
} else if (err == -EOPNOTSUPP) {
throw new RuntimeException("Camera initialization failed because the hal"
+ " version is not supported by this device");
} else if (err == -EINVAL) {
throw new RuntimeException("Camera initialization failed because the input"
+ " arugments are invalid");
} else if (err == -EBUSY) {
throw new RuntimeException("Camera initialization failed because the camera"
+ " device was already opened");
} else if (err == -EUSERS) {
throw new RuntimeException("Camera initialization failed because the max"
+ " number of camera devices were already opened");
}
// Should never hit this.
throw new RuntimeException("Unknown camera error");
}
}
private int cameraInitVersion(int cameraId, int halVersion) {
private int cameraInit(int cameraId) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
@@ -523,35 +484,13 @@ public class Camera {
mEventHandler = null;
}
return native_setup(new WeakReference<Camera>(this), cameraId, halVersion,
return native_setup(new WeakReference<Camera>(this), cameraId,
ActivityThread.currentOpPackageName());
}
private int cameraInitNormal(int cameraId) {
return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_NORMAL_CONNECT);
}
/**
* Connect to the camera service using #connectLegacy
*
* <p>
* This acts the same as normal except that it will return
* the detailed error code if open fails instead of
* converting everything into {@code NO_INIT}.</p>
*
* <p>Intended to use by the camera2 shim only, do <i>not</i> use this for other code.</p>
*
* @return a detailed errno error code, or {@code NO_ERROR} on success
*
* @hide
*/
public int cameraInitUnspecified(int cameraId) {
return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_UNSPECIFIED);
}
/** used by Camera#open, Camera#open(int) */
Camera(int cameraId) {
int err = cameraInitNormal(cameraId);
int err = cameraInit(cameraId);
if (checkInitErrors(err)) {
if (err == -EACCES) {
throw new RuntimeException("Fail to connect to camera service");
@@ -616,8 +555,7 @@ public class Camera {
}
@UnsupportedAppUsage
private native final int native_setup(Object camera_this, int cameraId, int halVersion,
String packageName);
private native int native_setup(Object cameraThis, int cameraId, String packageName);
private native final void native_release();

View File

@@ -23,14 +23,11 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
import android.hardware.CameraInfo;
import android.hardware.CameraStatus;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.legacy.CameraDeviceUserShim;
import android.hardware.camera2.legacy.LegacyMetadataMapper;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.CameraIdAndSessionConfiguration;
import android.hardware.camera2.utils.ConcurrentCameraIdCombination;
@@ -405,10 +402,6 @@ public final class CameraManager {
throw new IllegalArgumentException("No cameras available on device");
}
synchronized (mLock) {
/*
* Get the camera characteristics from the camera service directly if it supports it,
* otherwise get them from the legacy shim instead.
*/
ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
if (cameraService == null) {
throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
@@ -417,34 +410,18 @@ public final class CameraManager {
try {
Size displaySize = getDisplaySize();
// First check isHiddenPhysicalCamera to avoid supportsCamera2ApiLocked throwing
// exception in case cameraId is a hidden physical camera.
if (!isHiddenPhysicalCamera(cameraId) && !supportsCamera2ApiLocked(cameraId)) {
// Legacy backwards compatibility path; build static info from the camera
// parameters
int id = Integer.parseInt(cameraId);
String parameters = cameraService.getLegacyParameters(id);
CameraInfo info = cameraService.getCameraInfo(id);
characteristics = LegacyMetadataMapper.createCharacteristics(parameters, info,
id, displaySize);
} else {
// Normal path: Get the camera characteristics directly from the camera service
CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId);
try {
info.setCameraId(Integer.parseInt(cameraId));
} catch (NumberFormatException e) {
// For external camera, reaching here is expected.
Log.v(TAG, "Failed to parse camera Id " + cameraId + " to integer");
}
boolean hasConcurrentStreams =
CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId);
info.setHasMandatoryConcurrentStreams(hasConcurrentStreams);
info.setDisplaySize(displaySize);
characteristics = new CameraCharacteristics(info);
CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId);
try {
info.setCameraId(Integer.parseInt(cameraId));
} catch (NumberFormatException e) {
Log.v(TAG, "Failed to parse camera Id " + cameraId + " to integer");
}
boolean hasConcurrentStreams =
CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId);
info.setHasMandatoryConcurrentStreams(hasConcurrentStreams);
info.setDisplaySize(displaySize);
characteristics = new CameraCharacteristics(info);
} catch (ServiceSpecificException e) {
throwAsPublicException(e);
} catch (RemoteException e) {
@@ -500,30 +477,14 @@ public final class CameraManager {
ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
try {
if (supportsCamera2ApiLocked(cameraId)) {
// Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
if (cameraService == null) {
throw new ServiceSpecificException(
ICameraService.ERROR_DISCONNECTED,
"Camera service is currently unavailable");
}
cameraUser = cameraService.connectDevice(callbacks, cameraId,
mContext.getOpPackageName(), mContext.getAttributionTag(), uid);
} else {
// Use legacy camera implementation for HAL1 devices
int id;
try {
id = Integer.parseInt(cameraId);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: "
+ cameraId);
}
Log.i(TAG, "Using legacy camera HAL.");
cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id,
getDisplaySize());
ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
if (cameraService == null) {
throw new ServiceSpecificException(
ICameraService.ERROR_DISCONNECTED,
"Camera service is currently unavailable");
}
cameraUser = cameraService.connectDevice(callbacks, cameraId,
mContext.getOpPackageName(), mContext.getAttributionTag(), uid);
} catch (ServiceSpecificException e) {
if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
throw new AssertionError("Should've gone down the shim path");
@@ -1020,44 +981,6 @@ public final class CameraManager {
}
}
/**
* Queries the camera service if it supports the camera2 api directly, or needs a shim.
*
* @param cameraId a non-{@code null} camera identifier
* @return {@code false} if the legacy shim needs to be used, {@code true} otherwise.
*/
private boolean supportsCamera2ApiLocked(String cameraId) {
return supportsCameraApiLocked(cameraId, API_VERSION_2);
}
/**
* Queries the camera service if it supports a camera api directly, or needs a shim.
*
* @param cameraId a non-{@code null} camera identifier
* @param apiVersion the version, i.e. {@code API_VERSION_1} or {@code API_VERSION_2}
* @return {@code true} if connecting will work for that device version.
*/
private boolean supportsCameraApiLocked(String cameraId, int apiVersion) {
/*
* Possible return values:
* - NO_ERROR => CameraX API is supported
* - CAMERA_DEPRECATED_HAL => CameraX API is *not* supported (thrown as an exception)
* - Remote exception => If the camera service died
*
* Anything else is an unexpected error we don't want to recover from.
*/
try {
ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
// If no camera service, no support
if (cameraService == null) return false;
return cameraService.supportsCameraApi(cameraId, apiVersion);
} catch (RemoteException e) {
// Camera service is now down, no support for any API level
}
return false;
}
/**
* Queries the camera service if a cameraId is a hidden physical camera that belongs to a
* logical camera device.

View File

@@ -1,90 +0,0 @@
/*
* 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.hardware.camera2.CaptureRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Immutable container for a burst of capture results.
*/
public class BurstHolder {
private static final String TAG = "BurstHolder";
private final ArrayList<RequestHolder.Builder> mRequestBuilders;
private final boolean mRepeating;
private final int mRequestId;
/**
* Immutable container for a burst of capture results.
*
* @param requestId id of the burst request.
* @param repeating true if this burst is repeating.
* @param requests the array of {@link CaptureRequest}s for this burst.
* @param jpegSurfaceIds a {@link Collection} of IDs for the surfaces that have jpeg outputs.
*/
public BurstHolder(int requestId, boolean repeating, CaptureRequest[] requests,
Collection<Long> jpegSurfaceIds) {
mRequestBuilders = new ArrayList<>();
int i = 0;
for (CaptureRequest r : requests) {
mRequestBuilders.add(new RequestHolder.Builder(requestId, /*subsequenceId*/i,
/*request*/r, repeating, jpegSurfaceIds));
++i;
}
mRepeating = repeating;
mRequestId = requestId;
}
/**
* Get the id of this request.
*/
public int getRequestId() {
return mRequestId;
}
/**
* Return true if this repeating.
*/
public boolean isRepeating() {
return mRepeating;
}
/**
* Return the number of requests in this burst sequence.
*/
public int getNumberOfRequests() {
return mRequestBuilders.size();
}
/**
* Create a list of {@link RequestHolder} objects encapsulating the requests in this burst.
*
* @param frameNumber the starting framenumber for this burst.
* @return the list of {@link RequestHolder} objects.
*/
public List<RequestHolder> produceRequestHolders(long frameNumber) {
ArrayList<RequestHolder> holders = new ArrayList<RequestHolder>();
int i = 0;
for (RequestHolder.Builder b : mRequestBuilders) {
holders.add(b.build(frameNumber + i));
++i;
}
return holders;
}
}

View File

@@ -1,362 +0,0 @@
/*
* 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.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.os.Handler;
import android.util.Log;
/**
* Emulates a the state of a single Camera2 device.
*
* <p>
* This class acts as the state machine for a camera device. Valid state transitions are given
* in the table below:
* </p>
*
* <ul>
* <li>{@code UNCONFIGURED -> CONFIGURING}</li>
* <li>{@code CONFIGURING -> IDLE}</li>
* <li>{@code IDLE -> CONFIGURING}</li>
* <li>{@code IDLE -> CAPTURING}</li>
* <li>{@code IDLE -> IDLE}</li>
* <li>{@code CAPTURING -> IDLE}</li>
* <li>{@code ANY -> ERROR}</li>
* </ul>
*/
public class CameraDeviceState {
private static final String TAG = "CameraDeviceState";
private static final boolean DEBUG = false;
private static final int STATE_ERROR = 0;
private static final int STATE_UNCONFIGURED = 1;
private static final int STATE_CONFIGURING = 2;
private static final int STATE_IDLE = 3;
private static final int STATE_CAPTURING = 4;
private static final String[] sStateNames = { "ERROR", "UNCONFIGURED", "CONFIGURING", "IDLE",
"CAPTURING"};
private int mCurrentState = STATE_UNCONFIGURED;
private int mCurrentError = NO_CAPTURE_ERROR;
private RequestHolder mCurrentRequest = null;
private Handler mCurrentHandler = null;
private CameraDeviceStateListener mCurrentListener = null;
/**
* Error code used by {@link #setCaptureStart} and {@link #setCaptureResult} to indicate that no
* error has occurred.
*/
public static final int NO_CAPTURE_ERROR = -1;
/**
* CameraDeviceStateListener callbacks to be called after state transitions.
*/
public interface CameraDeviceStateListener {
void onError(int errorCode, Object errorArg, RequestHolder holder);
void onConfiguring();
void onIdle();
void onBusy();
void onCaptureStarted(RequestHolder holder, long timestamp);
void onCaptureResult(CameraMetadataNative result, RequestHolder holder);
void onRequestQueueEmpty();
void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId);
}
/**
* Transition to the {@code ERROR} state.
*
* <p>
* The device cannot exit the {@code ERROR} state. If the device was not already in the
* {@code ERROR} state, {@link CameraDeviceStateListener#onError(int, RequestHolder)} will be
* called.
* </p>
*
* @param error the error to set. Should be one of the error codes defined in
* {@link CameraDeviceImpl.CameraDeviceCallbacks}.
*/
public synchronized void setError(int error) {
mCurrentError = error;
doStateTransition(STATE_ERROR);
}
/**
* Transition to the {@code CONFIGURING} state, or {@code ERROR} if in an invalid state.
*
* <p>
* If the device was not already in the {@code CONFIGURING} state,
* {@link CameraDeviceStateListener#onConfiguring()} will be called.
* </p>
*
* @return {@code false} if an error has occurred.
*/
public synchronized boolean setConfiguring() {
doStateTransition(STATE_CONFIGURING);
return mCurrentError == NO_CAPTURE_ERROR;
}
/**
* Transition to the {@code IDLE} state, or {@code ERROR} if in an invalid state.
*
* <p>
* If the device was not already in the {@code IDLE} state,
* {@link CameraDeviceStateListener#onIdle()} will be called.
* </p>
*
* @return {@code false} if an error has occurred.
*/
public synchronized boolean setIdle() {
doStateTransition(STATE_IDLE);
return mCurrentError == NO_CAPTURE_ERROR;
}
/**
* Transition to the {@code CAPTURING} state, or {@code ERROR} if in an invalid state.
*
* <p>
* If the device was not already in the {@code CAPTURING} state,
* {@link CameraDeviceStateListener#onCaptureStarted(RequestHolder)} will be called.
* </p>
*
* @param request A {@link RequestHolder} containing the request for the current capture.
* @param timestamp The timestamp of the capture start in nanoseconds.
* @param captureError Report a recoverable error for a single request using a valid
* error code for {@code ICameraDeviceCallbacks}, or
* {@link #NO_CAPTURE_ERROR}
* @return {@code false} if an error has occurred.
*/
public synchronized boolean setCaptureStart(final RequestHolder request, long timestamp,
int captureError) {
mCurrentRequest = request;
doStateTransition(STATE_CAPTURING, timestamp, captureError);
return mCurrentError == NO_CAPTURE_ERROR;
}
/**
* Set the result for a capture.
*
* <p>
* If the device was in the {@code CAPTURING} state,
* {@link CameraDeviceStateListener#onCaptureResult(CameraMetadataNative, RequestHolder)} will
* be called with the given result, otherwise this will result in the device transitioning to
* the {@code ERROR} state,
* </p>
*
* @param request The {@link RequestHolder} request that created this result.
* @param result The {@link CameraMetadataNative} result to set.
* @param captureError Report a recoverable error for a single buffer or result using a valid
* error code for {@code ICameraDeviceCallbacks}, or
* {@link #NO_CAPTURE_ERROR}.
* @param captureErrorArg An argument for some error captureError codes.
* @return {@code false} if an error has occurred.
*/
public synchronized boolean setCaptureResult(final RequestHolder request,
final CameraMetadataNative result,
final int captureError, final Object captureErrorArg) {
if (mCurrentState != STATE_CAPTURING) {
Log.e(TAG, "Cannot receive result while in state: " + mCurrentState);
mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE;
doStateTransition(STATE_ERROR);
return mCurrentError == NO_CAPTURE_ERROR;
}
if (mCurrentHandler != null && mCurrentListener != null) {
if (captureError != NO_CAPTURE_ERROR) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onError(captureError, captureErrorArg, request);
}
});
} else {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onCaptureResult(result, request);
}
});
}
}
return mCurrentError == NO_CAPTURE_ERROR;
}
public synchronized boolean setCaptureResult(final RequestHolder request,
final CameraMetadataNative result) {
return setCaptureResult(request, result, NO_CAPTURE_ERROR, /*errorArg*/null);
}
/**
* Set repeating request error.
*
* <p>Repeating request has been stopped due to an error such as abandoned output surfaces.</p>
*
* @param lastFrameNumber Frame number of the last repeating request before it is stopped.
* @param repeatingRequestId The ID of the repeating request being stopped
*/
public synchronized void setRepeatingRequestError(final long lastFrameNumber,
final int repeatingRequestId) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onRepeatingRequestError(lastFrameNumber, repeatingRequestId);
}
});
}
/**
* Indicate that request queue (non-repeating) becomes empty.
*
* <p> Send notification that all non-repeating requests have been sent to camera device. </p>
*/
public synchronized void setRequestQueueEmpty() {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onRequestQueueEmpty();
}
});
}
/**
* Set the listener for state transition callbacks.
*
* @param handler handler on which to call the callbacks.
* @param listener the {@link CameraDeviceStateListener} callbacks to call.
*/
public synchronized void setCameraDeviceCallbacks(Handler handler,
CameraDeviceStateListener listener) {
mCurrentHandler = handler;
mCurrentListener = listener;
}
private void doStateTransition(int newState) {
doStateTransition(newState, /*timestamp*/0, NO_CAPTURE_ERROR);
}
private void doStateTransition(int newState, final long timestamp, final int error) {
if (newState != mCurrentState) {
String stateName = "UNKNOWN";
if (newState >= 0 && newState < sStateNames.length) {
stateName = sStateNames[newState];
}
Log.i(TAG, "Legacy camera service transitioning to state " + stateName);
}
// If we transitioned into a non-IDLE/non-ERROR state then mark the device as busy
if(newState != STATE_ERROR && newState != STATE_IDLE) {
if (mCurrentState != newState && mCurrentHandler != null &&
mCurrentListener != null) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onBusy();
}
});
}
}
switch(newState) {
case STATE_ERROR:
if (mCurrentState != STATE_ERROR && mCurrentHandler != null &&
mCurrentListener != null) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onError(mCurrentError, /*errorArg*/null, mCurrentRequest);
}
});
}
mCurrentState = STATE_ERROR;
break;
case STATE_CONFIGURING:
if (mCurrentState != STATE_UNCONFIGURED && mCurrentState != STATE_IDLE) {
Log.e(TAG, "Cannot call configure while in state: " + mCurrentState);
mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE;
doStateTransition(STATE_ERROR);
break;
}
if (mCurrentState != STATE_CONFIGURING && mCurrentHandler != null &&
mCurrentListener != null) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onConfiguring();
}
});
}
mCurrentState = STATE_CONFIGURING;
break;
case STATE_IDLE:
if (mCurrentState == STATE_IDLE) {
break;
}
if (mCurrentState != STATE_CONFIGURING && mCurrentState != STATE_CAPTURING) {
Log.e(TAG, "Cannot call idle while in state: " + mCurrentState);
mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE;
doStateTransition(STATE_ERROR);
break;
}
if (mCurrentState != STATE_IDLE && mCurrentHandler != null &&
mCurrentListener != null) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onIdle();
}
});
}
mCurrentState = STATE_IDLE;
break;
case STATE_CAPTURING:
if (mCurrentState != STATE_IDLE && mCurrentState != STATE_CAPTURING) {
Log.e(TAG, "Cannot call capture while in state: " + mCurrentState);
mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE;
doStateTransition(STATE_ERROR);
break;
}
if (mCurrentHandler != null && mCurrentListener != null) {
if (error != NO_CAPTURE_ERROR) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onError(error, /*errorArg*/null, mCurrentRequest);
}
});
} else {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onCaptureStarted(mCurrentRequest, timestamp);
}
});
}
}
mCurrentState = STATE_CAPTURING;
break;
default:
throw new IllegalStateException("Transition to unknown state: " + newState);
}
}
}

View File

@@ -1,805 +0,0 @@
/*
* 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.hardware.ICameraService;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.SubmitInfo;
import android.os.ConditionVariable;
import android.os.IBinder;
import android.os.Looper;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
import android.util.Size;
import android.util.SparseArray;
import android.view.Surface;
import java.util.ArrayList;
import java.util.List;
import static android.system.OsConstants.EACCES;
import static android.system.OsConstants.ENODEV;
/**
* Compatibility implementation of the Camera2 API binder interface.
*
* <p>
* This is intended to be called from the same process as client
* {@link android.hardware.camera2.CameraDevice}, and wraps a
* {@link android.hardware.camera2.legacy.LegacyCameraDevice} that emulates Camera2 service using
* the Camera1 API.
* </p>
*
* <p>
* Keep up to date with ICameraDeviceUser.aidl.
* </p>
*/
@SuppressWarnings("deprecation")
public class CameraDeviceUserShim implements ICameraDeviceUser {
private static final String TAG = "CameraDeviceUserShim";
private static final boolean DEBUG = false;
private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout)
private final LegacyCameraDevice mLegacyDevice;
private final Object mConfigureLock = new Object();
private int mSurfaceIdCounter;
private boolean mConfiguring;
private final SparseArray<Surface> mSurfaces;
private final CameraCharacteristics mCameraCharacteristics;
private final CameraLooper mCameraInit;
private final CameraCallbackThread mCameraCallbacks;
protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera,
CameraCharacteristics characteristics, CameraLooper cameraInit,
CameraCallbackThread cameraCallbacks) {
mLegacyDevice = legacyCamera;
mConfiguring = false;
mSurfaces = new SparseArray<Surface>();
mCameraCharacteristics = characteristics;
mCameraInit = cameraInit;
mCameraCallbacks = cameraCallbacks;
mSurfaceIdCounter = 0;
}
private static int translateErrorsFromCamera1(int errorCode) {
if (errorCode == -EACCES) {
return ICameraService.ERROR_PERMISSION_DENIED;
}
return errorCode;
}
/**
* Create a separate looper/thread for the camera to run on; open the camera.
*
* <p>Since the camera automatically latches on to the current thread's looper,
* it's important that we have our own thread with our own looper to guarantee
* that the camera callbacks get correctly posted to our own thread.</p>
*/
private static class CameraLooper implements Runnable, AutoCloseable {
private final int mCameraId;
private Looper mLooper;
private volatile int mInitErrors;
private final Camera mCamera = Camera.openUninitialized();
private final ConditionVariable mStartDone = new ConditionVariable();
private final Thread mThread;
/**
* Spin up a new thread, immediately open the camera in the background.
*
* <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p>
*
* @param cameraId numeric camera Id
*
* @see #waitForOpen
*/
public CameraLooper(int cameraId) {
mCameraId = cameraId;
mThread = new Thread(this, "LegacyCameraLooper");
mThread.start();
}
public Camera getCamera() {
return mCamera;
}
@Override
public void run() {
// Set up a looper to be used by camera.
Looper.prepare();
// Save the looper so that we can terminate this thread
// after we are done with it.
mLooper = Looper.myLooper();
mInitErrors = mCamera.cameraInitUnspecified(mCameraId);
mStartDone.open();
Looper.loop(); // Blocks forever until #close is called.
}
/**
* Quit the looper safely; then join until the thread shuts down.
*/
@Override
public void close() {
if (mLooper == null) {
return;
}
mLooper.quitSafely();
try {
mThread.join();
} catch (InterruptedException e) {
throw new AssertionError(e);
}
mLooper = null;
}
/**
* Block until the camera opens; then return its initialization error code (if any).
*
* @param timeoutMs timeout in milliseconds
*
* @return int error code
*
* @throws ServiceSpecificException if the camera open times out with ({@code CAMERA_ERROR})
*/
public int waitForOpen(int timeoutMs) {
// Block until the camera is open asynchronously
if (!mStartDone.block(timeoutMs)) {
Log.e(TAG, "waitForOpen - Camera failed to open after timeout of "
+ OPEN_CAMERA_TIMEOUT_MS + " ms");
try {
mCamera.release();
} catch (RuntimeException e) {
Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e);
}
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION);
}
return mInitErrors;
}
}
/**
* A thread to process callbacks to send back to the camera client.
*
* <p>This effectively emulates one-way binder semantics when in the same process as the
* callee.</p>
*/
private static class CameraCallbackThread implements ICameraDeviceCallbacks {
private static final int CAMERA_ERROR = 0;
private static final int CAMERA_IDLE = 1;
private static final int CAPTURE_STARTED = 2;
private static final int RESULT_RECEIVED = 3;
private static final int PREPARED = 4;
private static final int REPEATING_REQUEST_ERROR = 5;
private static final int REQUEST_QUEUE_EMPTY = 6;
private final HandlerThread mHandlerThread;
private Handler mHandler;
private final ICameraDeviceCallbacks mCallbacks;
public CameraCallbackThread(ICameraDeviceCallbacks callbacks) {
mCallbacks = callbacks;
mHandlerThread = new HandlerThread("LegacyCameraCallback");
mHandlerThread.start();
}
public void close() {
mHandlerThread.quitSafely();
}
@Override
public void onDeviceError(final int errorCode, final CaptureResultExtras resultExtras) {
Message msg = getHandler().obtainMessage(CAMERA_ERROR,
/*arg1*/ errorCode, /*arg2*/ 0,
/*obj*/ resultExtras);
getHandler().sendMessage(msg);
}
@Override
public void onDeviceIdle() {
Message msg = getHandler().obtainMessage(CAMERA_IDLE);
getHandler().sendMessage(msg);
}
@Override
public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
Message msg = getHandler().obtainMessage(CAPTURE_STARTED,
/*arg1*/ (int) (timestamp & 0xFFFFFFFFL),
/*arg2*/ (int) ( (timestamp >> 32) & 0xFFFFFFFFL),
/*obj*/ resultExtras);
getHandler().sendMessage(msg);
}
@Override
public void onResultReceived(final CameraMetadataNative result,
final CaptureResultExtras resultExtras,
PhysicalCaptureResultInfo physicalResults[]) {
Object[] resultArray = new Object[] { result, resultExtras };
Message msg = getHandler().obtainMessage(RESULT_RECEIVED,
/*obj*/ resultArray);
getHandler().sendMessage(msg);
}
@Override
public void onPrepared(int streamId) {
Message msg = getHandler().obtainMessage(PREPARED,
/*arg1*/ streamId, /*arg2*/ 0);
getHandler().sendMessage(msg);
}
@Override
public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
Object[] objArray = new Object[] { lastFrameNumber, repeatingRequestId };
Message msg = getHandler().obtainMessage(REPEATING_REQUEST_ERROR,
/*obj*/ objArray);
getHandler().sendMessage(msg);
}
@Override
public void onRequestQueueEmpty() {
Message msg = getHandler().obtainMessage(REQUEST_QUEUE_EMPTY,
/* arg1 */ 0, /* arg2 */ 0);
getHandler().sendMessage(msg);
}
@Override
public IBinder asBinder() {
// This is solely intended to be used for in-process binding.
return null;
}
private Handler getHandler() {
if (mHandler == null) {
mHandler = new CallbackHandler(mHandlerThread.getLooper());
}
return mHandler;
}
private class CallbackHandler extends Handler {
public CallbackHandler(Looper l) {
super(l);
}
@Override
public void handleMessage(Message msg) {
try {
switch (msg.what) {
case CAMERA_ERROR: {
int errorCode = msg.arg1;
CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
mCallbacks.onDeviceError(errorCode, resultExtras);
break;
}
case CAMERA_IDLE:
mCallbacks.onDeviceIdle();
break;
case CAPTURE_STARTED: {
long timestamp = msg.arg2 & 0xFFFFFFFFL;
timestamp = (timestamp << 32) | (msg.arg1 & 0xFFFFFFFFL);
CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
mCallbacks.onCaptureStarted(resultExtras, timestamp);
break;
}
case RESULT_RECEIVED: {
Object[] resultArray = (Object[]) msg.obj;
CameraMetadataNative result = (CameraMetadataNative) resultArray[0];
CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1];
mCallbacks.onResultReceived(result, resultExtras,
new PhysicalCaptureResultInfo[0]);
break;
}
case PREPARED: {
int streamId = msg.arg1;
mCallbacks.onPrepared(streamId);
break;
}
case REPEATING_REQUEST_ERROR: {
Object[] objArray = (Object[]) msg.obj;
long lastFrameNumber = (Long) objArray[0];
int repeatingRequestId = (Integer) objArray[1];
mCallbacks.onRepeatingRequestError(lastFrameNumber, repeatingRequestId);
break;
}
case REQUEST_QUEUE_EMPTY: {
mCallbacks.onRequestQueueEmpty();
break;
}
default:
throw new IllegalArgumentException(
"Unknown callback message " + msg.what);
}
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during camera callback " + msg.what, e);
}
}
}
}
public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
int cameraId, Size displaySize) {
if (DEBUG) {
Log.d(TAG, "Opening shim Camera device");
}
/*
* Put the camera open on a separate thread with its own looper; otherwise
* if the main thread is used then the callbacks might never get delivered
* (e.g. in CTS which run its own default looper only after tests)
*/
CameraLooper init = new CameraLooper(cameraId);
CameraCallbackThread threadCallbacks = new CameraCallbackThread(callbacks);
// TODO: Make this async instead of blocking
int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
Camera legacyCamera = init.getCamera();
// Check errors old HAL initialization
LegacyExceptionUtils.throwOnServiceError(initErrors);
// Disable shutter sounds (this will work unconditionally) for api2 clients
legacyCamera.disableShutterSound();
CameraInfo info = new CameraInfo();
Camera.getCameraInfo(cameraId, info);
Camera.Parameters legacyParameters = null;
try {
legacyParameters = legacyCamera.getParameters();
} catch (RuntimeException e) {
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION,
"Unable to get initial parameters: " + e.getMessage());
}
CameraCharacteristics characteristics =
LegacyMetadataMapper.createCharacteristics(legacyParameters, info, cameraId,
displaySize);
LegacyCameraDevice device = new LegacyCameraDevice(
cameraId, legacyCamera, characteristics, threadCallbacks);
return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks);
}
@Override
public void disconnect() {
if (DEBUG) {
Log.d(TAG, "disconnect called.");
}
if (mLegacyDevice.isClosed()) {
Log.w(TAG, "Cannot disconnect, device has already been closed.");
}
try {
mLegacyDevice.close();
} finally {
mCameraInit.close();
mCameraCallbacks.close();
}
}
@Override
public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) {
if (DEBUG) {
Log.d(TAG, "submitRequest called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot submit request, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
synchronized(mConfigureLock) {
if (mConfiguring) {
String err = "Cannot submit request, configuration change in progress.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
}
return mLegacyDevice.submitRequest(request, streaming);
}
@Override
public SubmitInfo submitRequestList(CaptureRequest[] request, boolean streaming) {
if (DEBUG) {
Log.d(TAG, "submitRequestList called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot submit request list, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
synchronized(mConfigureLock) {
if (mConfiguring) {
String err = "Cannot submit request, configuration change in progress.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
}
return mLegacyDevice.submitRequestList(request, streaming);
}
@Override
public long cancelRequest(int requestId) {
if (DEBUG) {
Log.d(TAG, "cancelRequest called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot cancel request, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
synchronized(mConfigureLock) {
if (mConfiguring) {
String err = "Cannot cancel request, configuration change in progress.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
}
return mLegacyDevice.cancelRequest(requestId);
}
@Override
public boolean isSessionConfigurationSupported(SessionConfiguration sessionConfig) {
if (sessionConfig.getSessionType() != SessionConfiguration.SESSION_REGULAR) {
Log.e(TAG, "Session type: " + sessionConfig.getSessionType() + " is different from " +
" regular. Legacy devices support only regular session types!");
return false;
}
if (sessionConfig.getInputConfiguration() != null) {
Log.e(TAG, "Input configuration present, legacy devices do not support this feature!");
return false;
}
List<OutputConfiguration> outputConfigs = sessionConfig.getOutputConfigurations();
if (outputConfigs.isEmpty()) {
Log.e(TAG, "Empty output configuration list!");
return false;
}
SparseArray<Surface> surfaces = new SparseArray<Surface>(outputConfigs.size());
int idx = 0;
for (OutputConfiguration outputConfig : outputConfigs) {
List<Surface> surfaceList = outputConfig.getSurfaces();
if (surfaceList.isEmpty() || (surfaceList.size() > 1)) {
Log.e(TAG, "Legacy devices do not support deferred or shared surfaces!");
return false;
}
surfaces.put(idx++, outputConfig.getSurface());
}
int ret = mLegacyDevice.configureOutputs(surfaces, /*validateSurfacesOnly*/true);
return ret == LegacyExceptionUtils.NO_ERROR;
}
@Override
public void beginConfigure() {
if (DEBUG) {
Log.d(TAG, "beginConfigure called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot begin configure, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
synchronized(mConfigureLock) {
if (mConfiguring) {
String err = "Cannot begin configure, configuration change already in progress.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
mConfiguring = true;
}
}
@Override
public int[] endConfigure(int operatingMode, CameraMetadataNative sessionParams) {
if (DEBUG) {
Log.d(TAG, "endConfigure called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot end configure, device has been closed.";
Log.e(TAG, err);
synchronized(mConfigureLock) {
mConfiguring = false;
}
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
if (operatingMode != ICameraDeviceUser.NORMAL_MODE) {
String err = "LEGACY devices do not support this operating mode";
Log.e(TAG, err);
synchronized(mConfigureLock) {
mConfiguring = false;
}
throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
}
SparseArray<Surface> surfaces = null;
synchronized(mConfigureLock) {
if (!mConfiguring) {
String err = "Cannot end configure, no configuration change in progress.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
if (mSurfaces != null) {
surfaces = mSurfaces.clone();
}
mConfiguring = false;
}
mLegacyDevice.configureOutputs(surfaces);
return new int[0]; // Offline mode is not supported
}
@Override
public void deleteStream(int streamId) {
if (DEBUG) {
Log.d(TAG, "deleteStream called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot delete stream, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
synchronized(mConfigureLock) {
if (!mConfiguring) {
String err = "Cannot delete stream, no configuration change in progress.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
int index = mSurfaces.indexOfKey(streamId);
if (index < 0) {
String err = "Cannot delete stream, stream id " + streamId + " doesn't exist.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
}
mSurfaces.removeAt(index);
}
}
@Override
public int createStream(OutputConfiguration outputConfiguration) {
if (DEBUG) {
Log.d(TAG, "createStream called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot create stream, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
synchronized(mConfigureLock) {
if (!mConfiguring) {
String err = "Cannot create stream, beginConfigure hasn't been called yet.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) {
String err = "Cannot create stream, stream rotation is not supported.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
}
int id = ++mSurfaceIdCounter;
mSurfaces.put(id, outputConfiguration.getSurface());
return id;
}
}
@Override
public void finalizeOutputConfigurations(int steamId, OutputConfiguration config) {
String err = "Finalizing output configuration is not supported on legacy devices";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
@Override
public int createInputStream(int width, int height, int format) {
String err = "Creating input stream is not supported on legacy devices";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
@Override
public Surface getInputSurface() {
String err = "Getting input surface is not supported on legacy devices";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
@Override
public CameraMetadataNative createDefaultRequest(int templateId) {
if (DEBUG) {
Log.d(TAG, "createDefaultRequest called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot create default request, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
CameraMetadataNative template;
try {
template =
LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
} catch (IllegalArgumentException e) {
String err = "createDefaultRequest - invalid templateId specified";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
}
return template;
}
@Override
public CameraMetadataNative getCameraInfo() {
if (DEBUG) {
Log.d(TAG, "getCameraInfo called.");
}
// TODO: implement getCameraInfo.
Log.e(TAG, "getCameraInfo unimplemented.");
return null;
}
@Override
public void updateOutputConfiguration(int streamId, OutputConfiguration config) {
// TODO: b/63912484 implement updateOutputConfiguration.
}
@Override
public void waitUntilIdle() throws RemoteException {
if (DEBUG) {
Log.d(TAG, "waitUntilIdle called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot wait until idle, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
synchronized(mConfigureLock) {
if (mConfiguring) {
String err = "Cannot wait until idle, configuration change in progress.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
}
mLegacyDevice.waitUntilIdle();
}
@Override
public long flush() {
if (DEBUG) {
Log.d(TAG, "flush called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot flush, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
synchronized(mConfigureLock) {
if (mConfiguring) {
String err = "Cannot flush, configuration change in progress.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
}
return mLegacyDevice.flush();
}
public void prepare(int streamId) {
if (DEBUG) {
Log.d(TAG, "prepare called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot prepare stream, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
// LEGACY doesn't support actual prepare, just signal success right away
mCameraCallbacks.onPrepared(streamId);
}
public void prepare2(int maxCount, int streamId) {
// We don't support this in LEGACY mode.
prepare(streamId);
}
public void tearDown(int streamId) {
if (DEBUG) {
Log.d(TAG, "tearDown called.");
}
if (mLegacyDevice.isClosed()) {
String err = "Cannot tear down stream, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
// LEGACY doesn't support actual teardown, so just a no-op
}
@Override
public void setCameraAudioRestriction(int mode) {
if (mLegacyDevice.isClosed()) {
String err = "Cannot set camera audio restriction, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
mLegacyDevice.setAudioRestriction(mode);
}
@Override
public int getGlobalAudioRestriction() {
if (mLegacyDevice.isClosed()) {
String err = "Cannot set camera audio restriction, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
return mLegacyDevice.getAudioRestriction();
}
@Override
public ICameraOfflineSession switchToOffline(ICameraDeviceCallbacks cbs,
int[] offlineOutputIds) {
throw new UnsupportedOperationException("Legacy device does not support offline mode");
}
@Override
public IBinder asBinder() {
// This is solely intended to be used for in-process binding.
return null;
}
}

View File

@@ -1,673 +0,0 @@
/*
* 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.hardware.camera2.impl.CameraDeviceImpl;
import android.util.Log;
import android.util.MutableLong;
import android.util.Pair;
import android.view.Surface;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Collect timestamps and state for each {@link CaptureRequest} as it passes through
* the Legacy camera pipeline.
*/
public class CaptureCollector {
private static final String TAG = "CaptureCollector";
private static final boolean DEBUG = false;
private static final int FLAG_RECEIVED_JPEG = 1;
private static final int FLAG_RECEIVED_JPEG_TS = 2;
private static final int FLAG_RECEIVED_PREVIEW = 4;
private static final int FLAG_RECEIVED_PREVIEW_TS = 8;
private static final int FLAG_RECEIVED_ALL_JPEG = FLAG_RECEIVED_JPEG | FLAG_RECEIVED_JPEG_TS;
private static final int FLAG_RECEIVED_ALL_PREVIEW = FLAG_RECEIVED_PREVIEW |
FLAG_RECEIVED_PREVIEW_TS;
private static final int MAX_JPEGS_IN_FLIGHT = 1;
private class CaptureHolder implements Comparable<CaptureHolder>{
private final RequestHolder mRequest;
private final LegacyRequest mLegacy;
public final boolean needsJpeg;
public final boolean needsPreview;
private long mTimestamp = 0;
private int mReceivedFlags = 0;
private boolean mHasStarted = false;
private boolean mFailedJpeg = false;
private boolean mFailedPreview = false;
private boolean mCompleted = false;
private boolean mPreviewCompleted = false;
public CaptureHolder(RequestHolder request, LegacyRequest legacyHolder) {
mRequest = request;
mLegacy = legacyHolder;
needsJpeg = request.hasJpegTargets();
needsPreview = request.hasPreviewTargets();
}
public boolean isPreviewCompleted() {
return (mReceivedFlags & FLAG_RECEIVED_ALL_PREVIEW) == FLAG_RECEIVED_ALL_PREVIEW;
}
public boolean isJpegCompleted() {
return (mReceivedFlags & FLAG_RECEIVED_ALL_JPEG) == FLAG_RECEIVED_ALL_JPEG;
}
public boolean isCompleted() {
return (needsJpeg == isJpegCompleted()) && (needsPreview == isPreviewCompleted());
}
public void tryComplete() {
if (!mPreviewCompleted && needsPreview && isPreviewCompleted()) {
CaptureCollector.this.onPreviewCompleted();
mPreviewCompleted = true;
}
if (isCompleted() && !mCompleted) {
if (mFailedPreview || mFailedJpeg) {
if (!mHasStarted) {
// Send a request error if the capture has not yet started.
mRequest.failRequest();
CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_REQUEST);
} else {
// Send buffer dropped errors for each pending buffer if the request has
// started.
for (Surface targetSurface : mRequest.getRequest().getTargets() ) {
try {
if (mRequest.jpegType(targetSurface)) {
if (mFailedJpeg) {
CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
/*result*/null,
CameraDeviceImpl.CameraDeviceCallbacks.
ERROR_CAMERA_BUFFER,
targetSurface);
}
} else {
// preview buffer
if (mFailedPreview) {
CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
/*result*/null,
CameraDeviceImpl.CameraDeviceCallbacks.
ERROR_CAMERA_BUFFER,
targetSurface);
}
}
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.e(TAG, "Unexpected exception when querying Surface: " + e);
}
}
}
}
CaptureCollector.this.onRequestCompleted(CaptureHolder.this);
mCompleted = true;
}
}
public void setJpegTimestamp(long timestamp) {
if (DEBUG) {
Log.d(TAG, "setJpegTimestamp - called for request " + mRequest.getRequestId());
}
if (!needsJpeg) {
throw new IllegalStateException(
"setJpegTimestamp called for capture with no jpeg targets.");
}
if (isCompleted()) {
throw new IllegalStateException(
"setJpegTimestamp called on already completed request.");
}
mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
if (mTimestamp == 0) {
mTimestamp = timestamp;
}
if (!mHasStarted) {
mHasStarted = true;
CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
CameraDeviceState.NO_CAPTURE_ERROR);
}
tryComplete();
}
public void setJpegProduced() {
if (DEBUG) {
Log.d(TAG, "setJpegProduced - called for request " + mRequest.getRequestId());
}
if (!needsJpeg) {
throw new IllegalStateException(
"setJpegProduced called for capture with no jpeg targets.");
}
if (isCompleted()) {
throw new IllegalStateException(
"setJpegProduced called on already completed request.");
}
mReceivedFlags |= FLAG_RECEIVED_JPEG;
tryComplete();
}
public void setJpegFailed() {
if (DEBUG) {
Log.d(TAG, "setJpegFailed - called for request " + mRequest.getRequestId());
}
if (!needsJpeg || isJpegCompleted()) {
return;
}
mFailedJpeg = true;
mReceivedFlags |= FLAG_RECEIVED_JPEG;
mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
tryComplete();
}
public void setPreviewTimestamp(long timestamp) {
if (DEBUG) {
Log.d(TAG, "setPreviewTimestamp - called for request " + mRequest.getRequestId());
}
if (!needsPreview) {
throw new IllegalStateException(
"setPreviewTimestamp called for capture with no preview targets.");
}
if (isCompleted()) {
throw new IllegalStateException(
"setPreviewTimestamp called on already completed request.");
}
mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
if (mTimestamp == 0) {
mTimestamp = timestamp;
}
if (!needsJpeg) {
if (!mHasStarted) {
mHasStarted = true;
CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
CameraDeviceState.NO_CAPTURE_ERROR);
}
}
tryComplete();
}
public void setPreviewProduced() {
if (DEBUG) {
Log.d(TAG, "setPreviewProduced - called for request " + mRequest.getRequestId());
}
if (!needsPreview) {
throw new IllegalStateException(
"setPreviewProduced called for capture with no preview targets.");
}
if (isCompleted()) {
throw new IllegalStateException(
"setPreviewProduced called on already completed request.");
}
mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
tryComplete();
}
public void setPreviewFailed() {
if (DEBUG) {
Log.d(TAG, "setPreviewFailed - called for request " + mRequest.getRequestId());
}
if (!needsPreview || isPreviewCompleted()) {
return;
}
mFailedPreview = true;
mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
tryComplete();
}
// Comparison and equals based on frame number.
@Override
public int compareTo(CaptureHolder captureHolder) {
return (mRequest.getFrameNumber() > captureHolder.mRequest.getFrameNumber()) ? 1 :
((mRequest.getFrameNumber() == captureHolder.mRequest.getFrameNumber()) ? 0 :
-1);
}
// Comparison and equals based on frame number.
@Override
public boolean equals(Object o) {
return o instanceof CaptureHolder && compareTo((CaptureHolder) o) == 0;
}
}
private final TreeSet<CaptureHolder> mActiveRequests;
private final ArrayDeque<CaptureHolder> mJpegCaptureQueue;
private final ArrayDeque<CaptureHolder> mJpegProduceQueue;
private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue;
private final ArrayDeque<CaptureHolder> mPreviewProduceQueue;
private final ArrayList<CaptureHolder> mCompletedRequests = new ArrayList<>();
private final ReentrantLock mLock = new ReentrantLock();
private final Condition mIsEmpty;
private final Condition mPreviewsEmpty;
private final Condition mNotFull;
private final CameraDeviceState mDeviceState;
private int mInFlight = 0;
private int mInFlightPreviews = 0;
private final int mMaxInFlight;
/**
* Create a new {@link CaptureCollector} that can modify the given {@link CameraDeviceState}.
*
* @param maxInFlight max allowed in-flight requests.
* @param deviceState the {@link CameraDeviceState} to update as requests are processed.
*/
public CaptureCollector(int maxInFlight, CameraDeviceState deviceState) {
mMaxInFlight = maxInFlight;
mJpegCaptureQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
mJpegProduceQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
mPreviewCaptureQueue = new ArrayDeque<>(mMaxInFlight);
mPreviewProduceQueue = new ArrayDeque<>(mMaxInFlight);
mActiveRequests = new TreeSet<>();
mIsEmpty = mLock.newCondition();
mNotFull = mLock.newCondition();
mPreviewsEmpty = mLock.newCondition();
mDeviceState = deviceState;
}
/**
* Queue a new request.
*
* <p>
* For requests that use the Camera1 API preview output stream, this will block if there are
* already {@code maxInFlight} requests in progress (until at least one prior request has
* completed). For requests that use the Camera1 API jpeg callbacks, this will block until
* all prior requests have been completed to avoid stopping preview for
* {@link android.hardware.Camera#takePicture} before prior preview requests have been
* completed.
* </p>
* @param holder the {@link RequestHolder} for this request.
* @param legacy the {@link LegacyRequest} for this request; this will not be mutated.
* @param timeout a timeout to use for this call.
* @param unit the units to use for the timeout.
* @return {@code false} if this method timed out.
* @throws InterruptedException if this thread is interrupted.
*/
public boolean queueRequest(RequestHolder holder, LegacyRequest legacy, long timeout,
TimeUnit unit)
throws InterruptedException {
CaptureHolder h = new CaptureHolder(holder, legacy);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.mLock;
lock.lock();
try {
if (DEBUG) {
Log.d(TAG, "queueRequest for request " + holder.getRequestId() +
" - " + mInFlight + " requests remain in flight.");
}
if (!(h.needsJpeg || h.needsPreview)) {
throw new IllegalStateException("Request must target at least one output surface!");
}
if (h.needsJpeg) {
// Wait for all current requests to finish before queueing jpeg.
while (mInFlight > 0) {
if (nanos <= 0) {
return false;
}
nanos = mIsEmpty.awaitNanos(nanos);
}
mJpegCaptureQueue.add(h);
mJpegProduceQueue.add(h);
}
if (h.needsPreview) {
while (mInFlight >= mMaxInFlight) {
if (nanos <= 0) {
return false;
}
nanos = mNotFull.awaitNanos(nanos);
}
mPreviewCaptureQueue.add(h);
mPreviewProduceQueue.add(h);
mInFlightPreviews++;
}
mActiveRequests.add(h);
mInFlight++;
return true;
} finally {
lock.unlock();
}
}
/**
* Wait all queued requests to complete.
*
* @param timeout a timeout to use for this call.
* @param unit the units to use for the timeout.
* @return {@code false} if this method timed out.
* @throws InterruptedException if this thread is interrupted.
*/
public boolean waitForEmpty(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.mLock;
lock.lock();
try {
while (mInFlight > 0) {
if (nanos <= 0) {
return false;
}
nanos = mIsEmpty.awaitNanos(nanos);
}
return true;
} finally {
lock.unlock();
}
}
/**
* Wait all queued requests that use the Camera1 API preview output to complete.
*
* @param timeout a timeout to use for this call.
* @param unit the units to use for the timeout.
* @return {@code false} if this method timed out.
* @throws InterruptedException if this thread is interrupted.
*/
public boolean waitForPreviewsEmpty(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.mLock;
lock.lock();
try {
while (mInFlightPreviews > 0) {
if (nanos <= 0) {
return false;
}
nanos = mPreviewsEmpty.awaitNanos(nanos);
}
return true;
} finally {
lock.unlock();
}
}
/**
* Wait for the specified request to be completed (all buffers available).
*
* <p>May not wait for the same request more than once, since a successful wait
* will erase the history of that request.</p>
*
* @param holder the {@link RequestHolder} for this request.
* @param timeout a timeout to use for this call.
* @param unit the units to use for the timeout.
* @param timestamp the timestamp of the request will be written out to here, in ns
*
* @return {@code false} if this method timed out.
*
* @throws InterruptedException if this thread is interrupted.
*/
public boolean waitForRequestCompleted(RequestHolder holder, long timeout, TimeUnit unit,
MutableLong timestamp)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.mLock;
lock.lock();
try {
while (!removeRequestIfCompleted(holder, /*out*/timestamp)) {
if (nanos <= 0) {
return false;
}
nanos = mNotFull.awaitNanos(nanos);
}
return true;
} finally {
lock.unlock();
}
}
private boolean removeRequestIfCompleted(RequestHolder holder, MutableLong timestamp) {
int i = 0;
for (CaptureHolder h : mCompletedRequests) {
if (h.mRequest.equals(holder)) {
timestamp.value = h.mTimestamp;
mCompletedRequests.remove(i);
return true;
}
i++;
}
return false;
}
/**
* Called to alert the {@link CaptureCollector} that the jpeg capture has begun.
*
* @param timestamp the time of the jpeg capture.
* @return the {@link RequestHolder} for the request associated with this capture.
*/
public RequestHolder jpegCaptured(long timestamp) {
final ReentrantLock lock = this.mLock;
lock.lock();
try {
CaptureHolder h = mJpegCaptureQueue.poll();
if (h == null) {
Log.w(TAG, "jpegCaptured called with no jpeg request on queue!");
return null;
}
h.setJpegTimestamp(timestamp);
return h.mRequest;
} finally {
lock.unlock();
}
}
/**
* Called to alert the {@link CaptureCollector} that the jpeg capture has completed.
*
* @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
*/
public Pair<RequestHolder, Long> jpegProduced() {
final ReentrantLock lock = this.mLock;
lock.lock();
try {
CaptureHolder h = mJpegProduceQueue.poll();
if (h == null) {
Log.w(TAG, "jpegProduced called with no jpeg request on queue!");
return null;
}
h.setJpegProduced();
return new Pair<>(h.mRequest, h.mTimestamp);
} finally {
lock.unlock();
}
}
/**
* Check if there are any pending capture requests that use the Camera1 API preview output.
*
* @return {@code true} if there are pending preview requests.
*/
public boolean hasPendingPreviewCaptures() {
final ReentrantLock lock = this.mLock;
lock.lock();
try {
return !mPreviewCaptureQueue.isEmpty();
} finally {
lock.unlock();
}
}
/**
* Called to alert the {@link CaptureCollector} that the preview capture has begun.
*
* @param timestamp the time of the preview capture.
* @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
*/
public Pair<RequestHolder, Long> previewCaptured(long timestamp) {
final ReentrantLock lock = this.mLock;
lock.lock();
try {
CaptureHolder h = mPreviewCaptureQueue.poll();
if (h == null) {
if (DEBUG) {
Log.d(TAG, "previewCaptured called with no preview request on queue!");
}
return null;
}
h.setPreviewTimestamp(timestamp);
return new Pair<>(h.mRequest, h.mTimestamp);
} finally {
lock.unlock();
}
}
/**
* Called to alert the {@link CaptureCollector} that the preview capture has completed.
*
* @return the {@link RequestHolder} for the request associated with this capture.
*/
public RequestHolder previewProduced() {
final ReentrantLock lock = this.mLock;
lock.lock();
try {
CaptureHolder h = mPreviewProduceQueue.poll();
if (h == null) {
Log.w(TAG, "previewProduced called with no preview request on queue!");
return null;
}
h.setPreviewProduced();
return h.mRequest;
} finally {
lock.unlock();
}
}
/**
* Called to alert the {@link CaptureCollector} that the next pending preview capture has failed.
*/
public void failNextPreview() {
final ReentrantLock lock = this.mLock;
lock.lock();
try {
CaptureHolder h1 = mPreviewCaptureQueue.peek();
CaptureHolder h2 = mPreviewProduceQueue.peek();
// Find the request with the lowest frame number.
CaptureHolder h = (h1 == null) ? h2 :
((h2 == null) ? h1 :
((h1.compareTo(h2) <= 0) ? h1 :
h2));
if (h != null) {
mPreviewCaptureQueue.remove(h);
mPreviewProduceQueue.remove(h);
mActiveRequests.remove(h);
h.setPreviewFailed();
}
} finally {
lock.unlock();
}
}
/**
* Called to alert the {@link CaptureCollector} that the next pending jpeg capture has failed.
*/
public void failNextJpeg() {
final ReentrantLock lock = this.mLock;
lock.lock();
try {
CaptureHolder h1 = mJpegCaptureQueue.peek();
CaptureHolder h2 = mJpegProduceQueue.peek();
// Find the request with the lowest frame number.
CaptureHolder h = (h1 == null) ? h2 :
((h2 == null) ? h1 :
((h1.compareTo(h2) <= 0) ? h1 :
h2));
if (h != null) {
mJpegCaptureQueue.remove(h);
mJpegProduceQueue.remove(h);
mActiveRequests.remove(h);
h.setJpegFailed();
}
} finally {
lock.unlock();
}
}
/**
* Called to alert the {@link CaptureCollector} all pending captures have failed.
*/
public void failAll() {
final ReentrantLock lock = this.mLock;
lock.lock();
try {
CaptureHolder h;
while ((h = mActiveRequests.pollFirst()) != null) {
h.setPreviewFailed();
h.setJpegFailed();
}
mPreviewCaptureQueue.clear();
mPreviewProduceQueue.clear();
mJpegCaptureQueue.clear();
mJpegProduceQueue.clear();
} finally {
lock.unlock();
}
}
private void onPreviewCompleted() {
mInFlightPreviews--;
if (mInFlightPreviews < 0) {
throw new IllegalStateException(
"More preview captures completed than requests queued.");
}
if (mInFlightPreviews == 0) {
mPreviewsEmpty.signalAll();
}
}
private void onRequestCompleted(CaptureHolder capture) {
RequestHolder request = capture.mRequest;
mInFlight--;
if (DEBUG) {
Log.d(TAG, "Completed request " + request.getRequestId() +
", " + mInFlight + " requests remain in flight.");
}
if (mInFlight < 0) {
throw new IllegalStateException(
"More captures completed than requests queued.");
}
mCompletedRequests.add(capture);
mActiveRequests.remove(capture);
mNotFull.signalAll();
if (mInFlight == 0) {
mIsEmpty.signalAll();
}
}
}

View File

@@ -1,264 +0,0 @@
/*
* 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.SurfaceTexture;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.view.Surface;
import java.util.Collection;
import static com.android.internal.util.Preconditions.*;
/**
* GLThreadManager handles the thread used for rendering into the configured output surfaces.
*/
public class GLThreadManager {
private final String TAG;
private static final boolean DEBUG = false;
private static final int MSG_NEW_CONFIGURATION = 1;
private static final int MSG_NEW_FRAME = 2;
private static final int MSG_CLEANUP = 3;
private static final int MSG_DROP_FRAMES = 4;
private static final int MSG_ALLOW_FRAMES = 5;
private CaptureCollector mCaptureCollector;
private final CameraDeviceState mDeviceState;
private final SurfaceTextureRenderer mTextureRenderer;
private final RequestHandlerThread mGLHandlerThread;
private final RequestThreadManager.FpsCounter mPrevCounter =
new RequestThreadManager.FpsCounter("GL Preview Producer");
/**
* Container object for Configure messages.
*/
private static class ConfigureHolder {
public final ConditionVariable condition;
public final Collection<Pair<Surface, Size>> surfaces;
public final CaptureCollector collector;
public ConfigureHolder(ConditionVariable condition, Collection<Pair<Surface,
Size>> surfaces, CaptureCollector collector) {
this.condition = condition;
this.surfaces = surfaces;
this.collector = collector;
}
}
private final Handler.Callback mGLHandlerCb = new Handler.Callback() {
private boolean mCleanup = false;
private boolean mConfigured = false;
private boolean mDroppingFrames = false;
@SuppressWarnings("unchecked")
@Override
public boolean handleMessage(Message msg) {
if (mCleanup) {
return true;
}
try {
switch (msg.what) {
case MSG_NEW_CONFIGURATION:
ConfigureHolder configure = (ConfigureHolder) msg.obj;
mTextureRenderer.cleanupEGLContext();
mTextureRenderer.configureSurfaces(configure.surfaces);
mCaptureCollector = checkNotNull(configure.collector);
configure.condition.open();
mConfigured = true;
break;
case MSG_NEW_FRAME:
if (mDroppingFrames) {
Log.w(TAG, "Ignoring frame.");
break;
}
if (DEBUG) {
mPrevCounter.countAndLog();
}
if (!mConfigured) {
Log.e(TAG, "Dropping frame, EGL context not configured!");
}
mTextureRenderer.drawIntoSurfaces(mCaptureCollector);
break;
case MSG_CLEANUP:
mTextureRenderer.cleanupEGLContext();
mCleanup = true;
mConfigured = false;
break;
case MSG_DROP_FRAMES:
mDroppingFrames = true;
break;
case MSG_ALLOW_FRAMES:
mDroppingFrames = false;
break;
case RequestHandlerThread.MSG_POKE_IDLE_HANDLER:
// OK: Ignore message.
break;
default:
Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
break;
}
} catch (Exception e) {
Log.e(TAG, "Received exception on GL render thread: ", e);
mDeviceState.setError(CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
}
return true;
}
};
/**
* Create a new GL thread and renderer.
*
* @param cameraId the camera id for this thread.
* @param facing direction the camera is facing.
* @param state {@link CameraDeviceState} to use for error handling.
*/
public GLThreadManager(int cameraId, int facing, CameraDeviceState state) {
mTextureRenderer = new SurfaceTextureRenderer(facing);
TAG = String.format("CameraDeviceGLThread-%d", cameraId);
mGLHandlerThread = new RequestHandlerThread(TAG, mGLHandlerCb);
mDeviceState = state;
}
/**
* Start the thread.
*
* <p>
* This must be called before queueing new frames.
* </p>
*/
public void start() {
mGLHandlerThread.start();
}
/**
* Wait until the thread has started.
*/
public void waitUntilStarted() {
mGLHandlerThread.waitUntilStarted();
}
/**
* Quit the thread.
*
* <p>
* No further methods can be called after this.
* </p>
*/
public void quit() {
Handler handler = mGLHandlerThread.getHandler();
handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
mGLHandlerThread.quitSafely();
try {
mGLHandlerThread.join();
} catch (InterruptedException e) {
Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
mGLHandlerThread.getName(), mGLHandlerThread.getId()));
}
}
/**
* Queue a new call to draw into the surfaces specified in the next available preview
* request from the {@link CaptureCollector} passed to
* {@link #setConfigurationAndWait(java.util.Collection, CaptureCollector)};
*/
public void queueNewFrame() {
Handler handler = mGLHandlerThread.getHandler();
/**
* Avoid queuing more than one new frame. If we are not consuming faster than frames
* are produced, drop frames rather than allowing the queue to back up.
*/
if (!handler.hasMessages(MSG_NEW_FRAME)) {
handler.sendMessage(handler.obtainMessage(MSG_NEW_FRAME));
} else {
Log.e(TAG, "GLThread dropping frame. Not consuming frames quickly enough!");
}
}
/**
* Configure the GL renderer for the given set of output surfaces, and block until
* this configuration has been applied.
*
* @param surfaces a collection of pairs of {@link android.view.Surface}s and their
* corresponding sizes to configure.
* @param collector a {@link CaptureCollector} to retrieve requests from.
*/
public void setConfigurationAndWait(Collection<Pair<Surface, Size>> surfaces,
CaptureCollector collector) {
checkNotNull(collector, "collector must not be null");
Handler handler = mGLHandlerThread.getHandler();
final ConditionVariable condition = new ConditionVariable(/*closed*/false);
ConfigureHolder configure = new ConfigureHolder(condition, surfaces, collector);
Message m = handler.obtainMessage(MSG_NEW_CONFIGURATION, /*arg1*/0, /*arg2*/0, configure);
handler.sendMessage(m);
// Block until configuration applied.
condition.block();
}
/**
* Get the underlying surface to produce frames from.
*
* <p>
* This returns the surface that is drawn into the set of surfaces passed in for each frame.
* This method should only be called after a call to
* {@link #setConfigurationAndWait(java.util.Collection)}. Calling this before the first call
* to {@link #setConfigurationAndWait(java.util.Collection)}, after {@link #quit()}, or
* concurrently to one of these calls may result in an invalid
* {@link android.graphics.SurfaceTexture} being returned.
* </p>
*
* @return an {@link android.graphics.SurfaceTexture} to draw to.
*/
public SurfaceTexture getCurrentSurfaceTexture() {
return mTextureRenderer.getSurfaceTexture();
}
/**
* Ignore any subsequent calls to {@link #queueNewFrame(java.util.Collection)}.
*/
public void ignoreNewFrames() {
mGLHandlerThread.getHandler().sendEmptyMessage(MSG_DROP_FRAMES);
}
/**
* Wait until no messages are queued.
*/
public void waitUntilIdle() {
mGLHandlerThread.waitUntilIdle();
}
/**
* Re-enable drawing new frames after a call to {@link #ignoreNewFrames()}.
*/
public void allowNewFrames() {
mGLHandlerThread.getHandler().sendEmptyMessage(MSG_ALLOW_FRAMES);
}
}

View File

@@ -1,886 +0,0 @@
/*
* 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.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.ArrayUtils;
import android.hardware.camera2.utils.SubmitInfo;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.util.SparseArray;
import android.view.Surface;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static android.hardware.camera2.legacy.LegacyExceptionUtils.*;
import static com.android.internal.util.Preconditions.*;
/**
* 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 {
private final String TAG;
private static final boolean DEBUG = false;
private final int mCameraId;
private final CameraCharacteristics mStaticCharacteristics;
private final ICameraDeviceCallbacks mDeviceCallbacks;
private final CameraDeviceState mDeviceState = new CameraDeviceState();
private SparseArray<Surface> mConfiguredSurfaces;
private boolean mClosed = false;
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;
// Keep up to date with values in hardware/libhardware/include/hardware/gralloc.h
private static final int GRALLOC_USAGE_RENDERSCRIPT = 0x00100000;
private static final int GRALLOC_USAGE_SW_READ_OFTEN = 0x00000003;
private static final int GRALLOC_USAGE_HW_TEXTURE = 0x00000100;
private static final int GRALLOC_USAGE_HW_COMPOSER = 0x00000800;
private static final int GRALLOC_USAGE_HW_RENDER = 0x00000200;
private static final int GRALLOC_USAGE_HW_VIDEO_ENCODER = 0x00010000;
public static final int MAX_DIMEN_FOR_ROUNDING = 1920; // maximum allowed width for rounding
// Keep up to date with values in system/core/include/system/window.h
public static final int NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW = 1;
private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
return getExtrasFromRequest(holder,
/*errorCode*/CameraDeviceState.NO_CAPTURE_ERROR, /*errorArg*/null);
}
private CaptureResultExtras getExtrasFromRequest(RequestHolder holder,
int errorCode, Object errorArg) {
int errorStreamId = -1;
if (errorCode == CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_BUFFER) {
Surface errorTarget = (Surface) errorArg;
int indexOfTarget = mConfiguredSurfaces.indexOfValue(errorTarget);
if (indexOfTarget < 0) {
Log.e(TAG, "Buffer drop error reported for unknown Surface");
} else {
errorStreamId = mConfiguredSurfaces.keyAt(indexOfTarget);
}
}
if (holder == null) {
return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null,
ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE);
}
return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
/*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(),
/*partialResultCount*/1, errorStreamId, null, holder.getFrameNumber(), -1, -1);
}
/**
* 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, final Object errorArg, final RequestHolder holder) {
if (DEBUG) {
Log.d(TAG, "onError called, errorCode = " + errorCode + ", errorArg = " + errorArg);
}
switch (errorCode) {
/*
* Only be considered idle if we hit a fatal error
* and no further requests can be processed.
*/
case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DISCONNECTED:
case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_SERVICE:
case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE: {
mIdle.open();
if (DEBUG) {
Log.d(TAG, "onError - opening idle");
}
}
}
final CaptureResultExtras extras = getExtrasFromRequest(holder, errorCode, errorArg);
mResultHandler.post(new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.d(TAG, "doing onError callback for request " + holder.getRequestId() +
", with error code " + errorCode);
}
try {
mDeviceCallbacks.onDeviceError(errorCode, extras);
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onCameraError callback: ", e);
}
}
});
}
@Override
public void onConfiguring() {
// Do nothing
if (DEBUG) {
Log.d(TAG, "doing onConfiguring callback.");
}
}
@Override
public void onIdle() {
if (DEBUG) {
Log.d(TAG, "onIdle called");
}
mIdle.open();
mResultHandler.post(new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.d(TAG, "doing onIdle callback.");
}
try {
mDeviceCallbacks.onDeviceIdle();
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onCameraIdle callback: ", e);
}
}
});
}
@Override
public void onBusy() {
mIdle.close();
if (DEBUG) {
Log.d(TAG, "onBusy called");
}
}
@Override
public void onCaptureStarted(final RequestHolder holder, final long timestamp) {
final CaptureResultExtras extras = getExtrasFromRequest(holder);
mResultHandler.post(new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.d(TAG, "doing onCaptureStarted callback for request " +
holder.getRequestId());
}
try {
mDeviceCallbacks.onCaptureStarted(extras, timestamp);
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onCameraError callback: ", e);
}
}
});
}
@Override
public void onRequestQueueEmpty() {
mResultHandler.post(new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.d(TAG, "doing onRequestQueueEmpty callback");
}
try {
mDeviceCallbacks.onRequestQueueEmpty();
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onRequestQueueEmpty callback: ",
e);
}
}
});
}
@Override
public void onCaptureResult(final CameraMetadataNative result, final RequestHolder holder) {
final CaptureResultExtras extras = getExtrasFromRequest(holder);
mResultHandler.post(new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.d(TAG, "doing onCaptureResult callback for request " +
holder.getRequestId());
}
try {
mDeviceCallbacks.onResultReceived(result, extras,
new PhysicalCaptureResultInfo[0]);
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onCameraError callback: ", e);
}
}
});
}
@Override
public void onRepeatingRequestError(final long lastFrameNumber,
final int repeatingRequestId) {
mResultHandler.post(new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.d(TAG, "doing onRepeatingRequestError callback.");
}
try {
mDeviceCallbacks.onRepeatingRequestError(lastFrameNumber,
repeatingRequestId);
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onRepeatingRequestError " +
"callback: ", e);
}
}
});
}
};
private final RequestThreadManager mRequestThreadManager;
/**
* Check if a given surface uses {@link ImageFormat#YUV_420_888} or format that can be readily
* converted to this; YV12 and NV21 are the two currently supported formats.
*
* @param s the surface to check.
* @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888} or a compatible
* format.
*/
static boolean needsConversion(Surface s) throws BufferQueueAbandonedException {
int nativeType = detectSurfaceType(s);
return nativeType == ImageFormat.YUV_420_888 || nativeType == ImageFormat.YV12 ||
nativeType == ImageFormat.NV21;
}
/**
* 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 characteristics the static camera characteristics for this camera device
* @param callbacks {@link ICameraDeviceCallbacks} callbacks to call for Camera2 API operations.
*/
public LegacyCameraDevice(int cameraId, Camera camera, CameraCharacteristics characteristics,
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);
mStaticCharacteristics = characteristics;
mRequestThreadManager =
new RequestThreadManager(cameraId, camera, characteristics, mDeviceState);
mRequestThreadManager.start();
}
/**
* Configure the device with a set of output surfaces.
*
* <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
*
* <p>Every surface in {@code outputs} must be non-{@code null}.</p>
*
* @param outputs a list of surfaces to set. LegacyCameraDevice will take ownership of this
* list; it must not be modified by the caller once it's passed in.
* @return an error code for this binder operation, or {@link NO_ERROR}
* on success.
*/
public int configureOutputs(SparseArray<Surface> outputs) {
return configureOutputs(outputs, /*validateSurfacesOnly*/false);
}
/**
* Configure the device with a set of output surfaces.
*
* <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
*
* <p>Every surface in {@code outputs} must be non-{@code null}.</p>
*
* @param outputs a list of surfaces to set. LegacyCameraDevice will take ownership of this
* list; it must not be modified by the caller once it's passed in.
* @param validateSurfacesOnly If set it will only check whether the outputs are supported
* and avoid any device configuration.
* @return an error code for this binder operation, or {@link NO_ERROR}
* on success.
* @hide
*/
public int configureOutputs(SparseArray<Surface> outputs, boolean validateSurfacesOnly) {
List<Pair<Surface, Size>> sizedSurfaces = new ArrayList<>();
if (outputs != null) {
int count = outputs.size();
for (int i = 0; i < count; i++) {
Surface output = outputs.valueAt(i);
if (output == null) {
Log.e(TAG, "configureOutputs - null outputs are not allowed");
return BAD_VALUE;
}
if (!output.isValid()) {
Log.e(TAG, "configureOutputs - invalid output surfaces are not allowed");
return BAD_VALUE;
}
StreamConfigurationMap streamConfigurations = mStaticCharacteristics.
get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
// Validate surface size and format.
try {
Size s = getSurfaceSize(output);
int surfaceType = detectSurfaceType(output);
boolean flexibleConsumer = isFlexibleConsumer(output);
Size[] sizes = streamConfigurations.getOutputSizes(surfaceType);
if (sizes == null) {
if (surfaceType == ImageFormat.PRIVATE) {
// YUV_420_888 is always present in LEGACY for all
// IMPLEMENTATION_DEFINED output sizes, and is publicly visible in the
// API (i.e. {@code #getOutputSizes} works here).
sizes = streamConfigurations.getOutputSizes(ImageFormat.YUV_420_888);
} else if (surfaceType == LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB) {
sizes = streamConfigurations.getOutputSizes(ImageFormat.JPEG);
}
}
if (!ArrayUtils.contains(sizes, s)) {
if (flexibleConsumer && (s = findClosestSize(s, sizes)) != null) {
sizedSurfaces.add(new Pair<>(output, s));
} else {
String reason = (sizes == null) ? "format is invalid." :
("size not in valid set: " + Arrays.toString(sizes));
Log.e(TAG, String.format("Surface with size (w=%d, h=%d) and format " +
"0x%x is not valid, %s", s.getWidth(), s.getHeight(),
surfaceType, reason));
return BAD_VALUE;
}
} else {
sizedSurfaces.add(new Pair<>(output, s));
}
// Lock down the size before configuration
if (!validateSurfacesOnly) {
setSurfaceDimens(output, s.getWidth(), s.getHeight());
}
} catch (BufferQueueAbandonedException e) {
Log.e(TAG, "Surface bufferqueue is abandoned, cannot configure as output: ", e);
return BAD_VALUE;
}
}
}
if (validateSurfacesOnly) {
return LegacyExceptionUtils.NO_ERROR;
}
boolean success = false;
if (mDeviceState.setConfiguring()) {
mRequestThreadManager.configure(sizedSurfaces);
success = mDeviceState.setIdle();
}
if (success) {
mConfiguredSurfaces = outputs;
} else {
return LegacyExceptionUtils.INVALID_OPERATION;
}
return LegacyExceptionUtils.NO_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.
* @return the submission info, including the new request id, and the last frame number, which
* 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.
*/
public SubmitInfo submitRequestList(CaptureRequest[] requestList, boolean repeating) {
if (requestList == null || requestList.length == 0) {
Log.e(TAG, "submitRequestList - Empty/null requests are not allowed");
throw new ServiceSpecificException(BAD_VALUE,
"submitRequestList - Empty/null requests are not allowed");
}
List<Long> surfaceIds;
try {
surfaceIds = (mConfiguredSurfaces == null) ? new ArrayList<Long>() :
getSurfaceIds(mConfiguredSurfaces);
} catch (BufferQueueAbandonedException e) {
throw new ServiceSpecificException(BAD_VALUE,
"submitRequestList - configured surface is abandoned.");
}
// Make sure that there all requests have at least 1 surface; all surfaces are non-null
for (CaptureRequest request : requestList) {
if (request.getTargets().isEmpty()) {
Log.e(TAG, "submitRequestList - "
+ "Each request must have at least one Surface target");
throw new ServiceSpecificException(BAD_VALUE,
"submitRequestList - "
+ "Each request must have at least one Surface target");
}
for (Surface surface : request.getTargets()) {
if (surface == null) {
Log.e(TAG, "submitRequestList - Null Surface targets are not allowed");
throw new ServiceSpecificException(BAD_VALUE,
"submitRequestList - Null Surface targets are not allowed");
} else if (mConfiguredSurfaces == null) {
Log.e(TAG, "submitRequestList - must configure " +
" device with valid surfaces before submitting requests");
throw new ServiceSpecificException(INVALID_OPERATION,
"submitRequestList - must configure " +
" device with valid surfaces before submitting requests");
} else if (!containsSurfaceId(surface, surfaceIds)) {
Log.e(TAG, "submitRequestList - cannot use a surface that wasn't configured");
throw new ServiceSpecificException(BAD_VALUE,
"submitRequestList - cannot use a surface that wasn't configured");
}
}
}
// TODO: further validation of request here
mIdle.close();
return mRequestThreadManager.submitCaptureRequests(requestList, repeating);
}
/**
* Submit a single capture request.
*
* @param request the capture request to execute.
* @param repeating {@code true} if this request is repeating.
* @return the submission info, including the new request id, and the last frame number, which
* 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.
*/
public SubmitInfo submitRequest(CaptureRequest request, boolean repeating) {
CaptureRequest[] requestList = { request };
return submitRequestList(requestList, repeating);
}
/**
* 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();
}
/**
* Flush any pending requests.
*
* @return the last frame number.
*/
public long flush() {
long lastFrame = mRequestThreadManager.flush();
waitUntilIdle();
return lastFrame;
}
public void setAudioRestriction(int mode) {
mRequestThreadManager.setAudioRestriction(mode);
}
public int getAudioRestriction() {
return mRequestThreadManager.getAudioRestriction();
}
/**
* Return {@code true} if the device has been closed.
*/
public boolean isClosed() {
return mClosed;
}
@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()));
}
mClosed = true;
}
@Override
protected void finalize() throws Throwable {
try {
close();
} catch (ServiceSpecificException e) {
Log.e(TAG, "Got error while trying to finalize, ignoring: " + e.getMessage());
} finally {
super.finalize();
}
}
static long findEuclidDistSquare(Size a, Size b) {
long d0 = a.getWidth() - b.getWidth();
long d1 = a.getHeight() - b.getHeight();
return d0 * d0 + d1 * d1;
}
// Keep up to date with rounding behavior in
// frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
static Size findClosestSize(Size size, Size[] supportedSizes) {
if (size == null || supportedSizes == null) {
return null;
}
Size bestSize = null;
for (Size s : supportedSizes) {
if (s.equals(size)) {
return size;
} else if (s.getWidth() <= MAX_DIMEN_FOR_ROUNDING && (bestSize == null ||
LegacyCameraDevice.findEuclidDistSquare(size, s) <
LegacyCameraDevice.findEuclidDistSquare(bestSize, s))) {
bestSize = s;
}
}
return bestSize;
}
/**
* Query the surface for its currently configured default buffer size.
* @param surface a non-{@code null} {@code Surface}
* @return the width and height of the surface
*
* @throws NullPointerException if the {@code surface} was {@code null}
* @throws BufferQueueAbandonedException if the {@code surface} was invalid
*/
public static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException {
checkNotNull(surface);
int[] dimens = new int[2];
LegacyExceptionUtils.throwOnError(nativeDetectSurfaceDimens(surface, /*out*/dimens));
return new Size(dimens[0], dimens[1]);
}
public static boolean isFlexibleConsumer(Surface output) {
int usageFlags = detectSurfaceUsageFlags(output);
// Keep up to date with allowed consumer types in
// frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
int disallowedFlags = GRALLOC_USAGE_HW_VIDEO_ENCODER | GRALLOC_USAGE_RENDERSCRIPT;
int allowedFlags = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_OFTEN |
GRALLOC_USAGE_HW_COMPOSER;
boolean flexibleConsumer = ((usageFlags & disallowedFlags) == 0 &&
(usageFlags & allowedFlags) != 0);
return flexibleConsumer;
}
public static boolean isPreviewConsumer(Surface output) {
int usageFlags = detectSurfaceUsageFlags(output);
int disallowedFlags = GRALLOC_USAGE_HW_VIDEO_ENCODER | GRALLOC_USAGE_RENDERSCRIPT |
GRALLOC_USAGE_SW_READ_OFTEN;
int allowedFlags = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_COMPOSER |
GRALLOC_USAGE_HW_RENDER;
boolean previewConsumer = ((usageFlags & disallowedFlags) == 0 &&
(usageFlags & allowedFlags) != 0);
int surfaceFormat = ImageFormat.UNKNOWN;
try {
surfaceFormat = detectSurfaceType(output);
} catch(BufferQueueAbandonedException e) {
throw new IllegalArgumentException("Surface was abandoned", e);
}
return previewConsumer;
}
public static boolean isVideoEncoderConsumer(Surface output) {
int usageFlags = detectSurfaceUsageFlags(output);
int disallowedFlags = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_COMPOSER |
GRALLOC_USAGE_RENDERSCRIPT | GRALLOC_USAGE_SW_READ_OFTEN;
int allowedFlags = GRALLOC_USAGE_HW_VIDEO_ENCODER;
boolean videoEncoderConsumer = ((usageFlags & disallowedFlags) == 0 &&
(usageFlags & allowedFlags) != 0);
int surfaceFormat = ImageFormat.UNKNOWN;
try {
surfaceFormat = detectSurfaceType(output);
} catch(BufferQueueAbandonedException e) {
throw new IllegalArgumentException("Surface was abandoned", e);
}
return videoEncoderConsumer;
}
/**
* Query the surface for its currently configured usage flags
*/
static int detectSurfaceUsageFlags(Surface surface) {
checkNotNull(surface);
return nativeDetectSurfaceUsageFlags(surface);
}
/**
* Query the surface for its currently configured format
*/
public static int detectSurfaceType(Surface surface) throws BufferQueueAbandonedException {
checkNotNull(surface);
int surfaceType = nativeDetectSurfaceType(surface);
// TODO: remove this override since the default format should be
// ImageFormat.PRIVATE. b/9487482
if ((surfaceType >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
surfaceType <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
surfaceType = ImageFormat.PRIVATE;
}
return LegacyExceptionUtils.throwOnError(surfaceType);
}
/**
* Query the surface for its currently configured dataspace
*/
public static int detectSurfaceDataspace(Surface surface) throws BufferQueueAbandonedException {
checkNotNull(surface);
return LegacyExceptionUtils.throwOnError(nativeDetectSurfaceDataspace(surface));
}
static void connectSurface(Surface surface) throws BufferQueueAbandonedException {
checkNotNull(surface);
LegacyExceptionUtils.throwOnError(nativeConnectSurface(surface));
}
static void disconnectSurface(Surface surface) throws BufferQueueAbandonedException {
if (surface == null) return;
LegacyExceptionUtils.throwOnError(nativeDisconnectSurface(surface));
}
static void produceFrame(Surface surface, byte[] pixelBuffer, int width,
int height, int pixelFormat)
throws BufferQueueAbandonedException {
checkNotNull(surface);
checkNotNull(pixelBuffer);
checkArgumentPositive(width, "width must be positive.");
checkArgumentPositive(height, "height must be positive.");
LegacyExceptionUtils.throwOnError(nativeProduceFrame(surface, pixelBuffer, width, height,
pixelFormat));
}
static void setSurfaceFormat(Surface surface, int pixelFormat)
throws BufferQueueAbandonedException {
checkNotNull(surface);
LegacyExceptionUtils.throwOnError(nativeSetSurfaceFormat(surface, pixelFormat));
}
static void setSurfaceDimens(Surface surface, int width, int height)
throws BufferQueueAbandonedException {
checkNotNull(surface);
checkArgumentPositive(width, "width must be positive.");
checkArgumentPositive(height, "height must be positive.");
LegacyExceptionUtils.throwOnError(nativeSetSurfaceDimens(surface, width, height));
}
public static long getSurfaceId(Surface surface) throws BufferQueueAbandonedException {
checkNotNull(surface);
try {
return nativeGetSurfaceId(surface);
} catch (IllegalArgumentException e) {
throw new BufferQueueAbandonedException();
}
}
static List<Long> getSurfaceIds(SparseArray<Surface> surfaces)
throws BufferQueueAbandonedException {
if (surfaces == null) {
throw new NullPointerException("Null argument surfaces");
}
List<Long> surfaceIds = new ArrayList<>();
int count = surfaces.size();
for (int i = 0; i < count; i++) {
long id = getSurfaceId(surfaces.valueAt(i));
if (id == 0) {
throw new IllegalStateException(
"Configured surface had null native GraphicBufferProducer pointer!");
}
surfaceIds.add(id);
}
return surfaceIds;
}
static List<Long> getSurfaceIds(Collection<Surface> surfaces)
throws BufferQueueAbandonedException {
if (surfaces == null) {
throw new NullPointerException("Null argument surfaces");
}
List<Long> surfaceIds = new ArrayList<>();
for (Surface s : surfaces) {
long id = getSurfaceId(s);
if (id == 0) {
throw new IllegalStateException(
"Configured surface had null native GraphicBufferProducer pointer!");
}
surfaceIds.add(id);
}
return surfaceIds;
}
static boolean containsSurfaceId(Surface s, Collection<Long> ids) {
long id = 0;
try {
id = getSurfaceId(s);
} catch (BufferQueueAbandonedException e) {
// If surface is abandoned, return false.
return false;
}
return ids.contains(id);
}
static void setSurfaceOrientation(Surface surface, int facing, int sensorOrientation)
throws BufferQueueAbandonedException {
checkNotNull(surface);
LegacyExceptionUtils.throwOnError(nativeSetSurfaceOrientation(surface, facing,
sensorOrientation));
}
static Size getTextureSize(SurfaceTexture surfaceTexture)
throws BufferQueueAbandonedException {
checkNotNull(surfaceTexture);
int[] dimens = new int[2];
LegacyExceptionUtils.throwOnError(nativeDetectTextureDimens(surfaceTexture,
/*out*/dimens));
return new Size(dimens[0], dimens[1]);
}
static void setNextTimestamp(Surface surface, long timestamp)
throws BufferQueueAbandonedException {
checkNotNull(surface);
LegacyExceptionUtils.throwOnError(nativeSetNextTimestamp(surface, timestamp));
}
static void setScalingMode(Surface surface, int mode)
throws BufferQueueAbandonedException {
checkNotNull(surface);
LegacyExceptionUtils.throwOnError(nativeSetScalingMode(surface, mode));
}
private static native int nativeDetectSurfaceType(Surface surface);
private static native int nativeDetectSurfaceDataspace(Surface surface);
private static native int nativeDetectSurfaceDimens(Surface surface,
/*out*/int[/*2*/] dimens);
private static native int nativeConnectSurface(Surface surface);
private static native int nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
int height, int pixelFormat);
private static native int nativeSetSurfaceFormat(Surface surface, int pixelFormat);
private static native int nativeSetSurfaceDimens(Surface surface, int width, int height);
private static native long nativeGetSurfaceId(Surface surface);
private static native int nativeSetSurfaceOrientation(Surface surface, int facing,
int sensorOrientation);
private static native int nativeDetectTextureDimens(SurfaceTexture surfaceTexture,
/*out*/int[/*2*/] dimens);
private static native int nativeSetNextTimestamp(Surface surface, long timestamp);
private static native int nativeDetectSurfaceUsageFlags(Surface surface);
private static native int nativeSetScalingMode(Surface surface, int scalingMode);
private static native int nativeDisconnectSurface(Surface surface);
static native int nativeGetJpegFooterSize();
}

View File

@@ -1,138 +0,0 @@
/*
* 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.hardware.ICameraService;
import android.os.ServiceSpecificException;
import android.util.AndroidException;
import static android.system.OsConstants.*;
/**
* Utility class containing exception handling used solely by the compatibility mode shim.
*/
public class LegacyExceptionUtils {
private static final String TAG = "LegacyExceptionUtils";
public static final int NO_ERROR = 0;
public static final int PERMISSION_DENIED = -EPERM;
public static final int ALREADY_EXISTS = -EEXIST;
public static final int BAD_VALUE = -EINVAL;
public static final int DEAD_OBJECT = -ENOSYS;
public static final int INVALID_OPERATION = -EPIPE;
public static final int TIMED_OUT = -ETIMEDOUT;
/**
* Checked exception thrown when a BufferQueue has been abandoned by its consumer.
*/
public static class BufferQueueAbandonedException extends AndroidException {
public BufferQueueAbandonedException () {}
public BufferQueueAbandonedException(String name) {
super(name);
}
public BufferQueueAbandonedException(String name, Throwable cause) {
super(name, cause);
}
public BufferQueueAbandonedException(Exception cause) {
super(cause);
}
}
/**
* Throw error codes used by legacy device methods as exceptions.
*
* <p>Non-negative return values are passed through, negative return values are thrown as
* exceptions.</p>
*
* @param errorFlag error to throw as an exception.
* @throws {@link BufferQueueAbandonedException} for BAD_VALUE.
* @throws {@link UnsupportedOperationException} for an unknown negative error code.
* @return {@code errorFlag} if the value was non-negative, throws otherwise.
*/
public static int throwOnError(int errorFlag) throws BufferQueueAbandonedException {
if (errorFlag == NO_ERROR) {
return NO_ERROR;
} else if (errorFlag == BAD_VALUE) {
throw new BufferQueueAbandonedException();
}
if (errorFlag < 0) {
throw new UnsupportedOperationException("Unknown error " + errorFlag);
}
return errorFlag;
}
/**
* Throw error codes returned by the camera service as exceptions.
*
* @param errorFlag error to throw as an exception.
*/
public static void throwOnServiceError(int errorFlag) {
int errorCode = ICameraService.ERROR_INVALID_OPERATION;
String errorMsg;
if (errorFlag >= NO_ERROR) {
return;
} else if (errorFlag == PERMISSION_DENIED) {
errorCode = ICameraService.ERROR_PERMISSION_DENIED;
errorMsg = "Lacking privileges to access camera service";
} else if (errorFlag == ALREADY_EXISTS) {
// This should be handled at the call site. Typically this isn't bad,
// just means we tried to do an operation that already completed.
return;
} else if (errorFlag == BAD_VALUE) {
errorCode = ICameraService.ERROR_ILLEGAL_ARGUMENT;
errorMsg = "Bad argument passed to camera service";
} else if (errorFlag == DEAD_OBJECT) {
errorCode = ICameraService.ERROR_DISCONNECTED;
errorMsg = "Camera service not available";
} else if (errorFlag == TIMED_OUT) {
errorCode = ICameraService.ERROR_INVALID_OPERATION;
errorMsg = "Operation timed out in camera service";
} else if (errorFlag == -EACCES) {
errorCode = ICameraService.ERROR_DISABLED;
errorMsg = "Camera disabled by policy";
} else if (errorFlag == -EBUSY) {
errorCode = ICameraService.ERROR_CAMERA_IN_USE;
errorMsg = "Camera already in use";
} else if (errorFlag == -EUSERS) {
errorCode = ICameraService.ERROR_MAX_CAMERAS_IN_USE;
errorMsg = "Maximum number of cameras in use";
} else if (errorFlag == -ENODEV) {
errorCode = ICameraService.ERROR_DISCONNECTED;
errorMsg = "Camera device not available";
} else if (errorFlag == -EOPNOTSUPP) {
errorCode = ICameraService.ERROR_DEPRECATED_HAL;
errorMsg = "Deprecated camera HAL does not support this";
} else if (errorFlag == INVALID_OPERATION) {
errorCode = ICameraService.ERROR_INVALID_OPERATION;
errorMsg = "Illegal state encountered in camera service.";
} else {
errorCode = ICameraService.ERROR_INVALID_OPERATION;
errorMsg = "Unknown camera device error " + errorFlag;
}
throw new ServiceSpecificException(errorCode, errorMsg);
}
private LegacyExceptionUtils() {
throw new AssertionError();
}
}

View File

@@ -1,265 +0,0 @@
/*
* 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.Rect;
import android.hardware.Camera;
import android.hardware.Camera.FaceDetectionListener;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.utils.ListUtils;
import android.hardware.camera2.utils.ParamsUtils;
import android.util.Log;
import android.util.Size;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
import java.util.List;
import static android.hardware.camera2.CaptureRequest.*;
import static com.android.internal.util.Preconditions.*;
/**
* Map legacy face detect callbacks into face detection results.
*/
@SuppressWarnings("deprecation")
public class LegacyFaceDetectMapper {
private static String TAG = "LegacyFaceDetectMapper";
private static final boolean DEBUG = false;
private final Camera mCamera;
/** Is the camera capable of face detection? */
private final boolean mFaceDetectSupported;
/** Is the camera is running face detection? */
private boolean mFaceDetectEnabled = false;
/** Did the last request say to use SCENE_MODE = FACE_PRIORITY? */
private boolean mFaceDetectScenePriority = false;
/** Did the last request enable the face detect mode to ON? */
private boolean mFaceDetectReporting = false;
/** Synchronize access to all fields */
private final Object mLock = new Object();
private Camera.Face[] mFaces;
private Camera.Face[] mFacesPrev;
/**
* Instantiate a new face detect mapper.
*
* @param camera a non-{@code null} camera1 device
* @param characteristics a non-{@code null} camera characteristics for that camera1
*
* @throws NullPointerException if any of the args were {@code null}
*/
public LegacyFaceDetectMapper(Camera camera, CameraCharacteristics characteristics) {
mCamera = checkNotNull(camera, "camera must not be null");
checkNotNull(characteristics, "characteristics must not be null");
mFaceDetectSupported = ArrayUtils.contains(
characteristics.get(
CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES),
STATISTICS_FACE_DETECT_MODE_SIMPLE);
if (!mFaceDetectSupported) {
return;
}
mCamera.setFaceDetectionListener(new FaceDetectionListener() {
@Override
public void onFaceDetection(Camera.Face[] faces, Camera camera) {
int lengthFaces = faces == null ? 0 : faces.length;
synchronized (mLock) {
if (mFaceDetectEnabled) {
mFaces = faces;
} else if (lengthFaces > 0) {
// stopFaceDetectMode could race against the requests, print a debug log
Log.d(TAG,
"onFaceDetection - Ignored some incoming faces since" +
"face detection was disabled");
}
}
if (DEBUG) {
Log.v(TAG, "onFaceDetection - read " + lengthFaces + " faces");
}
}
});
}
/**
* Process the face detect mode from the capture request into an api1 face detect toggle.
*
* <p>This method should be called after the parameters are {@link LegacyRequestMapper mapped}
* with the request.</p>
*
* <p>Callbacks are processed in the background, and the next call to {@link #mapResultTriggers}
* will have the latest faces detected as reflected by the camera1 callbacks.</p>
*
* <p>None of the arguments will be mutated.</p>
*
* @param captureRequest a non-{@code null} request
* @param parameters a non-{@code null} parameters corresponding to this request (read-only)
*/
public void processFaceDetectMode(CaptureRequest captureRequest,
Camera.Parameters parameters) {
checkNotNull(captureRequest, "captureRequest must not be null");
/*
* statistics.faceDetectMode
*/
int fdMode = ParamsUtils.getOrDefault(captureRequest, STATISTICS_FACE_DETECT_MODE,
STATISTICS_FACE_DETECT_MODE_OFF);
if (fdMode != STATISTICS_FACE_DETECT_MODE_OFF && !mFaceDetectSupported) {
Log.w(TAG,
"processFaceDetectMode - Ignoring statistics.faceDetectMode; " +
"face detection is not available");
return;
}
/*
* control.sceneMode
*/
int sceneMode = ParamsUtils.getOrDefault(captureRequest, CONTROL_SCENE_MODE,
CONTROL_SCENE_MODE_DISABLED);
if (sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY && !mFaceDetectSupported) {
Log.w(TAG, "processFaceDetectMode - ignoring control.sceneMode == FACE_PRIORITY; " +
"face detection is not available");
return;
}
// Print some warnings out in case the values were wrong
switch (fdMode) {
case STATISTICS_FACE_DETECT_MODE_OFF:
case STATISTICS_FACE_DETECT_MODE_SIMPLE:
break;
case STATISTICS_FACE_DETECT_MODE_FULL:
Log.w(TAG,
"processFaceDetectMode - statistics.faceDetectMode == FULL unsupported, " +
"downgrading to SIMPLE");
break;
default:
Log.w(TAG, "processFaceDetectMode - ignoring unknown statistics.faceDetectMode = "
+ fdMode);
return;
}
boolean enableFaceDetect = (fdMode != STATISTICS_FACE_DETECT_MODE_OFF)
|| (sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY);
synchronized (mLock) {
// Enable/disable face detection if it's changed since last time
if (enableFaceDetect != mFaceDetectEnabled) {
if (enableFaceDetect) {
mCamera.startFaceDetection();
if (DEBUG) {
Log.v(TAG, "processFaceDetectMode - start face detection");
}
} else {
mCamera.stopFaceDetection();
if (DEBUG) {
Log.v(TAG, "processFaceDetectMode - stop face detection");
}
mFaces = null;
}
mFaceDetectEnabled = enableFaceDetect;
mFaceDetectScenePriority = sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY;
mFaceDetectReporting = fdMode != STATISTICS_FACE_DETECT_MODE_OFF;
}
}
}
/**
* Update the {@code result} camera metadata map with the new value for the
* {@code statistics.faces} and {@code statistics.faceDetectMode}.
*
* <p>Face detect callbacks are processed in the background, and each call to
* {@link #mapResultFaces} will have the latest faces as reflected by the camera1 callbacks.</p>
*
* <p>If the scene mode was set to {@code FACE_PRIORITY} but face detection is disabled,
* the camera will still run face detection in the background, but no faces will be reported
* in the capture result.</p>
*
* @param result a non-{@code null} result
* @param legacyRequest a non-{@code null} request (read-only)
*/
public void mapResultFaces(CameraMetadataNative result, LegacyRequest legacyRequest) {
checkNotNull(result, "result must not be null");
checkNotNull(legacyRequest, "legacyRequest must not be null");
Camera.Face[] faces, previousFaces;
int fdMode;
boolean fdScenePriority;
synchronized (mLock) {
fdMode = mFaceDetectReporting ?
STATISTICS_FACE_DETECT_MODE_SIMPLE : STATISTICS_FACE_DETECT_MODE_OFF;
if (mFaceDetectReporting) {
faces = mFaces;
} else {
faces = null;
}
fdScenePriority = mFaceDetectScenePriority;
previousFaces = mFacesPrev;
mFacesPrev = faces;
}
CameraCharacteristics characteristics = legacyRequest.characteristics;
CaptureRequest request = legacyRequest.captureRequest;
Size previewSize = legacyRequest.previewSize;
Camera.Parameters params = legacyRequest.parameters;
Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
ZoomData zoomData = ParameterUtils.convertToLegacyZoom(activeArray,
request.get(CaptureRequest.SCALER_CROP_REGION),
request.get(CaptureRequest.CONTROL_ZOOM_RATIO),
previewSize, params);
List<Face> convertedFaces = new ArrayList<>();
if (faces != null) {
for (Camera.Face face : faces) {
if (face != null) {
convertedFaces.add(
ParameterUtils.convertFaceFromLegacy(face, activeArray, zoomData));
} else {
Log.w(TAG, "mapResultFaces - read NULL face from camera1 device");
}
}
}
if (DEBUG && previousFaces != faces) { // Log only in verbose and IF the faces changed
Log.v(TAG, "mapResultFaces - changed to " + ListUtils.listToString(convertedFaces));
}
result.set(CaptureResult.STATISTICS_FACES, convertedFaces.toArray(new Face[0]));
result.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, fdMode);
// Override scene mode with FACE_PRIORITY if the request was using FACE_PRIORITY
if (fdScenePriority) {
result.set(CaptureResult.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_FACE_PRIORITY);
}
}
}

View File

@@ -1,321 +0,0 @@
/*
* 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.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.utils.ParamsUtils;
import android.util.Log;
import java.util.Objects;
import static android.hardware.camera2.CaptureRequest.*;
import static com.android.internal.util.Preconditions.*;
/**
* Map capture request data into legacy focus state transitions.
*
* <p>This object will asynchronously process auto-focus changes, so no interaction
* with it is necessary beyond reading the current state and updating with the latest trigger.</p>
*/
@SuppressWarnings("deprecation")
public class LegacyFocusStateMapper {
private static String TAG = "LegacyFocusStateMapper";
private static final boolean DEBUG = false;
private final Camera mCamera;
private int mAfStatePrevious = CONTROL_AF_STATE_INACTIVE;
private String mAfModePrevious = null;
/** Guard mAfRun and mAfState */
private final Object mLock = new Object();
/** Guard access with mLock */
private int mAfRun = 0;
/** Guard access with mLock */
private int mAfState = CONTROL_AF_STATE_INACTIVE;
/**
* Instantiate a new focus state mapper.
*
* @param camera a non-{@code null} camera1 device
*
* @throws NullPointerException if any of the args were {@code null}
*/
public LegacyFocusStateMapper(Camera camera) {
mCamera = checkNotNull(camera, "camera must not be null");
}
/**
* Process the AF triggers from the request as a camera1 autofocus routine.
*
* <p>This method should be called after the parameters are {@link LegacyRequestMapper mapped}
* with the request.</p>
*
* <p>Callbacks are processed in the background, and the next call to {@link #mapResultTriggers}
* will have the latest AF state as reflected by the camera1 callbacks.</p>
*
* <p>None of the arguments will be mutated.</p>
*
* @param captureRequest a non-{@code null} request
* @param parameters a non-{@code null} parameters corresponding to this request (read-only)
*/
public void processRequestTriggers(CaptureRequest captureRequest,
Camera.Parameters parameters) {
checkNotNull(captureRequest, "captureRequest must not be null");
/*
* control.afTrigger
*/
int afTrigger = ParamsUtils.getOrDefault(captureRequest, CONTROL_AF_TRIGGER,
CONTROL_AF_TRIGGER_IDLE);
final String afMode = parameters.getFocusMode();
if (!Objects.equals(mAfModePrevious, afMode)) {
if (DEBUG) {
Log.v(TAG, "processRequestTriggers - AF mode switched from " + mAfModePrevious +
" to " + afMode);
}
// Switching modes always goes back to INACTIVE; ignore callbacks from previous modes
synchronized (mLock) {
++mAfRun;
mAfState = CONTROL_AF_STATE_INACTIVE;
}
mCamera.cancelAutoFocus();
}
mAfModePrevious = afMode;
// Passive AF Scanning
{
final int currentAfRun;
synchronized (mLock) {
currentAfRun = mAfRun;
}
Camera.AutoFocusMoveCallback afMoveCallback = new Camera.AutoFocusMoveCallback() {
@Override
public void onAutoFocusMoving(boolean start, Camera camera) {
synchronized (mLock) {
int latestAfRun = mAfRun;
if (DEBUG) {
Log.v(TAG,
"onAutoFocusMoving - start " + start + " latest AF run " +
latestAfRun + ", last AF run " + currentAfRun
);
}
if (currentAfRun != latestAfRun) {
Log.d(TAG,
"onAutoFocusMoving - ignoring move callbacks from old af run"
+ currentAfRun
);
return;
}
int newAfState = start ?
CONTROL_AF_STATE_PASSIVE_SCAN :
CONTROL_AF_STATE_PASSIVE_FOCUSED;
// We never send CONTROL_AF_STATE_PASSIVE_UNFOCUSED
switch (afMode) {
case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
break;
// This callback should never be sent in any other AF mode
default:
Log.w(TAG, "onAutoFocus - got unexpected onAutoFocus in mode "
+ afMode);
}
mAfState = newAfState;
}
}
};
// Only set move callback if we can call autofocus.
switch (afMode) {
case Parameters.FOCUS_MODE_AUTO:
case Parameters.FOCUS_MODE_MACRO:
case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
mCamera.setAutoFocusMoveCallback(afMoveCallback);
}
}
// AF Locking
switch (afTrigger) {
case CONTROL_AF_TRIGGER_START:
int afStateAfterStart;
switch (afMode) {
case Parameters.FOCUS_MODE_AUTO:
case Parameters.FOCUS_MODE_MACRO:
afStateAfterStart = CONTROL_AF_STATE_ACTIVE_SCAN;
break;
case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
afStateAfterStart = CONTROL_AF_STATE_PASSIVE_SCAN;
break;
default:
// EDOF, INFINITY
afStateAfterStart = CONTROL_AF_STATE_INACTIVE;
}
final int currentAfRun;
synchronized (mLock) {
currentAfRun = ++mAfRun;
mAfState = afStateAfterStart;
}
if (DEBUG) {
Log.v(TAG, "processRequestTriggers - got AF_TRIGGER_START, " +
"new AF run is " + currentAfRun);
}
// Avoid calling autofocus unless we are in a state that supports calling this.
if (afStateAfterStart == CONTROL_AF_STATE_INACTIVE) {
break;
}
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
synchronized (mLock) {
int latestAfRun = mAfRun;
if (DEBUG) {
Log.v(TAG, "onAutoFocus - success " + success + " latest AF run " +
latestAfRun + ", last AF run " + currentAfRun);
}
// Ignore old auto-focus results, since another trigger was requested
if (latestAfRun != currentAfRun) {
Log.d(TAG, String.format("onAutoFocus - ignoring AF callback " +
"(old run %d, new run %d)", currentAfRun, latestAfRun));
return;
}
int newAfState = success ?
CONTROL_AF_STATE_FOCUSED_LOCKED :
CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
switch (afMode) {
case Parameters.FOCUS_MODE_AUTO:
case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
case Parameters.FOCUS_MODE_MACRO:
break;
// This callback should never be sent in any other AF mode
default:
Log.w(TAG, "onAutoFocus - got unexpected onAutoFocus in mode "
+ afMode);
}
mAfState = newAfState;
}
}
});
break;
case CONTROL_AF_TRIGGER_CANCEL:
synchronized (mLock) {
int updatedAfRun;
synchronized (mLock) {
updatedAfRun = ++mAfRun;
mAfState = CONTROL_AF_STATE_INACTIVE;
}
mCamera.cancelAutoFocus();
if (DEBUG) {
Log.v(TAG, "processRequestTriggers - got AF_TRIGGER_CANCEL, " +
"new AF run is " + updatedAfRun);
}
}
break;
case CONTROL_AF_TRIGGER_IDLE:
// No action necessary. The callbacks will handle transitions.
break;
default:
Log.w(TAG, "processRequestTriggers - ignoring unknown control.afTrigger = "
+ afTrigger);
}
}
/**
* Update the {@code result} camera metadata map with the new value for the
* {@code control.afState}.
*
* <p>AF callbacks are processed in the background, and each call to {@link #mapResultTriggers}
* will have the latest AF state as reflected by the camera1 callbacks.</p>
*
* @param result a non-{@code null} result
*/
public void mapResultTriggers(CameraMetadataNative result) {
checkNotNull(result, "result must not be null");
int newAfState;
synchronized (mLock) {
newAfState = mAfState;
}
if (DEBUG && newAfState != mAfStatePrevious) {
Log.v(TAG, String.format("mapResultTriggers - afState changed from %s to %s",
afStateToString(mAfStatePrevious), afStateToString(newAfState)));
}
result.set(CaptureResult.CONTROL_AF_STATE, newAfState);
mAfStatePrevious = newAfState;
}
private static String afStateToString(int afState) {
switch (afState) {
case CONTROL_AF_STATE_ACTIVE_SCAN:
return "ACTIVE_SCAN";
case CONTROL_AF_STATE_FOCUSED_LOCKED:
return "FOCUSED_LOCKED";
case CONTROL_AF_STATE_INACTIVE:
return "INACTIVE";
case CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
return "NOT_FOCUSED_LOCKED";
case CONTROL_AF_STATE_PASSIVE_FOCUSED:
return "PASSIVE_FOCUSED";
case CONTROL_AF_STATE_PASSIVE_SCAN:
return "PASSIVE_SCAN";
case CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
return "PASSIVE_UNFOCUSED";
default :
return "UNKNOWN(" + afState + ")";
}
}
}

View File

@@ -1,67 +0,0 @@
/*
* 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.hardware.Camera;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.util.Size;
import static com.android.internal.util.Preconditions.*;
/**
* Hold important data necessary to build the camera1 parameters up from a capture request.
*/
public class LegacyRequest {
/** Immutable characteristics for the camera corresponding to this request */
public final CameraCharacteristics characteristics;
/** Immutable capture request, as requested by the user */
public final CaptureRequest captureRequest;
/** Immutable api1 preview buffer size at the time of the request */
public final Size previewSize;
/** <em>Mutable</em> camera parameters */
public final Camera.Parameters parameters;
/**
* Create a new legacy request; the parameters are copied.
*
* @param characteristics immutable static camera characteristics for this camera
* @param captureRequest immutable user-defined capture request
* @param previewSize immutable internal preview size used for {@link Camera#setPreviewSurface}
* @param parameters the initial camera1 parameter state; (copied) can be mutated
*/
public LegacyRequest(CameraCharacteristics characteristics, CaptureRequest captureRequest,
Size previewSize, Camera.Parameters parameters) {
this.characteristics = checkNotNull(characteristics, "characteristics must not be null");
this.captureRequest = checkNotNull(captureRequest, "captureRequest must not be null");
this.previewSize = checkNotNull(previewSize, "previewSize must not be null");
checkNotNull(parameters, "parameters must not be null");
this.parameters = Camera.getParametersCopy(parameters);
}
/**
* Update the current parameters in-place to be a copy of the new parameters.
*
* @param parameters non-{@code null} parameters for api1 camera
*/
public void setParameters(Camera.Parameters parameters) {
checkNotNull(parameters, "parameters must not be null");
this.parameters.copyFrom(parameters);
}
}

View File

@@ -1,688 +0,0 @@
/*
* 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.Rect;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.utils.ListUtils;
import android.hardware.camera2.utils.ParamsUtils;
import android.location.Location;
import android.util.Log;
import android.util.Range;
import android.util.Size;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import static android.hardware.camera2.CaptureRequest.*;
/**
* Provide legacy-specific implementations of camera2 CaptureRequest for legacy devices.
*/
@SuppressWarnings("deprecation")
public class LegacyRequestMapper {
private static final String TAG = "LegacyRequestMapper";
private static final boolean DEBUG = false;
/** Default quality for android.jpeg.quality, android.jpeg.thumbnailQuality */
private static final byte DEFAULT_JPEG_QUALITY = 85;
/**
* Set the legacy parameters using the {@link LegacyRequest legacy request}.
*
* <p>The legacy request's parameters are changed as a side effect of calling this
* method.</p>
*
* @param legacyRequest a non-{@code null} legacy request
*/
public static void convertRequestMetadata(LegacyRequest legacyRequest) {
CameraCharacteristics characteristics = legacyRequest.characteristics;
CaptureRequest request = legacyRequest.captureRequest;
Size previewSize = legacyRequest.previewSize;
Camera.Parameters params = legacyRequest.parameters;
Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
/*
* scaler.cropRegion
*/
ParameterUtils.ZoomData zoomData;
{
zoomData = ParameterUtils.convertToLegacyZoom(activeArray,
request.get(SCALER_CROP_REGION),
request.get(CONTROL_ZOOM_RATIO),
previewSize,
params);
if (params.isZoomSupported()) {
params.setZoom(zoomData.zoomIndex);
} else if (DEBUG) {
Log.v(TAG, "convertRequestToMetadata - zoom is not supported");
}
}
/*
* colorCorrection.*
*/
// colorCorrection.aberrationMode
{
int aberrationMode = ParamsUtils.getOrDefault(request,
COLOR_CORRECTION_ABERRATION_MODE,
/*defaultValue*/COLOR_CORRECTION_ABERRATION_MODE_FAST);
if (aberrationMode != COLOR_CORRECTION_ABERRATION_MODE_FAST &&
aberrationMode != COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY) {
Log.w(TAG, "convertRequestToMetadata - Ignoring unsupported " +
"colorCorrection.aberrationMode = " + aberrationMode);
}
}
/*
* control.ae*
*/
// control.aeAntibandingMode
{
String legacyMode;
Integer antiBandingMode = request.get(CONTROL_AE_ANTIBANDING_MODE);
if (antiBandingMode != null) {
legacyMode = convertAeAntiBandingModeToLegacy(antiBandingMode);
} else {
legacyMode = ListUtils.listSelectFirstFrom(params.getSupportedAntibanding(),
new String[] {
Parameters.ANTIBANDING_AUTO,
Parameters.ANTIBANDING_OFF,
Parameters.ANTIBANDING_50HZ,
Parameters.ANTIBANDING_60HZ,
});
}
if (legacyMode != null) {
params.setAntibanding(legacyMode);
}
}
/*
* control.aeRegions, afRegions
*/
{
// aeRegions
{
// Use aeRegions if available, fall back to using awbRegions if present
MeteringRectangle[] aeRegions = request.get(CONTROL_AE_REGIONS);
if (request.get(CONTROL_AWB_REGIONS) != null) {
Log.w(TAG, "convertRequestMetadata - control.awbRegions setting is not " +
"supported, ignoring value");
}
int maxNumMeteringAreas = params.getMaxNumMeteringAreas();
List<Camera.Area> meteringAreaList = convertMeteringRegionsToLegacy(
activeArray, zoomData, aeRegions, maxNumMeteringAreas,
/*regionName*/"AE");
// WAR: for b/17252693, some devices can't handle params.setFocusAreas(null).
if (maxNumMeteringAreas > 0) {
params.setMeteringAreas(meteringAreaList);
}
}
// afRegions
{
MeteringRectangle[] afRegions = request.get(CONTROL_AF_REGIONS);
int maxNumFocusAreas = params.getMaxNumFocusAreas();
List<Camera.Area> focusAreaList = convertMeteringRegionsToLegacy(
activeArray, zoomData, afRegions, maxNumFocusAreas,
/*regionName*/"AF");
// WAR: for b/17252693, some devices can't handle params.setFocusAreas(null).
if (maxNumFocusAreas > 0) {
params.setFocusAreas(focusAreaList);
}
}
}
// control.aeTargetFpsRange
Range<Integer> aeFpsRange = request.get(CONTROL_AE_TARGET_FPS_RANGE);
if (aeFpsRange != null) {
int[] legacyFps = convertAeFpsRangeToLegacy(aeFpsRange);
int[] rangeToApply = null;
for(int[] range : params.getSupportedPreviewFpsRange()) {
// Round range up/down to integer FPS value
int intRangeLow = (int) Math.floor(range[0] / 1000.0) * 1000;
int intRangeHigh = (int) Math.ceil(range[1] / 1000.0) * 1000;
if (legacyFps[0] == intRangeLow && legacyFps[1] == intRangeHigh) {
rangeToApply = range;
break;
}
}
if (rangeToApply != null) {
params.setPreviewFpsRange(rangeToApply[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
rangeToApply[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
} else {
Log.w(TAG, "Unsupported FPS range set [" + legacyFps[0] + "," + legacyFps[1] + "]");
}
}
/*
* control
*/
// control.aeExposureCompensation
{
Range<Integer> compensationRange =
characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
int compensation = ParamsUtils.getOrDefault(request,
CONTROL_AE_EXPOSURE_COMPENSATION,
/*defaultValue*/0);
if (!compensationRange.contains(compensation)) {
Log.w(TAG,
"convertRequestMetadata - control.aeExposureCompensation " +
"is out of range, ignoring value");
compensation = 0;
}
params.setExposureCompensation(compensation);
}
// control.aeLock
{
Boolean aeLock = getIfSupported(request, CONTROL_AE_LOCK, /*defaultValue*/false,
params.isAutoExposureLockSupported(),
/*allowedValue*/false);
if (aeLock != null) {
params.setAutoExposureLock(aeLock);
}
if (DEBUG) {
Log.v(TAG, "convertRequestToMetadata - control.aeLock set to " + aeLock);
}
// TODO: Don't add control.aeLock to availableRequestKeys if it's not supported
}
// control.aeMode, flash.mode
mapAeAndFlashMode(request, /*out*/params);
// control.afMode
{
int afMode = ParamsUtils.getOrDefault(request, CONTROL_AF_MODE,
/*defaultValue*/CONTROL_AF_MODE_OFF);
String focusMode = LegacyMetadataMapper.convertAfModeToLegacy(afMode,
params.getSupportedFocusModes());
if (focusMode != null) {
params.setFocusMode(focusMode);
}
if (DEBUG) {
Log.v(TAG, "convertRequestToMetadata - control.afMode "
+ afMode + " mapped to " + focusMode);
}
}
// control.awbMode
{
Integer awbMode = getIfSupported(request, CONTROL_AWB_MODE,
/*defaultValue*/CONTROL_AWB_MODE_AUTO,
params.getSupportedWhiteBalance() != null,
/*allowedValue*/CONTROL_AWB_MODE_AUTO);
String whiteBalanceMode = null;
if (awbMode != null) { // null iff AWB is not supported by camera1 api
whiteBalanceMode = convertAwbModeToLegacy(awbMode);
params.setWhiteBalance(whiteBalanceMode);
}
if (DEBUG) {
Log.v(TAG, "convertRequestToMetadata - control.awbMode "
+ awbMode + " mapped to " + whiteBalanceMode);
}
}
// control.awbLock
{
Boolean awbLock = getIfSupported(request, CONTROL_AWB_LOCK, /*defaultValue*/false,
params.isAutoWhiteBalanceLockSupported(),
/*allowedValue*/false);
if (awbLock != null) {
params.setAutoWhiteBalanceLock(awbLock);
}
// TODO: Don't add control.awbLock to availableRequestKeys if it's not supported
}
// control.captureIntent
{
int captureIntent = ParamsUtils.getOrDefault(request,
CONTROL_CAPTURE_INTENT,
/*defaultValue*/CONTROL_CAPTURE_INTENT_PREVIEW);
captureIntent = filterSupportedCaptureIntent(captureIntent);
params.setRecordingHint(
captureIntent == CONTROL_CAPTURE_INTENT_VIDEO_RECORD ||
captureIntent == CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT);
}
// control.videoStabilizationMode
{
Integer stabMode = getIfSupported(request, CONTROL_VIDEO_STABILIZATION_MODE,
/*defaultValue*/CONTROL_VIDEO_STABILIZATION_MODE_OFF,
params.isVideoStabilizationSupported(),
/*allowedValue*/CONTROL_VIDEO_STABILIZATION_MODE_OFF);
if (stabMode != null) {
params.setVideoStabilization(stabMode == CONTROL_VIDEO_STABILIZATION_MODE_ON);
}
}
// lens.focusDistance
{
boolean infinityFocusSupported =
ListUtils.listContains(params.getSupportedFocusModes(),
Parameters.FOCUS_MODE_INFINITY);
Float focusDistance = getIfSupported(request, LENS_FOCUS_DISTANCE,
/*defaultValue*/0f, infinityFocusSupported, /*allowedValue*/0f);
if (focusDistance == null || focusDistance != 0f) {
Log.w(TAG,
"convertRequestToMetadata - Ignoring android.lens.focusDistance "
+ infinityFocusSupported + ", only 0.0f is supported");
}
}
// control.sceneMode, control.mode
{
// TODO: Map FACE_PRIORITY scene mode to face detection.
if (params.getSupportedSceneModes() != null) {
int controlMode = ParamsUtils.getOrDefault(request, CONTROL_MODE,
/*defaultValue*/CONTROL_MODE_AUTO);
String modeToSet;
switch (controlMode) {
case CONTROL_MODE_USE_SCENE_MODE: {
int sceneMode = ParamsUtils.getOrDefault(request, CONTROL_SCENE_MODE,
/*defaultValue*/CONTROL_SCENE_MODE_DISABLED);
String legacySceneMode = LegacyMetadataMapper.
convertSceneModeToLegacy(sceneMode);
if (legacySceneMode != null) {
modeToSet = legacySceneMode;
} else {
modeToSet = Parameters.SCENE_MODE_AUTO;
Log.w(TAG, "Skipping unknown requested scene mode: " + sceneMode);
}
break;
}
case CONTROL_MODE_AUTO: {
modeToSet = Parameters.SCENE_MODE_AUTO;
break;
}
default: {
Log.w(TAG, "Control mode " + controlMode +
" is unsupported, defaulting to AUTO");
modeToSet = Parameters.SCENE_MODE_AUTO;
}
}
params.setSceneMode(modeToSet);
}
}
// control.effectMode
{
if (params.getSupportedColorEffects() != null) {
int effectMode = ParamsUtils.getOrDefault(request, CONTROL_EFFECT_MODE,
/*defaultValue*/CONTROL_EFFECT_MODE_OFF);
String legacyEffectMode = LegacyMetadataMapper.convertEffectModeToLegacy(effectMode);
if (legacyEffectMode != null) {
params.setColorEffect(legacyEffectMode);
} else {
params.setColorEffect(Parameters.EFFECT_NONE);
Log.w(TAG, "Skipping unknown requested effect mode: " + effectMode);
}
}
}
/*
* sensor
*/
// sensor.testPattern
{
int testPatternMode = ParamsUtils.getOrDefault(request, SENSOR_TEST_PATTERN_MODE,
/*defaultValue*/SENSOR_TEST_PATTERN_MODE_OFF);
if (testPatternMode != SENSOR_TEST_PATTERN_MODE_OFF) {
Log.w(TAG, "convertRequestToMetadata - ignoring sensor.testPatternMode "
+ testPatternMode + "; only OFF is supported");
}
}
/*
* jpeg.*
*/
// jpeg.gpsLocation
{
Location location = request.get(JPEG_GPS_LOCATION);
if (location != null) {
if (checkForCompleteGpsData(location)) {
params.setGpsAltitude(location.getAltitude());
params.setGpsLatitude(location.getLatitude());
params.setGpsLongitude(location.getLongitude());
params.setGpsProcessingMethod(location.getProvider().toUpperCase());
params.setGpsTimestamp(location.getTime());
} else {
Log.w(TAG, "Incomplete GPS parameters provided in location " + location);
}
} else {
params.removeGpsData();
}
}
// jpeg.orientation
{
Integer orientation = request.get(CaptureRequest.JPEG_ORIENTATION);
params.setRotation(ParamsUtils.getOrDefault(request, JPEG_ORIENTATION,
(orientation == null) ? 0 : orientation));
}
// jpeg.quality
{
params.setJpegQuality(0xFF & ParamsUtils.getOrDefault(request, JPEG_QUALITY,
DEFAULT_JPEG_QUALITY));
}
// jpeg.thumbnailQuality
{
params.setJpegThumbnailQuality(0xFF & ParamsUtils.getOrDefault(request,
JPEG_THUMBNAIL_QUALITY, DEFAULT_JPEG_QUALITY));
}
// jpeg.thumbnailSize
{
List<Camera.Size> sizes = params.getSupportedJpegThumbnailSizes();
if (sizes != null && sizes.size() > 0) {
Size s = request.get(JPEG_THUMBNAIL_SIZE);
boolean invalidSize = (s == null) ? false : !ParameterUtils.containsSize(sizes,
s.getWidth(), s.getHeight());
if (invalidSize) {
Log.w(TAG, "Invalid JPEG thumbnail size set " + s + ", skipping thumbnail...");
}
if (s == null || invalidSize) {
// (0,0) = "no thumbnail" in Camera API 1
params.setJpegThumbnailSize(/*width*/0, /*height*/0);
} else {
params.setJpegThumbnailSize(s.getWidth(), s.getHeight());
}
}
}
/*
* noiseReduction.*
*/
// noiseReduction.mode
{
int mode = ParamsUtils.getOrDefault(request,
NOISE_REDUCTION_MODE,
/*defaultValue*/NOISE_REDUCTION_MODE_FAST);
if (mode != NOISE_REDUCTION_MODE_FAST &&
mode != NOISE_REDUCTION_MODE_HIGH_QUALITY) {
Log.w(TAG, "convertRequestToMetadata - Ignoring unsupported " +
"noiseReduction.mode = " + mode);
}
}
}
private static boolean checkForCompleteGpsData(Location location) {
return location != null && location.getProvider() != null && location.getTime() != 0;
}
static int filterSupportedCaptureIntent(int captureIntent) {
switch (captureIntent) {
case CONTROL_CAPTURE_INTENT_CUSTOM:
case CONTROL_CAPTURE_INTENT_PREVIEW:
case CONTROL_CAPTURE_INTENT_STILL_CAPTURE:
case CONTROL_CAPTURE_INTENT_VIDEO_RECORD:
case CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT:
break;
case CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG:
case CONTROL_CAPTURE_INTENT_MANUAL:
captureIntent = CONTROL_CAPTURE_INTENT_PREVIEW;
Log.w(TAG, "Unsupported control.captureIntent value " + captureIntent
+ "; default to PREVIEW");
default:
captureIntent = CONTROL_CAPTURE_INTENT_PREVIEW;
Log.w(TAG, "Unknown control.captureIntent value " + captureIntent
+ "; default to PREVIEW");
}
return captureIntent;
}
private static List<Camera.Area> convertMeteringRegionsToLegacy(
Rect activeArray, ParameterUtils.ZoomData zoomData,
MeteringRectangle[] meteringRegions, int maxNumMeteringAreas, String regionName) {
if (meteringRegions == null || maxNumMeteringAreas <= 0) {
if (maxNumMeteringAreas > 0) {
return Arrays.asList(ParameterUtils.CAMERA_AREA_DEFAULT);
} else {
return null;
}
}
// Add all non-zero weight regions to the list
List<MeteringRectangle> meteringRectangleList = new ArrayList<>();
for (MeteringRectangle rect : meteringRegions) {
if (rect.getMeteringWeight() != MeteringRectangle.METERING_WEIGHT_DONT_CARE) {
meteringRectangleList.add(rect);
}
}
if (meteringRectangleList.size() == 0) {
Log.w(TAG, "Only received metering rectangles with weight 0.");
return Arrays.asList(ParameterUtils.CAMERA_AREA_DEFAULT);
}
// Ignore any regions beyond our maximum supported count
int countMeteringAreas =
Math.min(maxNumMeteringAreas, meteringRectangleList.size());
List<Camera.Area> meteringAreaList = new ArrayList<>(countMeteringAreas);
for (int i = 0; i < countMeteringAreas; ++i) {
MeteringRectangle rect = meteringRectangleList.get(i);
ParameterUtils.MeteringData meteringData =
ParameterUtils.convertMeteringRectangleToLegacy(activeArray, rect, zoomData);
meteringAreaList.add(meteringData.meteringArea);
}
if (maxNumMeteringAreas < meteringRectangleList.size()) {
Log.w(TAG,
"convertMeteringRegionsToLegacy - Too many requested " + regionName +
" regions, ignoring all beyond the first " + maxNumMeteringAreas);
}
if (DEBUG) {
Log.v(TAG, "convertMeteringRegionsToLegacy - " + regionName + " areas = "
+ ParameterUtils.stringFromAreaList(meteringAreaList));
}
return meteringAreaList;
}
private static void mapAeAndFlashMode(CaptureRequest r, /*out*/Parameters p) {
int flashMode = ParamsUtils.getOrDefault(r, FLASH_MODE, FLASH_MODE_OFF);
int aeMode = ParamsUtils.getOrDefault(r, CONTROL_AE_MODE, CONTROL_AE_MODE_ON);
List<String> supportedFlashModes = p.getSupportedFlashModes();
String flashModeSetting = null;
// Flash is OFF by default, on cameras that support flash
if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_OFF)) {
flashModeSetting = Parameters.FLASH_MODE_OFF;
}
/*
* Map all of the control.aeMode* enums, but ignore AE_MODE_OFF since we never support it
*/
// Ignore flash.mode controls unless aeMode == ON
if (aeMode == CONTROL_AE_MODE_ON) {
if (flashMode == FLASH_MODE_TORCH) {
if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_TORCH)) {
flashModeSetting = Parameters.FLASH_MODE_TORCH;
} else {
Log.w(TAG, "mapAeAndFlashMode - Ignore flash.mode == TORCH;" +
"camera does not support it");
}
} else if (flashMode == FLASH_MODE_SINGLE) {
if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
flashModeSetting = Parameters.FLASH_MODE_ON;
} else {
Log.w(TAG, "mapAeAndFlashMode - Ignore flash.mode == SINGLE;" +
"camera does not support it");
}
} else {
// Use the default FLASH_MODE_OFF
}
} else if (aeMode == CONTROL_AE_MODE_ON_ALWAYS_FLASH) {
if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
flashModeSetting = Parameters.FLASH_MODE_ON;
} else {
Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_ALWAYS_FLASH;" +
"camera does not support it");
}
} else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH) {
if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_AUTO)) {
flashModeSetting = Parameters.FLASH_MODE_AUTO;
} else {
Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_AUTO_FLASH;" +
"camera does not support it");
}
} else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE) {
if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_RED_EYE)) {
flashModeSetting = Parameters.FLASH_MODE_RED_EYE;
} else {
Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_AUTO_FLASH_REDEYE;"
+ "camera does not support it");
}
} else {
// Default to aeMode == ON, flash = OFF
}
if (flashModeSetting != null) {
p.setFlashMode(flashModeSetting);
}
if (DEBUG) {
Log.v(TAG,
"mapAeAndFlashMode - set flash.mode (api1) to " + flashModeSetting
+ ", requested (api2) " + flashMode
+ ", supported (api1) " + ListUtils.listToString(supportedFlashModes));
}
}
/**
* Returns null if the anti-banding mode enum is not supported.
*/
private static String convertAeAntiBandingModeToLegacy(int mode) {
switch (mode) {
case CONTROL_AE_ANTIBANDING_MODE_OFF: {
return Parameters.ANTIBANDING_OFF;
}
case CONTROL_AE_ANTIBANDING_MODE_50HZ: {
return Parameters.ANTIBANDING_50HZ;
}
case CONTROL_AE_ANTIBANDING_MODE_60HZ: {
return Parameters.ANTIBANDING_60HZ;
}
case CONTROL_AE_ANTIBANDING_MODE_AUTO: {
return Parameters.ANTIBANDING_AUTO;
}
default: {
return null;
}
}
}
private static int[] convertAeFpsRangeToLegacy(Range<Integer> fpsRange) {
int[] legacyFps = new int[2];
legacyFps[Parameters.PREVIEW_FPS_MIN_INDEX] = fpsRange.getLower() * 1000;
legacyFps[Parameters.PREVIEW_FPS_MAX_INDEX] = fpsRange.getUpper() * 1000;
return legacyFps;
}
private static String convertAwbModeToLegacy(int mode) {
switch (mode) {
case CONTROL_AWB_MODE_AUTO:
return Camera.Parameters.WHITE_BALANCE_AUTO;
case CONTROL_AWB_MODE_INCANDESCENT:
return Camera.Parameters.WHITE_BALANCE_INCANDESCENT;
case CONTROL_AWB_MODE_FLUORESCENT:
return Camera.Parameters.WHITE_BALANCE_FLUORESCENT;
case CONTROL_AWB_MODE_WARM_FLUORESCENT:
return Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT;
case CONTROL_AWB_MODE_DAYLIGHT:
return Camera.Parameters.WHITE_BALANCE_DAYLIGHT;
case CONTROL_AWB_MODE_CLOUDY_DAYLIGHT:
return Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT;
case CONTROL_AWB_MODE_TWILIGHT:
return Camera.Parameters.WHITE_BALANCE_TWILIGHT;
case CONTROL_AWB_MODE_SHADE:
return Parameters.WHITE_BALANCE_SHADE;
default:
Log.w(TAG, "convertAwbModeToLegacy - unrecognized control.awbMode" + mode);
return Camera.Parameters.WHITE_BALANCE_AUTO;
}
}
/**
* Return {@code null} if the value is not supported, otherwise return the retrieved key's
* value from the request (or the default value if it wasn't set).
*
* <p>If the fetched value in the request is equivalent to {@code allowedValue},
* then omit the warning (e.g. turning off AF lock on a camera
* that always has the AF lock turned off is a silent no-op), but still return {@code null}.</p>
*
* <p>Logs a warning to logcat if the key is not supported by api1 camera device.</p.
*/
private static <T> T getIfSupported(
CaptureRequest r, CaptureRequest.Key<T> key, T defaultValue, boolean isSupported,
T allowedValue) {
T val = ParamsUtils.getOrDefault(r, key, defaultValue);
if (!isSupported) {
if (!Objects.equals(val, allowedValue)) {
Log.w(TAG, key.getName() + " is not supported; ignoring requested value " + val);
}
return null;
}
return val;
}
}

View File

@@ -1,529 +0,0 @@
/*
* 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.Rect;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.legacy.ParameterUtils.WeightedRectangle;
import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.utils.ListUtils;
import android.hardware.camera2.utils.ParamsUtils;
import android.util.Log;
import android.util.Size;
import java.util.ArrayList;
import java.util.List;
import static android.hardware.camera2.CaptureResult.*;
/**
* Provide legacy-specific implementations of camera2 CaptureResult for legacy devices.
*/
@SuppressWarnings("deprecation")
public class LegacyResultMapper {
private static final String TAG = "LegacyResultMapper";
private static final boolean DEBUG = false;
private LegacyRequest mCachedRequest = null;
private CameraMetadataNative mCachedResult = null;
/**
* Generate capture result metadata from the legacy camera request.
*
* <p>This method caches and reuses the result from the previous call to this method if
* the {@code parameters} of the subsequent {@link LegacyRequest} passed to this method
* have not changed.</p>
*
* @param legacyRequest a non-{@code null} legacy request containing the latest parameters
* @param timestamp the timestamp to use for this result in nanoseconds.
*
* @return {@link CameraMetadataNative} object containing result metadata.
*/
public CameraMetadataNative cachedConvertResultMetadata(
LegacyRequest legacyRequest, long timestamp) {
CameraMetadataNative result;
boolean cached;
/*
* Attempt to look up the result from the cache if the parameters haven't changed
*/
if (mCachedRequest != null &&
legacyRequest.parameters.same(mCachedRequest.parameters) &&
legacyRequest.captureRequest.equals(mCachedRequest.captureRequest)) {
result = new CameraMetadataNative(mCachedResult);
cached = true;
} else {
result = convertResultMetadata(legacyRequest);
cached = false;
// Always cache a *copy* of the metadata result,
// since api2's client side takes ownership of it after it receives a result
mCachedRequest = legacyRequest;
mCachedResult = new CameraMetadataNative(result);
}
/*
* Unconditionally set fields that change in every single frame
*/
{
// sensor.timestamp
result.set(SENSOR_TIMESTAMP, timestamp);
}
if (DEBUG) {
Log.v(TAG, "cachedConvertResultMetadata - cached? " + cached +
" timestamp = " + timestamp);
Log.v(TAG, "----- beginning of result dump ------");
result.dumpToLog();
Log.v(TAG, "----- end of result dump ------");
}
return result;
}
/**
* Generate capture result metadata from the legacy camera request.
*
* @param legacyRequest a non-{@code null} legacy request containing the latest parameters
* @return a {@link CameraMetadataNative} object containing result metadata.
*/
private static CameraMetadataNative convertResultMetadata(LegacyRequest legacyRequest) {
CameraCharacteristics characteristics = legacyRequest.characteristics;
CaptureRequest request = legacyRequest.captureRequest;
Size previewSize = legacyRequest.previewSize;
Camera.Parameters params = legacyRequest.parameters;
CameraMetadataNative result = new CameraMetadataNative();
Rect activeArraySize = characteristics.get(
CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
ZoomData zoomData = ParameterUtils.convertToLegacyZoom(activeArraySize,
request.get(CaptureRequest.SCALER_CROP_REGION),
request.get(CaptureRequest.CONTROL_ZOOM_RATIO),
previewSize, params);
/*
* colorCorrection
*/
// colorCorrection.aberrationMode
{
result.set(COLOR_CORRECTION_ABERRATION_MODE,
request.get(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE));
}
/*
* control
*/
/*
* control.ae*
*/
mapAe(result, characteristics, request, activeArraySize, zoomData, /*out*/params);
/*
* control.af*
*/
mapAf(result, activeArraySize, zoomData, /*out*/params);
/*
* control.awb*
*/
mapAwb(result, /*out*/params);
/*
* control.captureIntent
*/
{
int captureIntent = ParamsUtils.getOrDefault(request,
CaptureRequest.CONTROL_CAPTURE_INTENT,
/*defaultValue*/CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW);
captureIntent = LegacyRequestMapper.filterSupportedCaptureIntent(captureIntent);
result.set(CONTROL_CAPTURE_INTENT, captureIntent);
}
/*
* control.mode
*/
{
int controlMode = ParamsUtils.getOrDefault(request, CaptureRequest.CONTROL_MODE,
CONTROL_MODE_AUTO);
if (controlMode == CaptureResult.CONTROL_MODE_USE_SCENE_MODE) {
result.set(CONTROL_MODE, CONTROL_MODE_USE_SCENE_MODE);
} else {
result.set(CONTROL_MODE, CONTROL_MODE_AUTO);
}
}
/*
* control.sceneMode
*/
{
String legacySceneMode = params.getSceneMode();
int mode = LegacyMetadataMapper.convertSceneModeFromLegacy(legacySceneMode);
if (mode != LegacyMetadataMapper.UNKNOWN_MODE) {
result.set(CaptureResult.CONTROL_SCENE_MODE, mode);
// In case of SCENE_MODE == FACE_PRIORITY, LegacyFaceDetectMapper will override
// the result to say SCENE_MODE == FACE_PRIORITY.
} else {
Log.w(TAG, "Unknown scene mode " + legacySceneMode +
" returned by camera HAL, setting to disabled.");
result.set(CaptureResult.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_DISABLED);
}
}
/*
* control.effectMode
*/
{
String legacyEffectMode = params.getColorEffect();
int mode = LegacyMetadataMapper.convertEffectModeFromLegacy(legacyEffectMode);
if (mode != LegacyMetadataMapper.UNKNOWN_MODE) {
result.set(CaptureResult.CONTROL_EFFECT_MODE, mode);
} else {
Log.w(TAG, "Unknown effect mode " + legacyEffectMode +
" returned by camera HAL, setting to off.");
result.set(CaptureResult.CONTROL_EFFECT_MODE, CONTROL_EFFECT_MODE_OFF);
}
}
// control.videoStabilizationMode
{
int stabMode =
(params.isVideoStabilizationSupported() && params.getVideoStabilization()) ?
CONTROL_VIDEO_STABILIZATION_MODE_ON :
CONTROL_VIDEO_STABILIZATION_MODE_OFF;
result.set(CONTROL_VIDEO_STABILIZATION_MODE, stabMode);
}
/*
* flash
*/
{
// flash.mode, flash.state mapped in mapAeAndFlashMode
}
/*
* lens
*/
// lens.focusDistance
{
if (Parameters.FOCUS_MODE_INFINITY.equals(params.getFocusMode())) {
result.set(CaptureResult.LENS_FOCUS_DISTANCE, 0.0f);
}
}
// lens.focalLength
result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
/*
* request
*/
// request.pipelineDepth
result.set(REQUEST_PIPELINE_DEPTH,
characteristics.get(CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH));
/*
* scaler
*/
mapScaler(result, zoomData, /*out*/params);
/*
* sensor
*/
// sensor.timestamp varies every frame; mapping is done in #cachedConvertResultMetadata
{
// Unconditionally no test patterns
result.set(SENSOR_TEST_PATTERN_MODE, SENSOR_TEST_PATTERN_MODE_OFF);
}
/*
* jpeg
*/
// jpeg.gpsLocation
result.set(JPEG_GPS_LOCATION, request.get(CaptureRequest.JPEG_GPS_LOCATION));
// jpeg.orientation
result.set(JPEG_ORIENTATION, request.get(CaptureRequest.JPEG_ORIENTATION));
// jpeg.quality
result.set(JPEG_QUALITY, (byte) params.getJpegQuality());
// jpeg.thumbnailQuality
result.set(JPEG_THUMBNAIL_QUALITY, (byte) params.getJpegThumbnailQuality());
// jpeg.thumbnailSize
Camera.Size s = params.getJpegThumbnailSize();
if (s != null) {
result.set(JPEG_THUMBNAIL_SIZE, ParameterUtils.convertSize(s));
} else {
Log.w(TAG, "Null thumbnail size received from parameters.");
}
/*
* noiseReduction.*
*/
// noiseReduction.mode
result.set(NOISE_REDUCTION_MODE, request.get(CaptureRequest.NOISE_REDUCTION_MODE));
return result;
}
private static void mapAe(CameraMetadataNative m,
CameraCharacteristics characteristics,
CaptureRequest request, Rect activeArray, ZoomData zoomData, /*out*/Parameters p) {
// control.aeAntiBandingMode
{
int antiBandingMode = LegacyMetadataMapper.convertAntiBandingModeOrDefault(
p.getAntibanding());
m.set(CONTROL_AE_ANTIBANDING_MODE, antiBandingMode);
}
// control.aeExposureCompensation
{
m.set(CONTROL_AE_EXPOSURE_COMPENSATION, p.getExposureCompensation());
}
// control.aeLock
{
boolean lock = p.isAutoExposureLockSupported() ? p.getAutoExposureLock() : false;
m.set(CONTROL_AE_LOCK, lock);
if (DEBUG) {
Log.v(TAG,
"mapAe - android.control.aeLock = " + lock +
", supported = " + p.isAutoExposureLockSupported());
}
Boolean requestLock = request.get(CaptureRequest.CONTROL_AE_LOCK);
if (requestLock != null && requestLock != lock) {
Log.w(TAG,
"mapAe - android.control.aeLock was requested to " + requestLock +
" but resulted in " + lock);
}
}
// control.aeMode, flash.mode, flash.state
mapAeAndFlashMode(m, characteristics, p);
// control.aeState
if (LegacyMetadataMapper.LIE_ABOUT_AE_STATE) {
// Lie to pass CTS temporarily.
// TODO: Implement precapture trigger, after which we can report CONVERGED ourselves
m.set(CONTROL_AE_STATE, CONTROL_AE_STATE_CONVERGED);
}
// control.aeRegions
if (p.getMaxNumMeteringAreas() > 0) {
if (DEBUG) {
String meteringAreas = p.get("metering-areas");
Log.v(TAG, "mapAe - parameter dump; metering-areas: " + meteringAreas);
}
MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
zoomData, p.getMeteringAreas(), "AE");
m.set(CONTROL_AE_REGIONS, meteringRectArray);
}
}
private static void mapAf(CameraMetadataNative m,
Rect activeArray, ZoomData zoomData, Camera.Parameters p) {
// control.afMode
m.set(CaptureResult.CONTROL_AF_MODE, convertLegacyAfMode(p.getFocusMode()));
// control.afRegions
if (p.getMaxNumFocusAreas() > 0) {
if (DEBUG) {
String focusAreas = p.get("focus-areas");
Log.v(TAG, "mapAe - parameter dump; focus-areas: " + focusAreas);
}
MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
zoomData, p.getFocusAreas(), "AF");
m.set(CONTROL_AF_REGIONS, meteringRectArray);
}
}
private static void mapAwb(CameraMetadataNative m, Camera.Parameters p) {
// control.awbLock
{
boolean lock = p.isAutoWhiteBalanceLockSupported() ?
p.getAutoWhiteBalanceLock() : false;
m.set(CONTROL_AWB_LOCK, lock);
}
// control.awbMode
{
int awbMode = convertLegacyAwbMode(p.getWhiteBalance());
m.set(CONTROL_AWB_MODE, awbMode);
}
}
private static MeteringRectangle[] getMeteringRectangles(Rect activeArray, ZoomData zoomData,
List<Camera.Area> meteringAreaList, String regionName) {
List<MeteringRectangle> meteringRectList = new ArrayList<>();
if (meteringAreaList != null) {
for (Camera.Area area : meteringAreaList) {
WeightedRectangle rect =
ParameterUtils.convertCameraAreaToActiveArrayRectangle(
activeArray, zoomData, area);
meteringRectList.add(rect.toMetering());
}
}
if (DEBUG) {
Log.v(TAG,
"Metering rectangles for " + regionName + ": "
+ ListUtils.listToString(meteringRectList));
}
return meteringRectList.toArray(new MeteringRectangle[0]);
}
/** Map results for control.aeMode, flash.mode, flash.state */
private static void mapAeAndFlashMode(CameraMetadataNative m,
CameraCharacteristics characteristics, Parameters p) {
// Default: AE mode on but flash never fires
int flashMode = FLASH_MODE_OFF;
// If there is no flash on this camera, the state is always unavailable
// , otherwise it's only known for TORCH/SINGLE modes
Integer flashState = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)
? null : FLASH_STATE_UNAVAILABLE;
int aeMode = CONTROL_AE_MODE_ON;
String flashModeSetting = p.getFlashMode();
if (flashModeSetting != null) {
switch (flashModeSetting) {
case Parameters.FLASH_MODE_OFF:
break; // ok, using default
case Parameters.FLASH_MODE_AUTO:
aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
break;
case Parameters.FLASH_MODE_ON:
// flashMode = SINGLE + aeMode = ON is indistinguishable from ON_ALWAYS_FLASH
flashMode = FLASH_MODE_SINGLE;
aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
flashState = FLASH_STATE_FIRED;
break;
case Parameters.FLASH_MODE_RED_EYE:
aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
break;
case Parameters.FLASH_MODE_TORCH:
flashMode = FLASH_MODE_TORCH;
flashState = FLASH_STATE_FIRED;
break;
default:
Log.w(TAG,
"mapAeAndFlashMode - Ignoring unknown flash mode " + p.getFlashMode());
}
}
// flash.state
m.set(FLASH_STATE, flashState);
// flash.mode
m.set(FLASH_MODE, flashMode);
// control.aeMode
m.set(CONTROL_AE_MODE, aeMode);
}
private static int convertLegacyAfMode(String mode) {
if (mode == null) {
Log.w(TAG, "convertLegacyAfMode - no AF mode, default to OFF");
return CONTROL_AF_MODE_OFF;
}
switch (mode) {
case Parameters.FOCUS_MODE_AUTO:
return CONTROL_AF_MODE_AUTO;
case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
return CONTROL_AF_MODE_CONTINUOUS_PICTURE;
case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
return CONTROL_AF_MODE_CONTINUOUS_VIDEO;
case Parameters.FOCUS_MODE_EDOF:
return CONTROL_AF_MODE_EDOF;
case Parameters.FOCUS_MODE_MACRO:
return CONTROL_AF_MODE_MACRO;
case Parameters.FOCUS_MODE_FIXED:
return CONTROL_AF_MODE_OFF;
case Parameters.FOCUS_MODE_INFINITY:
return CONTROL_AF_MODE_OFF;
default:
Log.w(TAG, "convertLegacyAfMode - unknown mode " + mode + " , ignoring");
return CONTROL_AF_MODE_OFF;
}
}
private static int convertLegacyAwbMode(String mode) {
if (mode == null) {
// OK: camera1 api may not support changing WB modes; assume AUTO
return CONTROL_AWB_MODE_AUTO;
}
switch (mode) {
case Camera.Parameters.WHITE_BALANCE_AUTO:
return CONTROL_AWB_MODE_AUTO;
case Camera.Parameters.WHITE_BALANCE_INCANDESCENT:
return CONTROL_AWB_MODE_INCANDESCENT;
case Camera.Parameters.WHITE_BALANCE_FLUORESCENT:
return CONTROL_AWB_MODE_FLUORESCENT;
case Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT:
return CONTROL_AWB_MODE_WARM_FLUORESCENT;
case Camera.Parameters.WHITE_BALANCE_DAYLIGHT:
return CONTROL_AWB_MODE_DAYLIGHT;
case Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT:
return CONTROL_AWB_MODE_CLOUDY_DAYLIGHT;
case Camera.Parameters.WHITE_BALANCE_TWILIGHT:
return CONTROL_AWB_MODE_TWILIGHT;
case Camera.Parameters.WHITE_BALANCE_SHADE:
return CONTROL_AWB_MODE_SHADE;
default:
Log.w(TAG, "convertAwbMode - unrecognized WB mode " + mode);
return CONTROL_AWB_MODE_AUTO;
}
}
/** Map results for scaler.* */
private static void mapScaler(CameraMetadataNative m,
ZoomData zoomData,
/*out*/Parameters p) {
/*
* scaler.cropRegion
*/
{
m.set(SCALER_CROP_REGION, zoomData.reportedCrop);
}
/*
* control.zoomRatio
*/
{
m.set(CONTROL_ZOOM_RATIO, zoomData.reportedZoomRatio);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,308 +0,0 @@
/*
* 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.os.SystemClock;
import android.util.Log;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/**
* GPU and CPU performance measurement for the legacy implementation.
*
* <p>Measures CPU and GPU processing duration for a set of operations, and dumps
* the results into a file.</p>
*
* <p>Rough usage:
* <pre>
* {@code
* <set up workload>
* <start long-running workload>
* mPerfMeasurement.startTimer();
* ...render a frame...
* mPerfMeasurement.stopTimer();
* <end workload>
* mPerfMeasurement.dumpPerformanceData("/sdcard/my_data.txt");
* }
* </pre>
* </p>
*
* <p>All calls to this object must be made within the same thread, and the same GL context.
* PerfMeasurement cannot be used outside of a GL context. The only exception is
* dumpPerformanceData, which can be called outside of a valid GL context.</p>
*/
class PerfMeasurement {
private static final String TAG = "PerfMeasurement";
public static final int DEFAULT_MAX_QUERIES = 3;
private final long mNativeContext;
private int mCompletedQueryCount = 0;
/**
* Values for completed measurements
*/
private ArrayList<Long> mCollectedGpuDurations = new ArrayList<>();
private ArrayList<Long> mCollectedCpuDurations = new ArrayList<>();
private ArrayList<Long> mCollectedTimestamps = new ArrayList<>();
/**
* Values for in-progress measurements (waiting for async GPU results)
*/
private Queue<Long> mTimestampQueue = new LinkedList<>();
private Queue<Long> mCpuDurationsQueue = new LinkedList<>();
private long mStartTimeNs;
/**
* The value returned by {@link #nativeGetNextGlDuration} if no new timing
* measurement is available since the last call.
*/
private static final long NO_DURATION_YET = -1l;
/**
* The value returned by {@link #nativeGetNextGlDuration} if timing failed for
* the next timing interval
*/
private static final long FAILED_TIMING = -2l;
/**
* Create a performance measurement object with a maximum of {@value #DEFAULT_MAX_QUERIES}
* in-progess queries.
*/
public PerfMeasurement() {
mNativeContext = nativeCreateContext(DEFAULT_MAX_QUERIES);
}
/**
* Create a performance measurement object with maxQueries as the maximum number of
* in-progress queries.
*
* @param maxQueries maximum in-progress queries, must be larger than 0.
* @throws IllegalArgumentException if maxQueries is less than 1.
*/
public PerfMeasurement(int maxQueries) {
if (maxQueries < 1) throw new IllegalArgumentException("maxQueries is less than 1");
mNativeContext = nativeCreateContext(maxQueries);
}
/**
* Returns true if the Gl timing methods will work, false otherwise.
*
* <p>Must be called within a valid GL context.</p>
*/
public static boolean isGlTimingSupported() {
return nativeQuerySupport();
}
/**
* Dump collected data to file, and clear the stored data.
*
* <p>
* Format is a simple csv-like text file with a header,
* followed by a 3-column list of values in nanoseconds:
* <pre>
* timestamp gpu_duration cpu_duration
* <long> <long> <long>
* <long> <long> <long>
* <long> <long> <long>
* ....
* </pre>
* </p>
*/
public void dumpPerformanceData(String path) {
try (BufferedWriter dump = new BufferedWriter(new FileWriter(path))) {
dump.write("timestamp gpu_duration cpu_duration\n");
for (int i = 0; i < mCollectedGpuDurations.size(); i++) {
dump.write(String.format("%d %d %d\n",
mCollectedTimestamps.get(i),
mCollectedGpuDurations.get(i),
mCollectedCpuDurations.get(i)));
}
mCollectedTimestamps.clear();
mCollectedGpuDurations.clear();
mCollectedCpuDurations.clear();
} catch (IOException e) {
Log.e(TAG, "Error writing data dump to " + path + ":" + e);
}
}
/**
* Start a GPU/CPU timing measurement.
*
* <p>Call before starting a rendering pass. Only one timing measurement can be active at once,
* so {@link #stopTimer} must be called before the next call to this method.</p>
*
* @throws IllegalStateException if the maximum number of queries are in progress already,
* or the method is called multiple times in a row, or there is
* a GPU error.
*/
public void startTimer() {
nativeStartGlTimer(mNativeContext);
mStartTimeNs = SystemClock.elapsedRealtimeNanos();
}
/**
* Finish a GPU/CPU timing measurement.
*
* <p>Call after finishing all the drawing for a rendering pass. Only one timing measurement can
* be active at once, so {@link #startTimer} must be called before the next call to this
* method.</p>
*
* @throws IllegalStateException if no GL timer is currently started, or there is a GPU
* error.
*/
public void stopTimer() {
// Complete CPU timing
long endTimeNs = SystemClock.elapsedRealtimeNanos();
mCpuDurationsQueue.add(endTimeNs - mStartTimeNs);
// Complete GL timing
nativeStopGlTimer(mNativeContext);
// Poll to see if GL timing results have arrived; if so
// store the results for a frame
long duration = getNextGlDuration();
if (duration > 0) {
mCollectedGpuDurations.add(duration);
mCollectedTimestamps.add(mTimestampQueue.isEmpty() ?
NO_DURATION_YET : mTimestampQueue.poll());
mCollectedCpuDurations.add(mCpuDurationsQueue.isEmpty() ?
NO_DURATION_YET : mCpuDurationsQueue.poll());
}
if (duration == FAILED_TIMING) {
// Discard timestamp and CPU measurement since GPU measurement failed
if (!mTimestampQueue.isEmpty()) {
mTimestampQueue.poll();
}
if (!mCpuDurationsQueue.isEmpty()) {
mCpuDurationsQueue.poll();
}
}
}
/**
* Add a timestamp to a timing measurement. These are queued up and matched to completed
* workload measurements as they become available.
*/
public void addTimestamp(long timestamp) {
mTimestampQueue.add(timestamp);
}
/**
* Get the next available GPU timing measurement.
*
* <p>Since the GPU works asynchronously, the results of a single start/stopGlTimer measurement
* will only be available some time after the {@link #stopTimer} call is made. Poll this method
* until the result becomes available. If multiple start/endTimer measurements are made in a
* row, the results will be available in FIFO order.</p>
*
* @return The measured duration of the GPU workload for the next pending query, or
* {@link #NO_DURATION_YET} if no queries are pending or the next pending query has not
* yet finished, or {@link #FAILED_TIMING} if the GPU was unable to complete the
* measurement.
*
* @throws IllegalStateException If there is a GPU error.
*
*/
private long getNextGlDuration() {
long duration = nativeGetNextGlDuration(mNativeContext);
if (duration > 0) {
mCompletedQueryCount++;
}
return duration;
}
/**
* Returns the number of measurements so far that returned a valid duration
* measurement.
*/
public int getCompletedQueryCount() {
return mCompletedQueryCount;
}
@Override
protected void finalize() {
nativeDeleteContext(mNativeContext);
}
/**
* Create a native performance measurement context.
*
* @param maxQueryCount maximum in-progress queries; must be >= 1.
*/
private static native long nativeCreateContext(int maxQueryCount);
/**
* Delete the native context.
*
* <p>Not safe to call more than once.</p>
*/
private static native void nativeDeleteContext(long contextHandle);
/**
* Query whether the relevant Gl extensions are available for Gl timing
*/
private static native boolean nativeQuerySupport();
/**
* Start a GL timing section.
*
* <p>All GL commands between this method and the next {@link #nativeEndGlTimer} will be
* included in the timing.</p>
*
* <p>Must be called from the same thread as calls to {@link #nativeEndGlTimer} and
* {@link #nativeGetNextGlDuration}.</p>
*
* @throws IllegalStateException if a GL error occurs or start is called repeatedly.
*/
protected static native void nativeStartGlTimer(long contextHandle);
/**
* Finish a GL timing section.
*
* <p>Some time after this call returns, the time the GPU took to
* execute all work submitted between the latest {@link #nativeStartGlTimer} and
* this call, will become available from calling {@link #nativeGetNextGlDuration}.</p>
*
* <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
* {@link #nativeGetNextGlDuration}.</p>
*
* @throws IllegalStateException if a GL error occurs or stop is called before start
*/
protected static native void nativeStopGlTimer(long contextHandle);
/**
* Get the next available GL duration measurement, in nanoseconds.
*
* <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
* {@link #nativeEndGlTimer}.</p>
*
* @return the next GL duration measurement, or {@link #NO_DURATION_YET} if
* no new measurement is available, or {@link #FAILED_TIMING} if timing
* failed for the next duration measurement.
* @throws IllegalStateException if a GL error occurs
*/
protected static native long nativeGetNextGlDuration(long contextHandle);
}

View File

@@ -1,113 +0,0 @@
/*
* 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.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.MessageQueue;
public class RequestHandlerThread extends HandlerThread {
/**
* Ensure that the MessageQueue's idle handler gets run by poking the message queue;
* normally if the message queue is already idle, the idle handler won't get invoked.
*
* <p>Users of this handler thread should ignore this message.</p>
*/
public final static int MSG_POKE_IDLE_HANDLER = -1;
private final ConditionVariable mStarted = new ConditionVariable(false);
private final ConditionVariable mIdle = new ConditionVariable(true);
private Handler.Callback mCallback;
private volatile Handler mHandler;
public RequestHandlerThread(String name, Handler.Callback callback) {
super(name, Thread.MAX_PRIORITY);
mCallback = callback;
}
@Override
protected void onLooperPrepared() {
mHandler = new Handler(getLooper(), mCallback);
mStarted.open();
}
// Blocks until thread has started
public void waitUntilStarted() {
mStarted.block();
}
// May return null if the handler is not set up yet.
public Handler getHandler() {
return mHandler;
}
// Blocks until thread has started
public Handler waitAndGetHandler() {
waitUntilStarted();
return getHandler();
}
// Atomic multi-type message existence check
public boolean hasAnyMessages(int[] what) {
synchronized (mHandler.getLooper().getQueue()) {
for (int i : what) {
if (mHandler.hasMessages(i)) {
return true;
}
}
}
return false;
}
// Atomic multi-type message remove
public void removeMessages(int[] what) {
synchronized (mHandler.getLooper().getQueue()) {
for (int i : what) {
mHandler.removeMessages(i);
}
}
}
private final MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
mIdle.open();
return false;
}
};
// Blocks until thread is idling
public void waitUntilIdle() {
Handler handler = waitAndGetHandler();
MessageQueue queue = handler.getLooper().getQueue();
if (queue.isIdle()) {
return;
}
mIdle.close();
queue.addIdleHandler(mIdleHandler);
// Ensure that the idle handler gets run even if the looper already went idle
handler.sendEmptyMessage(MSG_POKE_IDLE_HANDLER);
if (queue.isIdle()) {
return;
}
mIdle.block();
}
}

View File

@@ -1,283 +0,0 @@
/*
* 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.hardware.camera2.CaptureRequest;
import android.util.Log;
import android.view.Surface;
import java.util.Collection;
import static com.android.internal.util.Preconditions.*;
/**
* Semi-immutable container for a single capture request and associated information,
* the only mutable characteristic of this container is whether or not is has been
* marked as "failed" using {@code #failRequest}.
*/
public class RequestHolder {
private static final String TAG = "RequestHolder";
private final boolean mRepeating;
private final CaptureRequest mRequest;
private final int mRequestId;
private final int mSubsequeceId;
private final long mFrameNumber;
private final int mNumJpegTargets;
private final int mNumPreviewTargets;
private volatile boolean mFailed = false;
private boolean mOutputAbandoned = false;
private final Collection<Long> mJpegSurfaceIds;
/**
* A builder class for {@link RequestHolder} objects.
*
* <p>
* This allows per-request queries to be cached for repeating {@link CaptureRequest} objects.
* </p>
*/
public final static class Builder {
private final int mRequestId;
private final int mSubsequenceId;
private final CaptureRequest mRequest;
private final boolean mRepeating;
private final int mNumJpegTargets;
private final int mNumPreviewTargets;
private final Collection<Long> mJpegSurfaceIds;
/**
* Construct a new {@link Builder} to generate {@link RequestHolder} objects.
*
* @param requestId the ID to set in {@link RequestHolder} objects.
* @param subsequenceId the sequence ID to set in {@link RequestHolder} objects.
* @param request the original {@link CaptureRequest} to set in {@link RequestHolder}
* objects.
* @param repeating {@code true} if the request is repeating.
*/
public Builder(int requestId, int subsequenceId, CaptureRequest request,
boolean repeating, Collection<Long> jpegSurfaceIds) {
checkNotNull(request, "request must not be null");
mRequestId = requestId;
mSubsequenceId = subsequenceId;
mRequest = request;
mRepeating = repeating;
mJpegSurfaceIds = jpegSurfaceIds;
mNumJpegTargets = numJpegTargets(mRequest);
mNumPreviewTargets = numPreviewTargets(mRequest);
}
/**
* Returns true if the given surface requires jpeg buffers.
*
* @param s a {@link android.view.Surface} to check.
* @return true if the surface requires a jpeg buffer.
*/
private boolean jpegType(Surface s)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
return LegacyCameraDevice.containsSurfaceId(s, mJpegSurfaceIds);
}
/**
* Returns true if the given surface requires non-jpeg buffer types.
*
* <p>
* "Jpeg buffer" refers to the buffers returned in the jpeg
* {@link android.hardware.Camera.PictureCallback}. Non-jpeg buffers are created using a tee
* of the preview stream drawn to the surface
* set via {@link android.hardware.Camera#setPreviewDisplay(android.view.SurfaceHolder)} or
* equivalent methods.
* </p>
* @param s a {@link android.view.Surface} to check.
* @return true if the surface requires a non-jpeg buffer type.
*/
private boolean previewType(Surface s)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
return !jpegType(s);
}
/**
* Returns the number of surfaces targeted by the request that require jpeg buffers.
*/
private int numJpegTargets(CaptureRequest request) {
int count = 0;
for (Surface s : request.getTargets()) {
try {
if (jpegType(s)) {
++count;
}
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.d(TAG, "Surface abandoned, skipping...", e);
}
}
return count;
}
/**
* Returns the number of surfaces targeted by the request that require non-jpeg buffers.
*/
private int numPreviewTargets(CaptureRequest request) {
int count = 0;
for (Surface s : request.getTargets()) {
try {
if (previewType(s)) {
++count;
}
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.d(TAG, "Surface abandoned, skipping...", e);
}
}
return count;
}
/**
* Build a new {@link RequestHolder} using with parameters generated from this
* {@link Builder}.
*
* @param frameNumber the {@code framenumber} to generate in the {@link RequestHolder}.
* @return a {@link RequestHolder} constructed with the {@link Builder}'s parameters.
*/
public RequestHolder build(long frameNumber) {
return new RequestHolder(mRequestId, mSubsequenceId, mRequest, mRepeating, frameNumber,
mNumJpegTargets, mNumPreviewTargets, mJpegSurfaceIds);
}
}
private RequestHolder(int requestId, int subsequenceId, CaptureRequest request,
boolean repeating, long frameNumber, int numJpegTargets,
int numPreviewTargets, Collection<Long> jpegSurfaceIds) {
mRepeating = repeating;
mRequest = request;
mRequestId = requestId;
mSubsequeceId = subsequenceId;
mFrameNumber = frameNumber;
mNumJpegTargets = numJpegTargets;
mNumPreviewTargets = numPreviewTargets;
mJpegSurfaceIds = jpegSurfaceIds;
}
/**
* Return the request id for the contained {@link CaptureRequest}.
*/
public int getRequestId() {
return mRequestId;
}
/**
* Returns true if the contained request is repeating.
*/
public boolean isRepeating() {
return mRepeating;
}
/**
* Return the subsequence id for this request.
*/
public int getSubsequeceId() {
return mSubsequeceId;
}
/**
* Returns the frame number for this request.
*/
public long getFrameNumber() {
return mFrameNumber;
}
/**
* Returns the contained request.
*/
public CaptureRequest getRequest() {
return mRequest;
}
/**
* Returns a read-only collection of the surfaces targeted by the contained request.
*/
public Collection<Surface> getHolderTargets() {
return getRequest().getTargets();
}
/**
* Returns true if any of the surfaces targeted by the contained request require jpeg buffers.
*/
public boolean hasJpegTargets() {
return mNumJpegTargets > 0;
}
/**
* Returns true if any of the surfaces targeted by the contained request require a
* non-jpeg buffer type.
*/
public boolean hasPreviewTargets(){
return mNumPreviewTargets > 0;
}
/**
* Return the number of jpeg-type surfaces targeted by this request.
*/
public int numJpegTargets() {
return mNumJpegTargets;
}
/**
* Return the number of non-jpeg-type surfaces targeted by this request.
*/
public int numPreviewTargets() {
return mNumPreviewTargets;
}
/**
* Returns true if the given surface requires jpeg buffers.
*
* @param s a {@link android.view.Surface} to check.
* @return true if the surface requires a jpeg buffer.
*/
public boolean jpegType(Surface s)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
return LegacyCameraDevice.containsSurfaceId(s, mJpegSurfaceIds);
}
/**
* Mark this request as failed.
*/
public void failRequest() {
Log.w(TAG, "Capture failed for request: " + getRequestId());
mFailed = true;
}
/**
* Return {@code true} if this request failed.
*/
public boolean requestFailed() {
return mFailed;
}
/**
* Mark at least one of this request's output surfaces is abandoned.
*/
public void setOutputAbandoned() {
mOutputAbandoned = true;
}
/**
* Return if any of this request's output surface is abandoned.
*/
public boolean isOutputAbandoned() {
return mOutputAbandoned;
}
}

View File

@@ -1,174 +0,0 @@
/*
* 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.hardware.camera2.CaptureRequest;
import android.hardware.camera2.utils.SubmitInfo;
import android.util.Log;
import java.util.ArrayDeque;
import java.util.List;
/**
* A queue of bursts of requests.
*
* <p>This queue maintains the count of frames that have been produced, and is thread safe.</p>
*/
public class RequestQueue {
private static final String TAG = "RequestQueue";
public static final long INVALID_FRAME = -1;
private BurstHolder mRepeatingRequest = null;
private final ArrayDeque<BurstHolder> mRequestQueue = new ArrayDeque<BurstHolder>();
private long mCurrentFrameNumber = 0;
private long mCurrentRepeatingFrameNumber = INVALID_FRAME;
private int mCurrentRequestId = 0;
private final List<Long> mJpegSurfaceIds;
public final class RequestQueueEntry {
private final BurstHolder mBurstHolder;
private final Long mFrameNumber;
private final boolean mQueueEmpty;
public BurstHolder getBurstHolder() {
return mBurstHolder;
}
public Long getFrameNumber() {
return mFrameNumber;
}
public boolean isQueueEmpty() {
return mQueueEmpty;
}
public RequestQueueEntry(BurstHolder burstHolder, Long frameNumber, boolean queueEmpty) {
mBurstHolder = burstHolder;
mFrameNumber = frameNumber;
mQueueEmpty = queueEmpty;
}
}
public RequestQueue(List<Long> jpegSurfaceIds) {
mJpegSurfaceIds = jpegSurfaceIds;
}
/**
* Return and remove the next burst on the queue.
*
* <p>If a repeating burst is returned, it will not be removed.</p>
*
* @return an entry containing the next burst, the current frame number, and flag about whether
* request queue becomes empty. Null if no burst exists.
*/
public synchronized RequestQueueEntry getNext() {
BurstHolder next = mRequestQueue.poll();
boolean queueEmptied = (next != null && mRequestQueue.size() == 0);
if (next == null && mRepeatingRequest != null) {
next = mRepeatingRequest;
mCurrentRepeatingFrameNumber = mCurrentFrameNumber +
next.getNumberOfRequests();
}
if (next == null) {
return null;
}
RequestQueueEntry ret = new RequestQueueEntry(next, mCurrentFrameNumber, queueEmptied);
mCurrentFrameNumber += next.getNumberOfRequests();
return ret;
}
/**
* Cancel a repeating request.
*
* @param requestId the id of the repeating request to cancel.
* @return the last frame to be returned from the HAL for the given repeating request, or
* {@code INVALID_FRAME} if none exists.
*/
public synchronized long stopRepeating(int requestId) {
long ret = INVALID_FRAME;
if (mRepeatingRequest != null && mRepeatingRequest.getRequestId() == requestId) {
mRepeatingRequest = null;
ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
mCurrentRepeatingFrameNumber - 1;
mCurrentRepeatingFrameNumber = INVALID_FRAME;
Log.i(TAG, "Repeating capture request cancelled.");
} else {
Log.e(TAG, "cancel failed: no repeating request exists for request id: " + requestId);
}
return ret;
}
/**
* Cancel a repeating request.
*
* @return the last frame to be returned from the HAL for the given repeating request, or
* {@code INVALID_FRAME} if none exists.
*/
public synchronized long stopRepeating() {
if (mRepeatingRequest == null) {
Log.e(TAG, "cancel failed: no repeating request exists.");
return INVALID_FRAME;
}
return stopRepeating(mRepeatingRequest.getRequestId());
}
/**
* Add a the given burst to the queue.
*
* <p>If the burst is repeating, replace the current repeating burst.</p>
*
* @param requests the burst of requests to add to the queue.
* @param repeating true if the burst is repeating.
* @return the submission info, including the new request id, and the last frame number, which
* 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.
*/
public synchronized SubmitInfo submit(CaptureRequest[] requests, boolean repeating) {
int requestId = mCurrentRequestId++;
BurstHolder burst = new BurstHolder(requestId, repeating, requests, mJpegSurfaceIds);
long lastFrame = INVALID_FRAME;
if (burst.isRepeating()) {
Log.i(TAG, "Repeating capture request set.");
if (mRepeatingRequest != null) {
lastFrame = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
mCurrentRepeatingFrameNumber - 1;
}
mCurrentRepeatingFrameNumber = INVALID_FRAME;
mRepeatingRequest = burst;
} else {
mRequestQueue.offer(burst);
lastFrame = calculateLastFrame(burst.getRequestId());
}
SubmitInfo info = new SubmitInfo(requestId, lastFrame);
return info;
}
private long calculateLastFrame(int requestId) {
long total = mCurrentFrameNumber;
for (BurstHolder b : mRequestQueue) {
total += b.getNumberOfRequests();
if (b.getRequestId() == requestId) {
return total - 1;
}
}
throw new IllegalStateException(
"At least one request must be in the queue to calculate frame number");
}
}

View File

@@ -1,72 +0,0 @@
/*
* 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.hardware.Camera;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.android.internal.util.Preconditions.*;
/**
* Comparator for api1 {@link Camera.Size} objects by the area.
*
* <p>This comparator totally orders by rectangle area. Tie-breaks on width.</p>
*/
@SuppressWarnings("deprecation")
public class SizeAreaComparator implements Comparator<Camera.Size> {
/**
* {@inheritDoc}
*/
@Override
public int compare(Camera.Size size, Camera.Size size2) {
checkNotNull(size, "size must not be null");
checkNotNull(size2, "size2 must not be null");
if (size.equals(size2)) {
return 0;
}
long width = size.width;
long width2 = size2.width;
long area = width * size.height;
long area2 = width2 * size2.height;
if (area == area2) {
return (width > width2) ? 1 : -1;
}
return (area > area2) ? 1 : -1;
}
/**
* Get the largest api1 {@code Camera.Size} from the list by comparing each size's area
* by each other using {@link SizeAreaComparator}.
*
* @param sizes a non-{@code null} list of non-{@code null} sizes
* @return a non-{@code null} size
*
* @throws NullPointerException if {@code sizes} or any elements in it were {@code null}
*/
public static Camera.Size findLargestByArea(List<Camera.Size> sizes) {
checkNotNull(sizes, "sizes must not be null");
return Collections.max(sizes, new SizeAreaComparator());
}
}

View File

@@ -1,882 +0,0 @@
/*
* 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.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCharacteristics;
import android.os.Environment;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.view.Surface;
import android.os.SystemProperties;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
/**
* A renderer class that manages the GL state, and can draw a frame into a set of output
* {@link Surface}s.
*/
public class SurfaceTextureRenderer {
private static final String TAG = SurfaceTextureRenderer.class.getSimpleName();
private static final boolean DEBUG = false;
private static final int EGL_RECORDABLE_ANDROID = 0x3142; // from EGL/eglext.h
private static final int GL_MATRIX_SIZE = 16;
private static final int VERTEX_POS_SIZE = 3;
private static final int VERTEX_UV_SIZE = 2;
private static final int EGL_COLOR_BITLENGTH = 8;
private static final int GLES_VERSION = 2;
private static final int PBUFFER_PIXEL_BYTES = 4;
private static final int FLIP_TYPE_NONE = 0;
private static final int FLIP_TYPE_HORIZONTAL = 1;
private static final int FLIP_TYPE_VERTICAL = 2;
private static final int FLIP_TYPE_BOTH = FLIP_TYPE_HORIZONTAL | FLIP_TYPE_VERTICAL;
private static final DateTimeFormatter LOG_NAME_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss", Locale.ROOT);
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
private EGLConfig mConfigs;
private class EGLSurfaceHolder {
Surface surface;
EGLSurface eglSurface;
int width;
int height;
}
private List<EGLSurfaceHolder> mSurfaces = new ArrayList<EGLSurfaceHolder>();
private List<EGLSurfaceHolder> mConversionSurfaces = new ArrayList<EGLSurfaceHolder>();
private ByteBuffer mPBufferPixels;
// Hold this to avoid GC
private volatile SurfaceTexture mSurfaceTexture;
private static final int FLOAT_SIZE_BYTES = 4;
private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
// Sampling is mirrored across the horizontal axis
private static final float[] sHorizontalFlipTriangleVertices = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 1.f, 0.f,
1.0f, -1.0f, 0, 0.f, 0.f,
-1.0f, 1.0f, 0, 1.f, 1.f,
1.0f, 1.0f, 0, 0.f, 1.f,
};
// Sampling is mirrored across the vertical axis
private static final float[] sVerticalFlipTriangleVertices = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 0.f, 1.f,
1.0f, -1.0f, 0, 1.f, 1.f,
-1.0f, 1.0f, 0, 0.f, 0.f,
1.0f, 1.0f, 0, 1.f, 0.f,
};
// Sampling is mirrored across the both axes
private static final float[] sBothFlipTriangleVertices = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 1.f, 1.f,
1.0f, -1.0f, 0, 0.f, 1.f,
-1.0f, 1.0f, 0, 1.f, 0.f,
1.0f, 1.0f, 0, 0.f, 0.f,
};
// Sampling is 1:1 for a straight copy for the back camera
private static final float[] sRegularTriangleVertices = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 0.f, 0.f,
1.0f, -1.0f, 0, 1.f, 0.f,
-1.0f, 1.0f, 0, 0.f, 1.f,
1.0f, 1.0f, 0, 1.f, 1.f,
};
private FloatBuffer mRegularTriangleVertices;
private FloatBuffer mHorizontalFlipTriangleVertices;
private FloatBuffer mVerticalFlipTriangleVertices;
private FloatBuffer mBothFlipTriangleVertices;
private final int mFacing;
/**
* As used in this file, this vertex shader maps a unit square to the view, and
* tells the fragment shader to interpolate over it. Each surface pixel position
* is mapped to a 2D homogeneous texture coordinate of the form (s, t, 0, 1) with
* s and t in the inclusive range [0, 1], and the matrix from
* {@link SurfaceTexture#getTransformMatrix(float[])} is used to map this
* coordinate to a texture location.
*/
private static final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;\n" +
"uniform mat4 uSTMatrix;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
"}\n";
/**
* This fragment shader simply draws the color in the 2D texture at
* the location from the {@code VERTEX_SHADER}.
*/
private static final String FRAGMENT_SHADER =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"varying vec2 vTextureCoord;\n" +
"uniform samplerExternalOES sTexture;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
"}\n";
private float[] mMVPMatrix = new float[GL_MATRIX_SIZE];
private float[] mSTMatrix = new float[GL_MATRIX_SIZE];
private int mProgram;
private int mTextureID = 0;
private int muMVPMatrixHandle;
private int muSTMatrixHandle;
private int maPositionHandle;
private int maTextureHandle;
private PerfMeasurement mPerfMeasurer = null;
private static final String LEGACY_PERF_PROPERTY = "persist.camera.legacy_perf";
public SurfaceTextureRenderer(int facing) {
mFacing = facing;
mRegularTriangleVertices = ByteBuffer.allocateDirect(sRegularTriangleVertices.length *
FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
mRegularTriangleVertices.put(sRegularTriangleVertices).position(0);
mHorizontalFlipTriangleVertices = ByteBuffer.allocateDirect(
sHorizontalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
order(ByteOrder.nativeOrder()).asFloatBuffer();
mHorizontalFlipTriangleVertices.put(sHorizontalFlipTriangleVertices).position(0);
mVerticalFlipTriangleVertices = ByteBuffer.allocateDirect(
sVerticalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
order(ByteOrder.nativeOrder()).asFloatBuffer();
mVerticalFlipTriangleVertices.put(sVerticalFlipTriangleVertices).position(0);
mBothFlipTriangleVertices = ByteBuffer.allocateDirect(
sBothFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
order(ByteOrder.nativeOrder()).asFloatBuffer();
mBothFlipTriangleVertices.put(sBothFlipTriangleVertices).position(0);
Matrix.setIdentityM(mSTMatrix, 0);
}
private int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
checkGlError("glCreateShader type=" + shaderType);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "Could not compile shader " + shaderType + ":");
Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
// TODO: handle this more gracefully
throw new IllegalStateException("Could not compile shader " + shaderType);
}
return shader;
}
private int createProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
int program = GLES20.glCreateProgram();
checkGlError("glCreateProgram");
if (program == 0) {
Log.e(TAG, "Could not create program");
}
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Could not link program: ");
Log.e(TAG, GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
// TODO: handle this more gracefully
throw new IllegalStateException("Could not link program");
}
return program;
}
private void drawFrame(SurfaceTexture st, int width, int height, int flipType)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
checkGlError("onDrawFrame start");
st.getTransformMatrix(mSTMatrix);
Matrix.setIdentityM(mMVPMatrix, /*smOffset*/0);
// Find intermediate buffer dimensions
Size dimens;
try {
dimens = LegacyCameraDevice.getTextureSize(st);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
// Should never hit this.
throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
}
float texWidth = dimens.getWidth();
float texHeight = dimens.getHeight();
if (texWidth <= 0 || texHeight <= 0) {
throw new IllegalStateException("Illegal intermediate texture with dimension of 0");
}
// Letterbox or pillar-box output dimensions into intermediate dimensions.
RectF intermediate = new RectF(/*left*/0, /*top*/0, /*right*/texWidth, /*bottom*/texHeight);
RectF output = new RectF(/*left*/0, /*top*/0, /*right*/width, /*bottom*/height);
android.graphics.Matrix boxingXform = new android.graphics.Matrix();
boxingXform.setRectToRect(output, intermediate, android.graphics.Matrix.ScaleToFit.CENTER);
boxingXform.mapRect(output);
// Find scaling factor from pillar-boxed/letter-boxed output dimensions to intermediate
// buffer dimensions.
float scaleX = intermediate.width() / output.width();
float scaleY = intermediate.height() / output.height();
// Intermediate texture is implicitly scaled to 'fill' the output dimensions in clip space
// coordinates in the shader. To avoid stretching, we need to scale the larger dimension
// of the intermediate buffer so that the output buffer is actually letter-boxed
// or pillar-boxed into the intermediate buffer after clipping.
Matrix.scaleM(mMVPMatrix, /*offset*/0, /*x*/scaleX, /*y*/scaleY, /*z*/1);
if (DEBUG) {
Log.d(TAG, "Scaling factors (S_x = " + scaleX + ",S_y = " + scaleY + ") used for " +
width + "x" + height + " surface, intermediate buffer size is " + texWidth +
"x" + texHeight);
}
// Set viewport to be output buffer dimensions
GLES20.glViewport(0, 0, width, height);
if (DEBUG) {
GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
}
GLES20.glUseProgram(mProgram);
checkGlError("glUseProgram");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
FloatBuffer triangleVertices;
switch(flipType) {
case FLIP_TYPE_HORIZONTAL:
triangleVertices = mHorizontalFlipTriangleVertices;
break;
case FLIP_TYPE_VERTICAL:
triangleVertices = mVerticalFlipTriangleVertices;
break;
case FLIP_TYPE_BOTH:
triangleVertices = mBothFlipTriangleVertices;
break;
default:
triangleVertices = mRegularTriangleVertices;
break;
}
triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(maPositionHandle, VERTEX_POS_SIZE, GLES20.GL_FLOAT,
/*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
checkGlError("glVertexAttribPointer maPosition");
GLES20.glEnableVertexAttribArray(maPositionHandle);
checkGlError("glEnableVertexAttribArray maPositionHandle");
triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
GLES20.glVertexAttribPointer(maTextureHandle, VERTEX_UV_SIZE, GLES20.GL_FLOAT,
/*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
checkGlError("glVertexAttribPointer maTextureHandle");
GLES20.glEnableVertexAttribArray(maTextureHandle);
checkGlError("glEnableVertexAttribArray maTextureHandle");
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, /*count*/ 1, /*transpose*/ false, mMVPMatrix,
/*offset*/ 0);
GLES20.glUniformMatrix4fv(muSTMatrixHandle, /*count*/ 1, /*transpose*/ false, mSTMatrix,
/*offset*/ 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4);
checkGlDrawError("glDrawArrays");
}
/**
* Initializes GL state. Call this after the EGL surface has been created and made current.
*/
private void initializeGLState() {
mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
if (mProgram == 0) {
throw new IllegalStateException("failed creating program");
}
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
checkGlError("glGetAttribLocation aPosition");
if (maPositionHandle == -1) {
throw new IllegalStateException("Could not get attrib location for aPosition");
}
maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
checkGlError("glGetAttribLocation aTextureCoord");
if (maTextureHandle == -1) {
throw new IllegalStateException("Could not get attrib location for aTextureCoord");
}
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
checkGlError("glGetUniformLocation uMVPMatrix");
if (muMVPMatrixHandle == -1) {
throw new IllegalStateException("Could not get attrib location for uMVPMatrix");
}
muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
checkGlError("glGetUniformLocation uSTMatrix");
if (muSTMatrixHandle == -1) {
throw new IllegalStateException("Could not get attrib location for uSTMatrix");
}
int[] textures = new int[1];
GLES20.glGenTextures(/*n*/ 1, textures, /*offset*/ 0);
mTextureID = textures[0];
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
checkGlError("glBindTexture mTextureID");
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE);
checkGlError("glTexParameter");
}
private int getTextureId() {
return mTextureID;
}
private void clearState() {
mSurfaces.clear();
for (EGLSurfaceHolder holder : mConversionSurfaces) {
try {
LegacyCameraDevice.disconnectSurface(holder.surface);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, skipping...", e);
}
}
mConversionSurfaces.clear();
mPBufferPixels = null;
if (mSurfaceTexture != null) {
mSurfaceTexture.release();
}
mSurfaceTexture = null;
}
private void configureEGLContext() {
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
throw new IllegalStateException("No EGL14 display");
}
int[] version = new int[2];
if (!EGL14.eglInitialize(mEGLDisplay, version, /*offset*/ 0, version, /*offset*/ 1)) {
throw new IllegalStateException("Cannot initialize EGL14");
}
int[] attribList = {
EGL14.EGL_RED_SIZE, EGL_COLOR_BITLENGTH,
EGL14.EGL_GREEN_SIZE, EGL_COLOR_BITLENGTH,
EGL14.EGL_BLUE_SIZE, EGL_COLOR_BITLENGTH,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL_RECORDABLE_ANDROID, 1,
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT | EGL14.EGL_WINDOW_BIT,
EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
EGL14.eglChooseConfig(mEGLDisplay, attribList, /*offset*/ 0, configs, /*offset*/ 0,
configs.length, numConfigs, /*offset*/ 0);
checkEglError("eglCreateContext RGB888+recordable ES2");
mConfigs = configs[0];
int[] attrib_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION,
EGL14.EGL_NONE
};
mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
attrib_list, /*offset*/ 0);
checkEglError("eglCreateContext");
if(mEGLContext == EGL14.EGL_NO_CONTEXT) {
throw new IllegalStateException("No EGLContext could be made");
}
}
private void configureEGLOutputSurfaces(Collection<EGLSurfaceHolder> surfaces) {
if (surfaces == null || surfaces.size() == 0) {
throw new IllegalStateException("No Surfaces were provided to draw to");
}
int[] surfaceAttribs = {
EGL14.EGL_NONE
};
for (EGLSurfaceHolder holder : surfaces) {
holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs,
holder.surface, surfaceAttribs, /*offset*/ 0);
checkEglError("eglCreateWindowSurface");
}
}
private void configureEGLPbufferSurfaces(Collection<EGLSurfaceHolder> surfaces) {
if (surfaces == null || surfaces.size() == 0) {
throw new IllegalStateException("No Surfaces were provided to draw to");
}
int maxLength = 0;
for (EGLSurfaceHolder holder : surfaces) {
int length = holder.width * holder.height;
// Find max surface size, ensure PBuffer can hold this many pixels
maxLength = (length > maxLength) ? length : maxLength;
int[] surfaceAttribs = {
EGL14.EGL_WIDTH, holder.width,
EGL14.EGL_HEIGHT, holder.height,
EGL14.EGL_NONE
};
holder.eglSurface =
EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
checkEglError("eglCreatePbufferSurface");
}
mPBufferPixels = ByteBuffer.allocateDirect(maxLength * PBUFFER_PIXEL_BYTES)
.order(ByteOrder.nativeOrder());
}
private void releaseEGLContext() {
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT);
dumpGlTiming();
if (mSurfaces != null) {
for (EGLSurfaceHolder holder : mSurfaces) {
if (holder.eglSurface != null) {
EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
}
}
}
if (mConversionSurfaces != null) {
for (EGLSurfaceHolder holder : mConversionSurfaces) {
if (holder.eglSurface != null) {
EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
}
}
}
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
EGL14.eglReleaseThread();
EGL14.eglTerminate(mEGLDisplay);
}
mConfigs = null;
mEGLDisplay = EGL14.EGL_NO_DISPLAY;
mEGLContext = EGL14.EGL_NO_CONTEXT;
clearState();
}
private void makeCurrent(EGLSurface surface)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
EGL14.eglMakeCurrent(mEGLDisplay, surface, surface, mEGLContext);
checkEglDrawError("makeCurrent");
}
private boolean swapBuffers(EGLSurface surface)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
boolean result = EGL14.eglSwapBuffers(mEGLDisplay, surface);
int error = EGL14.eglGetError();
switch (error) {
case EGL14.EGL_SUCCESS:
return result;
// Check for an abandoned buffer queue, or other error conditions out
// of the user's control.
//
// From the EGL 1.4 spec (2013-12-04), Section 3.9.4 Posting Errors:
//
// If eglSwapBuffers is called and the native window associated with
// surface is no longer valid, an EGL_BAD_NATIVE_WINDOW error is
// generated.
//
// We also interpret EGL_BAD_SURFACE as indicating an abandoned
// surface, even though the EGL spec does not document it as such, for
// backwards compatibility with older versions of this file.
case EGL14.EGL_BAD_NATIVE_WINDOW:
case EGL14.EGL_BAD_SURFACE:
throw new LegacyExceptionUtils.BufferQueueAbandonedException();
default:
throw new IllegalStateException(
"swapBuffers: EGL error: 0x" + Integer.toHexString(error));
}
}
private void checkEglDrawError(String msg)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
int error;
if ((error = EGL14.eglGetError()) == EGL14.EGL_BAD_NATIVE_WINDOW) {
throw new LegacyExceptionUtils.BufferQueueAbandonedException();
}
if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
throw new IllegalStateException(msg + ": EGL error: 0x" + Integer.toHexString(error));
}
}
private void checkEglError(String msg) {
int error;
if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
throw new IllegalStateException(msg + ": EGL error: 0x" + Integer.toHexString(error));
}
}
private void checkGlError(String msg) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
throw new IllegalStateException(
msg + ": GLES20 error: 0x" + Integer.toHexString(error));
}
}
private void checkGlDrawError(String msg)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
int error;
boolean surfaceAbandoned = false;
boolean glError = false;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
if (error == GLES20.GL_OUT_OF_MEMORY) {
surfaceAbandoned = true;
} else {
glError = true;
}
}
if (glError) {
throw new IllegalStateException(
msg + ": GLES20 error: 0x" + Integer.toHexString(error));
}
if (surfaceAbandoned) {
throw new LegacyExceptionUtils.BufferQueueAbandonedException();
}
}
/**
* Save a measurement dump to disk, in
* {@code /sdcard/CameraLegacy/durations_<time>_<width1>x<height1>_...txt}
*/
private void dumpGlTiming() {
if (mPerfMeasurer == null) return;
File legacyStorageDir = new File(Environment.getExternalStorageDirectory(), "CameraLegacy");
if (!legacyStorageDir.exists()){
if (!legacyStorageDir.mkdirs()){
Log.e(TAG, "Failed to create directory for data dump");
return;
}
}
StringBuilder path = new StringBuilder(legacyStorageDir.getPath());
path.append(File.separator);
path.append("durations_");
path.append(formatTimestamp(System.currentTimeMillis()));
path.append("_S");
for (EGLSurfaceHolder surface : mSurfaces) {
path.append(String.format("_%d_%d", surface.width, surface.height));
}
path.append("_C");
for (EGLSurfaceHolder surface : mConversionSurfaces) {
path.append(String.format("_%d_%d", surface.width, surface.height));
}
path.append(".txt");
mPerfMeasurer.dumpPerformanceData(path.toString());
}
private static String formatTimestamp(long timeMillis) {
// This is a replacement for {@link Time#format2445()} that doesn't suffer from Y2038
// issues.
Instant instant = Instant.ofEpochMilli(timeMillis);
ZoneId zoneId = ZoneId.systemDefault();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zoneId);
return LOG_NAME_TIME_FORMATTER.format(localDateTime);
}
private void setupGlTiming() {
if (PerfMeasurement.isGlTimingSupported()) {
Log.d(TAG, "Enabling GL performance measurement");
mPerfMeasurer = new PerfMeasurement();
} else {
Log.d(TAG, "GL performance measurement not supported on this device");
mPerfMeasurer = null;
}
}
private void beginGlTiming() {
if (mPerfMeasurer == null) return;
mPerfMeasurer.startTimer();
}
private void addGlTimestamp(long timestamp) {
if (mPerfMeasurer == null) return;
mPerfMeasurer.addTimestamp(timestamp);
}
private void endGlTiming() {
if (mPerfMeasurer == null) return;
mPerfMeasurer.stopTimer();
}
/**
* Return the surface texture to draw to - this is the texture use to when producing output
* surface buffers.
*
* @return a {@link SurfaceTexture}.
*/
public SurfaceTexture getSurfaceTexture() {
return mSurfaceTexture;
}
/**
* Set a collection of output {@link Surface}s that can be drawn to.
*
* @param surfaces a {@link Collection} of surfaces.
*/
public void configureSurfaces(Collection<Pair<Surface, Size>> surfaces) {
releaseEGLContext();
if (surfaces == null || surfaces.size() == 0) {
Log.w(TAG, "No output surfaces configured for GL drawing.");
return;
}
for (Pair<Surface, Size> p : surfaces) {
Surface s = p.first;
Size surfaceSize = p.second;
// If pixel conversions aren't handled by egl, use a pbuffer
try {
EGLSurfaceHolder holder = new EGLSurfaceHolder();
holder.surface = s;
holder.width = surfaceSize.getWidth();
holder.height = surfaceSize.getHeight();
if (LegacyCameraDevice.needsConversion(s)) {
mConversionSurfaces.add(holder);
// LegacyCameraDevice is the producer of surfaces if it's not handled by EGL,
// so LegacyCameraDevice needs to connect to the surfaces.
LegacyCameraDevice.connectSurface(s);
} else {
mSurfaces.add(holder);
}
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, skipping configuration... ", e);
}
}
// Set up egl display
configureEGLContext();
// Set up regular egl surfaces if needed
if (mSurfaces.size() > 0) {
configureEGLOutputSurfaces(mSurfaces);
}
// Set up pbuffer surface if needed
if (mConversionSurfaces.size() > 0) {
configureEGLPbufferSurfaces(mConversionSurfaces);
}
try {
makeCurrent((mSurfaces.size() > 0) ? mSurfaces.get(0).eglSurface :
mConversionSurfaces.get(0).eglSurface);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, skipping configuration... ", e);
}
initializeGLState();
mSurfaceTexture = new SurfaceTexture(getTextureId());
// Set up performance tracking if enabled
if (SystemProperties.getBoolean(LEGACY_PERF_PROPERTY, false)) {
setupGlTiming();
}
}
/**
* Draw the current buffer in the {@link SurfaceTexture} returned from
* {@link #getSurfaceTexture()} into the set of target {@link Surface}s
* in the next request from the given {@link CaptureCollector}, or drop
* the frame if none is available.
*
* <p>
* Any {@link Surface}s targeted must be a subset of the {@link Surface}s
* set in the last {@link #configureSurfaces(java.util.Collection)} call.
* </p>
*
* @param targetCollector the surfaces to draw to.
*/
public void drawIntoSurfaces(CaptureCollector targetCollector) {
if ((mSurfaces == null || mSurfaces.size() == 0)
&& (mConversionSurfaces == null || mConversionSurfaces.size() == 0)) {
return;
}
boolean doTiming = targetCollector.hasPendingPreviewCaptures();
checkGlError("before updateTexImage");
if (doTiming) {
beginGlTiming();
}
mSurfaceTexture.updateTexImage();
long timestamp = mSurfaceTexture.getTimestamp();
Pair<RequestHolder, Long> captureHolder = targetCollector.previewCaptured(timestamp);
// No preview request queued, drop frame.
if (captureHolder == null) {
if (DEBUG) {
Log.d(TAG, "Dropping preview frame.");
}
if (doTiming) {
endGlTiming();
}
return;
}
RequestHolder request = captureHolder.first;
Collection<Surface> targetSurfaces = request.getHolderTargets();
if (doTiming) {
addGlTimestamp(timestamp);
}
List<Long> targetSurfaceIds = new ArrayList();
try {
targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
request.setOutputAbandoned();
}
for (EGLSurfaceHolder holder : mSurfaces) {
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
try{
LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
holder.height);
makeCurrent(holder.eglSurface);
LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
drawFrame(mSurfaceTexture, holder.width, holder.height,
(mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
FLIP_TYPE_HORIZONTAL : FLIP_TYPE_NONE);
swapBuffers(holder.eglSurface);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
request.setOutputAbandoned();
}
}
}
for (EGLSurfaceHolder holder : mConversionSurfaces) {
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
// glReadPixels reads from the bottom of the buffer, so add an extra vertical flip
try {
makeCurrent(holder.eglSurface);
drawFrame(mSurfaceTexture, holder.width, holder.height,
(mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
FLIP_TYPE_BOTH : FLIP_TYPE_VERTICAL);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
// Should never hit this.
throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
}
mPBufferPixels.clear();
GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
checkGlError("glReadPixels");
try {
int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
holder.height);
LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
holder.width, holder.height, format);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
request.setOutputAbandoned();
}
}
}
targetCollector.previewProduced();
if (doTiming) {
endGlTiming();
}
}
/**
* Clean up the current GL context.
*/
public void cleanupEGLContext() {
releaseEGLContext();
}
/**
* Drop all current GL operations on the floor.
*/
public void flush() {
// TODO: implement flush
Log.e(TAG, "Flush not yet implemented.");
}
}

View File

@@ -1,3 +0,0 @@
<body>
{@hide}
</body>

View File

@@ -24,7 +24,6 @@ import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.legacy.LegacyCameraDevice;
import android.hardware.camera2.utils.HashCodeHelpers;
import android.hardware.camera2.utils.SurfaceUtils;
import android.util.Range;
@@ -69,6 +68,8 @@ public final class StreamConfigurationMap {
private static final String TAG = "StreamConfigurationMap";
private static final int MAX_DIMEN_FOR_ROUNDING = 1920; // maximum allowed width for rounding
/**
* Create a new {@link StreamConfigurationMap}.
*
@@ -568,7 +569,7 @@ public final class StreamConfigurationMap {
if (config.getSize().equals(surfaceSize)) {
return true;
} else if (isFlexible &&
(config.getSize().getWidth() <= LegacyCameraDevice.MAX_DIMEN_FOR_ROUNDING)) {
(config.getSize().getWidth() <= MAX_DIMEN_FOR_ROUNDING)) {
return true;
}
}

View File

@@ -16,10 +16,14 @@
package android.hardware.camera2.utils;
import static android.system.OsConstants.EINVAL;
import static com.android.internal.util.Preconditions.checkNotNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.ImageFormat;
import android.hardware.camera2.legacy.LegacyCameraDevice;
import android.hardware.camera2.legacy.LegacyExceptionUtils.BufferQueueAbandonedException;
import android.graphics.PixelFormat;
import android.hardware.HardwareBuffer;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.util.Range;
import android.util.Size;
@@ -35,6 +39,15 @@ import java.util.List;
*/
public class SurfaceUtils {
// Usage flags not yet included in HardwareBuffer
private static final int USAGE_RENDERSCRIPT = 0x00100000;
private static final int USAGE_HW_COMPOSER = 0x00000800;
// Image formats not yet included in PixelFormat
private static final int BGRA_8888 = 0x5;
private static final int BAD_VALUE = -EINVAL;
/**
* Check if a surface is for preview consumer based on consumer end point Gralloc usage flags.
*
@@ -42,7 +55,17 @@ public class SurfaceUtils {
* @return true if the surface is for preview consumer, false otherwise.
*/
public static boolean isSurfaceForPreview(Surface surface) {
return LegacyCameraDevice.isPreviewConsumer(surface);
checkNotNull(surface);
long usageFlags = nativeDetectSurfaceUsageFlags(surface);
long disallowedFlags = HardwareBuffer.USAGE_VIDEO_ENCODE | USAGE_RENDERSCRIPT
| HardwareBuffer.USAGE_CPU_READ_OFTEN;
long allowedFlags = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | USAGE_HW_COMPOSER
| HardwareBuffer.USAGE_GPU_COLOR_OUTPUT;
boolean previewConsumer = ((usageFlags & disallowedFlags) == 0
&& (usageFlags & allowedFlags) != 0);
int surfaceFormat = getSurfaceFormat(surface);
return previewConsumer;
}
/**
@@ -53,7 +76,17 @@ public class SurfaceUtils {
* @return true if the surface is for hardware video encoder consumer, false otherwise.
*/
public static boolean isSurfaceForHwVideoEncoder(Surface surface) {
return LegacyCameraDevice.isVideoEncoderConsumer(surface);
checkNotNull(surface);
long usageFlags = nativeDetectSurfaceUsageFlags(surface);
long disallowedFlags = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | USAGE_HW_COMPOSER
| USAGE_RENDERSCRIPT | HardwareBuffer.USAGE_CPU_READ_OFTEN;
long allowedFlags = HardwareBuffer.USAGE_VIDEO_ENCODE;
boolean videoEncoderConsumer = ((usageFlags & disallowedFlags) == 0
&& (usageFlags & allowedFlags) != 0);
int surfaceFormat = getSurfaceFormat(surface);
return videoEncoderConsumer;
}
/**
@@ -63,9 +96,10 @@ public class SurfaceUtils {
* @return the native object id of the surface, 0 if surface is not backed by a native object.
*/
public static long getSurfaceId(Surface surface) {
checkNotNull(surface);
try {
return LegacyCameraDevice.getSurfaceId(surface);
} catch (BufferQueueAbandonedException e) {
return nativeGetSurfaceId(surface);
} catch (IllegalArgumentException e) {
return 0;
}
}
@@ -80,11 +114,13 @@ public class SurfaceUtils {
*/
@UnsupportedAppUsage
public static Size getSurfaceSize(Surface surface) {
try {
return LegacyCameraDevice.getSurfaceSize(surface);
} catch (BufferQueueAbandonedException e) {
throw new IllegalArgumentException("Surface was abandoned", e);
}
checkNotNull(surface);
int[] dimens = new int[2];
int errorFlag = nativeDetectSurfaceDimens(surface, /*out*/dimens);
if (errorFlag == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");
return new Size(dimens[0], dimens[1]);
}
/**
@@ -96,11 +132,17 @@ public class SurfaceUtils {
* @throws IllegalArgumentException if the surface is already abandoned.
*/
public static int getSurfaceFormat(Surface surface) {
try {
return LegacyCameraDevice.detectSurfaceType(surface);
} catch (BufferQueueAbandonedException e) {
throw new IllegalArgumentException("Surface was abandoned", e);
checkNotNull(surface);
int surfaceType = nativeDetectSurfaceType(surface);
if (surfaceType == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");
// TODO: remove this override since the default format should be
// ImageFormat.PRIVATE. b/9487482
if ((surfaceType >= PixelFormat.RGBA_8888
&& surfaceType <= BGRA_8888)) {
surfaceType = ImageFormat.PRIVATE;
}
return surfaceType;
}
/**
@@ -112,11 +154,10 @@ public class SurfaceUtils {
* @throws IllegalArgumentException if the surface is already abandoned.
*/
public static int getSurfaceDataspace(Surface surface) {
try {
return LegacyCameraDevice.detectSurfaceDataspace(surface);
} catch (BufferQueueAbandonedException e) {
throw new IllegalArgumentException("Surface was abandoned", e);
}
checkNotNull(surface);
int dataSpace = nativeDetectSurfaceDataspace(surface);
if (dataSpace == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");
return dataSpace;
}
/**
@@ -125,9 +166,21 @@ public class SurfaceUtils {
*
*/
public static boolean isFlexibleConsumer(Surface output) {
return LegacyCameraDevice.isFlexibleConsumer(output);
checkNotNull(output);
long usageFlags = nativeDetectSurfaceUsageFlags(output);
// Keep up to date with allowed consumer types in
// frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
long disallowedFlags = HardwareBuffer.USAGE_VIDEO_ENCODE | USAGE_RENDERSCRIPT;
long allowedFlags = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
| HardwareBuffer.USAGE_CPU_READ_OFTEN
| USAGE_HW_COMPOSER;
boolean flexibleConsumer = ((usageFlags & disallowedFlags) == 0
&& (usageFlags & allowedFlags) != 0);
return flexibleConsumer;
}
/**
* A high speed output surface can only be preview or hardware encoder surface.
*
@@ -209,4 +262,14 @@ public class SurfaceUtils {
}
}
private static native int nativeDetectSurfaceType(Surface surface);
private static native int nativeDetectSurfaceDataspace(Surface surface);
private static native long nativeDetectSurfaceUsageFlags(Surface surface);
private static native int nativeDetectSurfaceDimens(Surface surface,
/*out*/int[/*2*/] dimens);
private static native long nativeGetSurfaceId(Surface surface);
}

View File

@@ -155,9 +155,8 @@ cc_library_shared {
"android_media_ToneGenerator.cpp",
"android_hardware_Camera.cpp",
"android_hardware_camera2_CameraMetadata.cpp",
"android_hardware_camera2_legacy_LegacyCameraDevice.cpp",
"android_hardware_camera2_legacy_PerfMeasurement.cpp",
"android_hardware_camera2_DngCreator.cpp",
"android_hardware_camera2_utils_SurfaceUtils.cpp",
"android_hardware_display_DisplayManagerGlobal.cpp",
"android_hardware_display_DisplayViewport.cpp",
"android_hardware_HardwareBuffer.cpp",

View File

@@ -73,9 +73,8 @@ extern int register_android_opengl_jni_GLES32(JNIEnv* env);
extern int register_android_hardware_Camera(JNIEnv *env);
extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env);
extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *env);
extern int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv *env);
extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
extern int register_android_hardware_camera2_utils_SurfaceUtils(JNIEnv* env);
extern int register_android_hardware_display_DisplayManagerGlobal(JNIEnv* env);
extern int register_android_hardware_HardwareBuffer(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
@@ -1526,9 +1525,8 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr),
REG_JNI(register_android_hardware_Camera),
REG_JNI(register_android_hardware_camera2_CameraMetadata),
REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice),
REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement),
REG_JNI(register_android_hardware_camera2_DngCreator),
REG_JNI(register_android_hardware_camera2_utils_SurfaceUtils),
REG_JNI(register_android_hardware_display_DisplayManagerGlobal),
REG_JNI(register_android_hardware_HardwareBuffer),
REG_JNI(register_android_hardware_SensorManager),

View File

@@ -556,7 +556,7 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz,
// connect to camera service
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
jobject weak_this, jint cameraId, jint halVersion, jstring clientPackageName)
jobject weak_this, jint cameraId, jstring clientPackageName)
{
// Convert jstring to String16
const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
@@ -566,19 +566,8 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
env->ReleaseStringChars(clientPackageName,
reinterpret_cast<const jchar*>(rawClientName));
sp<Camera> camera;
if (halVersion == CAMERA_HAL_API_VERSION_NORMAL_CONNECT) {
// Default path: hal version is don't care, do normal camera connect.
camera = Camera::connect(cameraId, clientName,
Camera::USE_CALLING_UID, Camera::USE_CALLING_PID);
} else {
jint status = Camera::connectLegacy(cameraId, halVersion, clientName,
Camera::USE_CALLING_UID, camera);
if (status != NO_ERROR) {
return status;
}
}
sp<Camera> camera =
Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID);
if (camera == NULL) {
return -EACCES;
}
@@ -1068,7 +1057,7 @@ static const JNINativeMethod camMethods[] = {
"(ILandroid/hardware/Camera$CameraInfo;)V",
(void*)android_hardware_Camera_getCameraInfo },
{ "native_setup",
"(Ljava/lang/Object;IILjava/lang/String;)I",
"(Ljava/lang/Object;ILjava/lang/String;)I",
(void*)android_hardware_Camera_native_setup },
{ "native_release",
"()V",

View File

@@ -1,841 +0,0 @@
/*
* 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.
*/
#define LOG_TAG "Legacy-CameraDevice-JNI"
// #define LOG_NDEBUG 0
#include <utils/Log.h>
#include <utils/Errors.h>
#include <utils/Trace.h>
#include <camera/CameraUtils.h>
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include "core_jni_helpers.h"
#include "android_runtime/android_view_Surface.h"
#include "android_runtime/android_graphics_SurfaceTexture.h"
#include <gui/IGraphicBufferProducer.h>
#include <gui/IProducerListener.h>
#include <gui/Surface.h>
#include <hardware/camera3.h>
#include <surfacetexture/SurfaceTexture.h>
#include <system/camera_metadata.h>
#include <system/window.h>
#include <ui/GraphicBuffer.h>
#include <stdint.h>
#include <inttypes.h>
using namespace android;
// fully-qualified class name
#define CAMERA_DEVICE_CLASS_NAME "android/hardware/camera2/legacy/LegacyCameraDevice"
#define CAMERA_DEVICE_BUFFER_SLACK 3
#define DONT_CARE 0
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(*(a)))
#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
// Use BAD_VALUE for surface abandoned error
#define OVERRIDE_SURFACE_ERROR(err) \
do { \
if (err == -ENODEV) { \
err = BAD_VALUE; \
} \
} while (0)
#define UPDATE(md, tag, data, size) \
do { \
if ((md).update((tag), (data), (size))) { \
ALOGE("Update " #tag " failed!"); \
return BAD_VALUE; \
} \
} while (0)
/**
* Convert from RGB 888 to Y'CbCr using the conversion specified in JFIF v1.02
*/
static void rgbToYuv420(uint8_t* rgbBuf, size_t width, size_t height, uint8_t* yPlane,
uint8_t* crPlane, uint8_t* cbPlane, size_t chromaStep, size_t yStride, size_t chromaStride) {
uint8_t R, G, B;
size_t index = 0;
for (size_t j = 0; j < height; j++) {
uint8_t* cr = crPlane;
uint8_t* cb = cbPlane;
uint8_t* y = yPlane;
bool jEven = (j & 1) == 0;
for (size_t i = 0; i < width; i++) {
R = rgbBuf[index++];
G = rgbBuf[index++];
B = rgbBuf[index++];
*y++ = (77 * R + 150 * G + 29 * B) >> 8;
if (jEven && (i & 1) == 0) {
*cb = (( -43 * R - 85 * G + 128 * B) >> 8) + 128;
*cr = (( 128 * R - 107 * G - 21 * B) >> 8) + 128;
cr += chromaStep;
cb += chromaStep;
}
// Skip alpha
index++;
}
yPlane += yStride;
if (jEven) {
crPlane += chromaStride;
cbPlane += chromaStride;
}
}
}
static void rgbToYuv420(uint8_t* rgbBuf, size_t width, size_t height, android_ycbcr* ycbcr) {
size_t cStep = ycbcr->chroma_step;
size_t cStride = ycbcr->cstride;
size_t yStride = ycbcr->ystride;
ALOGV("%s: yStride is: %zu, cStride is: %zu, cStep is: %zu", __FUNCTION__, yStride, cStride,
cStep);
rgbToYuv420(rgbBuf, width, height, reinterpret_cast<uint8_t*>(ycbcr->y),
reinterpret_cast<uint8_t*>(ycbcr->cr), reinterpret_cast<uint8_t*>(ycbcr->cb),
cStep, yStride, cStride);
}
static status_t connectSurface(const sp<Surface>& surface, int32_t maxBufferSlack) {
status_t err = NO_ERROR;
err = surface->connect(NATIVE_WINDOW_API_CAMERA, /*listener*/NULL);
if (err != OK) {
ALOGE("%s: Unable to connect to surface, error %s (%d).", __FUNCTION__,
strerror(-err), err);
return err;
}
err = native_window_set_usage(surface.get(), GRALLOC_USAGE_SW_WRITE_OFTEN);
if (err != NO_ERROR) {
ALOGE("%s: Failed to set native window usage flag, error %s (%d).", __FUNCTION__,
strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
int minUndequeuedBuffers;
err = static_cast<ANativeWindow*>(surface.get())->query(surface.get(),
NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minUndequeuedBuffers);
if (err != NO_ERROR) {
ALOGE("%s: Failed to get native window min undequeued buffers, error %s (%d).",
__FUNCTION__, strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
ALOGV("%s: Setting buffer count to %d", __FUNCTION__,
maxBufferSlack + 1 + minUndequeuedBuffers);
err = native_window_set_buffer_count(surface.get(), maxBufferSlack + 1 + minUndequeuedBuffers);
if (err != NO_ERROR) {
ALOGE("%s: Failed to set native window buffer count, error %s (%d).", __FUNCTION__,
strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return NO_ERROR;
}
/**
* Produce a frame in the given surface.
*
* Args:
* anw - a surface to produce a frame in.
* pixelBuffer - image buffer to generate a frame from.
* width - width of the pixelBuffer in pixels.
* height - height of the pixelBuffer in pixels.
* pixelFmt - format of the pixelBuffer, one of:
* HAL_PIXEL_FORMAT_YCrCb_420_SP,
* HAL_PIXEL_FORMAT_YCbCr_420_888,
* HAL_PIXEL_FORMAT_BLOB
* bufSize - the size of the pixelBuffer in bytes.
*/
static status_t produceFrame(const sp<ANativeWindow>& anw,
uint8_t* pixelBuffer,
int32_t bufWidth, // Width of the pixelBuffer
int32_t bufHeight, // Height of the pixelBuffer
int32_t pixelFmt, // Format of the pixelBuffer
int32_t bufSize) {
ATRACE_CALL();
status_t err = NO_ERROR;
ANativeWindowBuffer* anb;
ALOGV("%s: Dequeue buffer from %p %dx%d (fmt=%x, size=%x)",
__FUNCTION__, anw.get(), bufWidth, bufHeight, pixelFmt, bufSize);
if (anw == 0) {
ALOGE("%s: anw must not be NULL", __FUNCTION__);
return BAD_VALUE;
} else if (pixelBuffer == NULL) {
ALOGE("%s: pixelBuffer must not be NULL", __FUNCTION__);
return BAD_VALUE;
} else if (bufWidth < 0) {
ALOGE("%s: width must be non-negative", __FUNCTION__);
return BAD_VALUE;
} else if (bufHeight < 0) {
ALOGE("%s: height must be non-negative", __FUNCTION__);
return BAD_VALUE;
} else if (bufSize < 0) {
ALOGE("%s: bufSize must be non-negative", __FUNCTION__);
return BAD_VALUE;
}
size_t width = static_cast<size_t>(bufWidth);
size_t height = static_cast<size_t>(bufHeight);
size_t bufferLength = static_cast<size_t>(bufSize);
// TODO: Switch to using Surface::lock and Surface::unlockAndPost
err = native_window_dequeue_buffer_and_wait(anw.get(), &anb);
if (err != NO_ERROR) {
ALOGE("%s: Failed to dequeue buffer, error %s (%d).", __FUNCTION__,
strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
sp<GraphicBuffer> buf(GraphicBuffer::from(anb));
uint32_t grallocBufWidth = buf->getWidth();
uint32_t grallocBufHeight = buf->getHeight();
uint32_t grallocBufStride = buf->getStride();
if (grallocBufWidth != width || grallocBufHeight != height) {
ALOGE("%s: Received gralloc buffer with bad dimensions %" PRIu32 "x%" PRIu32
", expecting dimensions %zu x %zu", __FUNCTION__, grallocBufWidth,
grallocBufHeight, width, height);
return BAD_VALUE;
}
int32_t bufFmt = 0;
err = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &bufFmt);
if (err != NO_ERROR) {
ALOGE("%s: Error while querying surface pixel format %s (%d).", __FUNCTION__,
strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
uint64_t tmpSize = (pixelFmt == HAL_PIXEL_FORMAT_BLOB) ? grallocBufWidth :
4 * grallocBufHeight * grallocBufWidth;
if (bufFmt != pixelFmt) {
if (bufFmt == HAL_PIXEL_FORMAT_RGBA_8888 && pixelFmt == HAL_PIXEL_FORMAT_BLOB) {
ALOGV("%s: Using BLOB to RGBA format override.", __FUNCTION__);
tmpSize = 4 * (grallocBufWidth + grallocBufStride * (grallocBufHeight - 1));
} else {
ALOGW("%s: Format mismatch in produceFrame: expecting format %#" PRIx32
", but received buffer with format %#" PRIx32, __FUNCTION__, pixelFmt, bufFmt);
}
}
if (tmpSize > SIZE_MAX) {
ALOGE("%s: Overflow calculating size, buffer with dimens %zu x %zu is absurdly large...",
__FUNCTION__, width, height);
return BAD_VALUE;
}
size_t totalSizeBytes = tmpSize;
ALOGV("%s: Pixel format chosen: %x", __FUNCTION__, pixelFmt);
switch(pixelFmt) {
case HAL_PIXEL_FORMAT_YCrCb_420_SP: {
if (bufferLength < totalSizeBytes) {
ALOGE("%s: PixelBuffer size %zu too small for given dimensions",
__FUNCTION__, bufferLength);
return BAD_VALUE;
}
uint8_t* img = NULL;
ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get());
err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
if (err != NO_ERROR) return err;
uint8_t* yPlane = img;
uint8_t* uPlane = img + height * width;
uint8_t* vPlane = uPlane + 1;
size_t chromaStep = 2;
size_t yStride = width;
size_t chromaStride = width;
rgbToYuv420(pixelBuffer, width, height, yPlane,
uPlane, vPlane, chromaStep, yStride, chromaStride);
break;
}
case HAL_PIXEL_FORMAT_YV12: {
if (bufferLength < totalSizeBytes) {
ALOGE("%s: PixelBuffer size %zu too small for given dimensions",
__FUNCTION__, bufferLength);
return BAD_VALUE;
}
if ((width & 1) || (height & 1)) {
ALOGE("%s: Dimens %zu x %zu are not divisible by 2.", __FUNCTION__, width, height);
return BAD_VALUE;
}
uint8_t* img = NULL;
ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get());
err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
if (err != NO_ERROR) {
ALOGE("%s: Error %s (%d) while locking gralloc buffer for write.", __FUNCTION__,
strerror(-err), err);
return err;
}
uint32_t stride = buf->getStride();
ALOGV("%s: stride is: %" PRIu32, __FUNCTION__, stride);
LOG_ALWAYS_FATAL_IF(stride % 16, "Stride is not 16 pixel aligned %d", stride);
uint32_t cStride = ALIGN(stride / 2, 16);
size_t chromaStep = 1;
uint8_t* yPlane = img;
uint8_t* crPlane = img + static_cast<uint32_t>(height) * stride;
uint8_t* cbPlane = crPlane + cStride * static_cast<uint32_t>(height) / 2;
rgbToYuv420(pixelBuffer, width, height, yPlane,
crPlane, cbPlane, chromaStep, stride, cStride);
break;
}
case HAL_PIXEL_FORMAT_YCbCr_420_888: {
// Software writes with YCbCr_420_888 format are unsupported
// by the gralloc module for now
if (bufferLength < totalSizeBytes) {
ALOGE("%s: PixelBuffer size %zu too small for given dimensions",
__FUNCTION__, bufferLength);
return BAD_VALUE;
}
android_ycbcr ycbcr = android_ycbcr();
ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get());
err = buf->lockYCbCr(GRALLOC_USAGE_SW_WRITE_OFTEN, &ycbcr);
if (err != NO_ERROR) {
ALOGE("%s: Failed to lock ycbcr buffer, error %s (%d).", __FUNCTION__,
strerror(-err), err);
return err;
}
rgbToYuv420(pixelBuffer, width, height, &ycbcr);
break;
}
case HAL_PIXEL_FORMAT_BLOB: {
int8_t* img = NULL;
struct camera3_jpeg_blob footer = {
.jpeg_blob_id = CAMERA3_JPEG_BLOB_ID,
.jpeg_size = (uint32_t)bufferLength
};
size_t totalJpegSize = bufferLength + sizeof(footer);
totalJpegSize = (totalJpegSize + 3) & ~0x3; // round up to nearest octonibble
if (totalJpegSize > totalSizeBytes) {
ALOGE("%s: Pixel buffer needs size %zu, cannot fit in gralloc buffer of size %zu",
__FUNCTION__, totalJpegSize, totalSizeBytes);
return BAD_VALUE;
}
err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
if (err != NO_ERROR) {
ALOGE("%s: Failed to lock buffer, error %s (%d).", __FUNCTION__, strerror(-err),
err);
return err;
}
memcpy(img, pixelBuffer, bufferLength);
memcpy(img + totalSizeBytes - sizeof(footer), &footer, sizeof(footer));
break;
}
default: {
ALOGE("%s: Invalid pixel format in produceFrame: %x", __FUNCTION__, pixelFmt);
return BAD_VALUE;
}
}
ALOGV("%s: Unlock buffer from %p", __FUNCTION__, anw.get());
err = buf->unlock();
if (err != NO_ERROR) {
ALOGE("%s: Failed to unlock buffer, error %s (%d).", __FUNCTION__, strerror(-err), err);
return err;
}
ALOGV("%s: Queue buffer to %p", __FUNCTION__, anw.get());
err = anw->queueBuffer(anw.get(), buf->getNativeBuffer(), /*fenceFd*/-1);
if (err != NO_ERROR) {
ALOGE("%s: Failed to queue buffer, error %s (%d).", __FUNCTION__, strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return NO_ERROR;
}
static sp<ANativeWindow> getNativeWindow(JNIEnv* env, jobject surface) {
sp<ANativeWindow> anw;
if (surface) {
anw = android_view_Surface_getNativeWindow(env, surface);
if (env->ExceptionCheck()) {
return NULL;
}
} else {
jniThrowNullPointerException(env, "surface");
return NULL;
}
if (anw == NULL) {
ALOGE("%s: Surface had no valid native window.", __FUNCTION__);
return NULL;
}
return anw;
}
static sp<ANativeWindow> getSurfaceTextureNativeWindow(JNIEnv* env, jobject thiz) {
sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, thiz));
sp<Surface> surfaceTextureClient(producer != NULL ? new Surface(producer) : NULL);
return surfaceTextureClient;
}
static sp<ANativeWindow> getNativeWindowFromTexture(JNIEnv* env, jobject surfaceTexture) {
sp<ANativeWindow> anw;
if (surfaceTexture) {
anw = getSurfaceTextureNativeWindow(env, surfaceTexture);
if (env->ExceptionCheck()) {
return NULL;
}
} else {
jniThrowNullPointerException(env, "surfaceTexture");
return NULL;
}
if (anw == NULL) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
"SurfaceTexture had no valid native window.");
return NULL;
}
return anw;
}
static sp<Surface> getSurface(JNIEnv* env, jobject surface) {
sp<Surface> s;
if (surface) {
s = android_view_Surface_getSurface(env, surface);
if (env->ExceptionCheck()) {
return NULL;
}
} else {
jniThrowNullPointerException(env, "surface");
return NULL;
}
if (s == NULL) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
"Surface had no valid native Surface.");
return NULL;
}
return s;
}
extern "C" {
static jint LegacyCameraDevice_nativeDetectSurfaceType(JNIEnv* env, jobject thiz, jobject surface) {
ALOGV("nativeDetectSurfaceType");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
int32_t fmt = 0;
status_t err = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &fmt);
if(err != NO_ERROR) {
ALOGE("%s: Error while querying surface pixel format %s (%d).", __FUNCTION__, strerror(-err),
err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return fmt;
}
static jint LegacyCameraDevice_nativeDetectSurfaceDataspace(JNIEnv* env, jobject thiz, jobject surface) {
ALOGV("nativeDetectSurfaceDataspace");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
int32_t fmt = 0;
status_t err = anw->query(anw.get(), NATIVE_WINDOW_DEFAULT_DATASPACE, &fmt);
if(err != NO_ERROR) {
ALOGE("%s: Error while querying surface dataspace %s (%d).", __FUNCTION__, strerror(-err),
err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return fmt;
}
static jint LegacyCameraDevice_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz,
jobject surface, jintArray dimens) {
ALOGV("nativeGetSurfaceDimens");
if (dimens == NULL) {
ALOGE("%s: Null dimens argument passed to nativeDetectSurfaceDimens", __FUNCTION__);
return BAD_VALUE;
}
if (env->GetArrayLength(dimens) < 2) {
ALOGE("%s: Invalid length of dimens argument in nativeDetectSurfaceDimens", __FUNCTION__);
return BAD_VALUE;
}
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
int32_t dimenBuf[2];
status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf);
if(err != NO_ERROR) {
ALOGE("%s: Error while querying surface width %s (%d).", __FUNCTION__, strerror(-err),
err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1);
if(err != NO_ERROR) {
ALOGE("%s: Error while querying surface height %s (%d).", __FUNCTION__, strerror(-err),
err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
env->SetIntArrayRegion(dimens, /*start*/0, /*length*/ARRAY_SIZE(dimenBuf), dimenBuf);
return NO_ERROR;
}
static jint LegacyCameraDevice_nativeDetectSurfaceUsageFlags(JNIEnv* env, jobject thiz,
jobject surface) {
ALOGV("nativeDetectSurfaceUsageFlags");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
jniThrowException(env, "java/lang/UnsupportedOperationException",
"Could not retrieve native window from surface.");
return BAD_VALUE;
}
int32_t usage = 0;
status_t err = anw->query(anw.get(), NATIVE_WINDOW_CONSUMER_USAGE_BITS, &usage);
if(err != NO_ERROR) {
jniThrowException(env, "java/lang/UnsupportedOperationException",
"Error while querying surface usage bits");
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return usage;
}
static jint LegacyCameraDevice_nativeDisconnectSurface(JNIEnv* env, jobject thiz,
jobject surface) {
ALOGV("nativeDisconnectSurface");
if (surface == nullptr) return NO_ERROR;
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGV("Buffer queue has already been abandoned.");
return NO_ERROR;
}
status_t err = native_window_api_disconnect(anw.get(), NATIVE_WINDOW_API_CAMERA);
if(err != NO_ERROR) {
jniThrowException(env, "java/lang/UnsupportedOperationException",
"Error while disconnecting surface");
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return NO_ERROR;
}
static jint LegacyCameraDevice_nativeDetectTextureDimens(JNIEnv* env, jobject thiz,
jobject surfaceTexture, jintArray dimens) {
ALOGV("nativeDetectTextureDimens");
sp<ANativeWindow> anw;
if ((anw = getNativeWindowFromTexture(env, surfaceTexture)) == NULL) {
ALOGE("%s: Could not retrieve native window from SurfaceTexture.", __FUNCTION__);
return BAD_VALUE;
}
int32_t dimenBuf[2];
status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf);
if(err != NO_ERROR) {
ALOGE("%s: Error while querying SurfaceTexture width %s (%d)", __FUNCTION__,
strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1);
if(err != NO_ERROR) {
ALOGE("%s: Error while querying SurfaceTexture height %s (%d)", __FUNCTION__,
strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
env->SetIntArrayRegion(dimens, /*start*/0, /*length*/ARRAY_SIZE(dimenBuf), dimenBuf);
if (env->ExceptionCheck()) {
return BAD_VALUE;
}
return NO_ERROR;
}
static jint LegacyCameraDevice_nativeConnectSurface(JNIEnv* env, jobject thiz, jobject surface) {
ALOGV("nativeConnectSurface");
sp<Surface> s;
if ((s = getSurface(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve surface.", __FUNCTION__);
return BAD_VALUE;
}
status_t err = connectSurface(s, CAMERA_DEVICE_BUFFER_SLACK);
if (err != NO_ERROR) {
ALOGE("%s: Error while configuring surface %s (%d).", __FUNCTION__, strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return NO_ERROR;
}
static jint LegacyCameraDevice_nativeProduceFrame(JNIEnv* env, jobject thiz, jobject surface,
jbyteArray pixelBuffer, jint width, jint height, jint pixelFormat) {
ALOGV("nativeProduceFrame");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
if (pixelBuffer == NULL) {
jniThrowNullPointerException(env, "pixelBuffer");
return DONT_CARE;
}
int32_t bufSize = static_cast<int32_t>(env->GetArrayLength(pixelBuffer));
jbyte* pixels = env->GetByteArrayElements(pixelBuffer, /*is_copy*/NULL);
if (pixels == NULL) {
jniThrowNullPointerException(env, "pixels");
return DONT_CARE;
}
status_t err = produceFrame(anw, reinterpret_cast<uint8_t*>(pixels), width, height,
pixelFormat, bufSize);
env->ReleaseByteArrayElements(pixelBuffer, pixels, JNI_ABORT);
if (err != NO_ERROR) {
ALOGE("%s: Error while producing frame %s (%d).", __FUNCTION__, strerror(-err), err);
return err;
}
return NO_ERROR;
}
static jint LegacyCameraDevice_nativeSetSurfaceFormat(JNIEnv* env, jobject thiz, jobject surface,
jint pixelFormat) {
ALOGV("nativeSetSurfaceType");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
status_t err = native_window_set_buffers_format(anw.get(), pixelFormat);
if (err != NO_ERROR) {
ALOGE("%s: Error while setting surface format %s (%d).", __FUNCTION__, strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return NO_ERROR;
}
static jint LegacyCameraDevice_nativeSetSurfaceDimens(JNIEnv* env, jobject thiz, jobject surface,
jint width, jint height) {
ALOGV("nativeSetSurfaceDimens");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
// Set user dimensions only
// The producer dimensions are owned by GL
status_t err = native_window_set_buffers_user_dimensions(anw.get(), width, height);
if (err != NO_ERROR) {
ALOGE("%s: Error while setting surface user dimens %s (%d).", __FUNCTION__, strerror(-err),
err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return NO_ERROR;
}
static jlong LegacyCameraDevice_nativeGetSurfaceId(JNIEnv* env, jobject thiz, jobject surface) {
ALOGV("nativeGetSurfaceId");
sp<Surface> s;
if ((s = getSurface(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native Surface from surface.", __FUNCTION__);
return 0;
}
sp<IGraphicBufferProducer> gbp = s->getIGraphicBufferProducer();
if (gbp == NULL) {
ALOGE("%s: Could not retrieve IGraphicBufferProducer from surface.", __FUNCTION__);
return 0;
}
sp<IBinder> b = IInterface::asBinder(gbp);
if (b == NULL) {
ALOGE("%s: Could not retrieve IBinder from surface.", __FUNCTION__);
return 0;
}
/*
* FIXME: Use better unique ID for surfaces than native IBinder pointer. Fix also in the camera
* service (CameraDeviceClient.h).
*/
return reinterpret_cast<jlong>(b.get());
}
static jint LegacyCameraDevice_nativeSetSurfaceOrientation(JNIEnv* env, jobject thiz,
jobject surface, jint facing, jint orientation) {
ALOGV("nativeSetSurfaceOrientation");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
status_t err = NO_ERROR;
CameraMetadata staticMetadata;
int32_t orientVal = static_cast<int32_t>(orientation);
uint8_t facingVal = static_cast<uint8_t>(facing);
staticMetadata.update(ANDROID_SENSOR_ORIENTATION, &orientVal, 1);
staticMetadata.update(ANDROID_LENS_FACING, &facingVal, 1);
int32_t transform = 0;
if ((err = CameraUtils::getRotationTransform(staticMetadata, /*out*/&transform)) != NO_ERROR) {
ALOGE("%s: Invalid rotation transform %s (%d)", __FUNCTION__, strerror(-err),
err);
return err;
}
ALOGV("%s: Setting buffer sticky transform to %d", __FUNCTION__, transform);
if ((err = native_window_set_buffers_sticky_transform(anw.get(), transform)) != NO_ERROR) {
ALOGE("%s: Unable to configure surface transform, error %s (%d)", __FUNCTION__,
strerror(-err), err);
return err;
}
return NO_ERROR;
}
static jint LegacyCameraDevice_nativeSetNextTimestamp(JNIEnv* env, jobject thiz, jobject surface,
jlong timestamp) {
ALOGV("nativeSetNextTimestamp");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
status_t err = NO_ERROR;
if ((err = native_window_set_buffers_timestamp(anw.get(), static_cast<int64_t>(timestamp))) !=
NO_ERROR) {
ALOGE("%s: Unable to set surface timestamp, error %s (%d)", __FUNCTION__, strerror(-err),
err);
return err;
}
return NO_ERROR;
}
static jint LegacyCameraDevice_nativeSetScalingMode(JNIEnv* env, jobject thiz, jobject surface,
jint mode) {
ALOGV("nativeSetScalingMode");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
status_t err = NO_ERROR;
if ((err = native_window_set_scaling_mode(anw.get(), static_cast<int>(mode))) != NO_ERROR) {
ALOGE("%s: Unable to set surface scaling mode, error %s (%d)", __FUNCTION__,
strerror(-err), err);
return err;
}
return NO_ERROR;
}
static jint LegacyCameraDevice_nativeGetJpegFooterSize(JNIEnv* env, jobject thiz) {
ALOGV("nativeGetJpegFooterSize");
return static_cast<jint>(sizeof(struct camera3_jpeg_blob));
}
} // extern "C"
static const JNINativeMethod gCameraDeviceMethods[] = {
{ "nativeDetectSurfaceType",
"(Landroid/view/Surface;)I",
(void *)LegacyCameraDevice_nativeDetectSurfaceType },
{ "nativeDetectSurfaceDataspace",
"(Landroid/view/Surface;)I",
(void *)LegacyCameraDevice_nativeDetectSurfaceDataspace },
{ "nativeDetectSurfaceDimens",
"(Landroid/view/Surface;[I)I",
(void *)LegacyCameraDevice_nativeDetectSurfaceDimens },
{ "nativeConnectSurface",
"(Landroid/view/Surface;)I",
(void *)LegacyCameraDevice_nativeConnectSurface },
{ "nativeProduceFrame",
"(Landroid/view/Surface;[BIII)I",
(void *)LegacyCameraDevice_nativeProduceFrame },
{ "nativeSetSurfaceFormat",
"(Landroid/view/Surface;I)I",
(void *)LegacyCameraDevice_nativeSetSurfaceFormat },
{ "nativeSetSurfaceDimens",
"(Landroid/view/Surface;II)I",
(void *)LegacyCameraDevice_nativeSetSurfaceDimens },
{ "nativeGetSurfaceId",
"(Landroid/view/Surface;)J",
(void *)LegacyCameraDevice_nativeGetSurfaceId },
{ "nativeDetectTextureDimens",
"(Landroid/graphics/SurfaceTexture;[I)I",
(void *)LegacyCameraDevice_nativeDetectTextureDimens },
{ "nativeSetSurfaceOrientation",
"(Landroid/view/Surface;II)I",
(void *)LegacyCameraDevice_nativeSetSurfaceOrientation },
{ "nativeSetNextTimestamp",
"(Landroid/view/Surface;J)I",
(void *)LegacyCameraDevice_nativeSetNextTimestamp },
{ "nativeGetJpegFooterSize",
"()I",
(void *)LegacyCameraDevice_nativeGetJpegFooterSize },
{ "nativeDetectSurfaceUsageFlags",
"(Landroid/view/Surface;)I",
(void *)LegacyCameraDevice_nativeDetectSurfaceUsageFlags },
{ "nativeSetScalingMode",
"(Landroid/view/Surface;I)I",
(void *)LegacyCameraDevice_nativeSetScalingMode },
{ "nativeDisconnectSurface",
"(Landroid/view/Surface;)I",
(void *)LegacyCameraDevice_nativeDisconnectSurface },
};
// Get all the required offsets in java class and register native functions
int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv* env)
{
// Register native functions
return RegisterMethodsOrDie(env,
CAMERA_DEVICE_CLASS_NAME,
gCameraDeviceMethods,
NELEM(gCameraDeviceMethods));
}

View File

@@ -1,335 +0,0 @@
/*
* 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.
*/
#define LOG_TAG "Camera2-Legacy-PerfMeasurement-JNI"
#include <utils/Log.h>
#include <utils/Errors.h>
#include <utils/Trace.h>
#include <utils/Vector.h>
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include "core_jni_helpers.h"
#include <ui/GraphicBuffer.h>
#include <system/window.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
using namespace android;
// fully-qualified class name
#define PERF_MEASUREMENT_CLASS_NAME "android/hardware/camera2/legacy/PerfMeasurement"
/** GL utility methods copied from com_google_android_gles_jni_GLImpl.cpp */
// Check if the extension at the head of pExtensions is pExtension. Note that pExtensions is
// terminated by either 0 or space, while pExtension is terminated by 0.
static bool
extensionEqual(const GLubyte* pExtensions, const GLubyte* pExtension) {
while (true) {
char a = *pExtensions++;
char b = *pExtension++;
bool aEnd = a == '\0' || a == ' ';
bool bEnd = b == '\0';
if (aEnd || bEnd) {
return aEnd == bEnd;
}
if (a != b) {
return false;
}
}
}
static const GLubyte*
nextExtension(const GLubyte* pExtensions) {
while (true) {
char a = *pExtensions++;
if (a == '\0') {
return pExtensions-1;
} else if ( a == ' ') {
return pExtensions;
}
}
}
static bool
checkForExtension(const GLubyte* pExtensions, const GLubyte* pExtension) {
for (; *pExtensions != '\0'; pExtensions = nextExtension(pExtensions)) {
if (extensionEqual(pExtensions, pExtension)) {
return true;
}
}
return false;
}
/** End copied GL utility methods */
bool checkGlError(JNIEnv* env) {
int error;
if ((error = glGetError()) != GL_NO_ERROR) {
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"GLES20 error: 0x%d", error);
return true;
}
return false;
}
/**
* Asynchronous low-overhead GL performance measurement using
* http://www.khronos.org/registry/gles/extensions/EXT/EXT_disjoint_timer_query.txt
*
* Measures the duration of GPU processing for a set of GL commands, delivering
* the measurement asynchronously once processing completes.
*
* All calls must come from a single thread with a valid GL context active.
**/
class PerfMeasurementContext {
private:
Vector<GLuint> mTimingQueries;
size_t mTimingStartIndex;
size_t mTimingEndIndex;
size_t mTimingQueryIndex;
size_t mFreeQueries;
bool mInitDone;
public:
/**
* maxQueryCount should be a conservative estimate of how many query objects
* will be active at once, which is a function of the GPU's level of
* pipelining and the frequency of queries.
*/
explicit PerfMeasurementContext(size_t maxQueryCount):
mTimingStartIndex(0),
mTimingEndIndex(0),
mTimingQueryIndex(0) {
mTimingQueries.resize(maxQueryCount);
mFreeQueries = maxQueryCount;
mInitDone = false;
}
int getMaxQueryCount() {
return mTimingQueries.size();
}
/**
* Start a measurement period using the next available query object.
* Returns INVALID_OPERATION if called multiple times in a row,
* and BAD_VALUE if no more query objects are available.
*/
int startGlTimer() {
// Lazy init of queries to avoid needing GL context during construction
if (!mInitDone) {
glGenQueriesEXT(mTimingQueries.size(), mTimingQueries.editArray());
mInitDone = true;
}
if (mTimingEndIndex != mTimingStartIndex) {
return INVALID_OPERATION;
}
if (mFreeQueries == 0) {
return BAD_VALUE;
}
glBeginQueryEXT(GL_TIME_ELAPSED_EXT, mTimingQueries[mTimingStartIndex]);
mTimingStartIndex = (mTimingStartIndex + 1) % mTimingQueries.size();
mFreeQueries--;
return OK;
}
/**
* Finish the current measurement period
* Returns INVALID_OPERATION if called before any startGLTimer calls
* or if called multiple times in a row.
*/
int stopGlTimer() {
size_t nextEndIndex = (mTimingEndIndex + 1) % mTimingQueries.size();
if (nextEndIndex != mTimingStartIndex) {
return INVALID_OPERATION;
}
glEndQueryEXT(GL_TIME_ELAPSED_EXT);
mTimingEndIndex = nextEndIndex;
return OK;
}
static const nsecs_t NO_DURATION_YET = -1L;
static const nsecs_t FAILED_MEASUREMENT = -2L;
/**
* Get the next available duration measurement.
*
* Returns NO_DURATION_YET if no new measurement is available,
* and FAILED_MEASUREMENT if an error occurred during the next
* measurement period.
*
* Otherwise returns a positive number of nanoseconds measuring the
* duration of the oldest completed query.
*/
nsecs_t getNextGlDuration() {
if (!mInitDone) {
// No start/stop called yet
return NO_DURATION_YET;
}
GLint available;
glGetQueryObjectivEXT(mTimingQueries[mTimingQueryIndex],
GL_QUERY_RESULT_AVAILABLE_EXT, &available);
if (!available) {
return NO_DURATION_YET;
}
GLint64 duration = FAILED_MEASUREMENT;
GLint disjointOccurred;
glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjointOccurred);
if (!disjointOccurred) {
glGetQueryObjecti64vEXT(mTimingQueries[mTimingQueryIndex],
GL_QUERY_RESULT_EXT,
&duration);
}
mTimingQueryIndex = (mTimingQueryIndex + 1) % mTimingQueries.size();
mFreeQueries++;
return static_cast<nsecs_t>(duration);
}
static bool isMeasurementSupported() {
const GLubyte* extensions = glGetString(GL_EXTENSIONS);
return checkForExtension(extensions,
reinterpret_cast<const GLubyte*>("GL_EXT_disjoint_timer_query"));
}
};
PerfMeasurementContext* getContext(jlong context) {
return reinterpret_cast<PerfMeasurementContext*>(context);
}
extern "C" {
static jlong PerfMeasurement_nativeCreateContext(JNIEnv* env, jobject thiz,
jint maxQueryCount) {
PerfMeasurementContext *context = new PerfMeasurementContext(maxQueryCount);
return reinterpret_cast<jlong>(context);
}
static void PerfMeasurement_nativeDeleteContext(JNIEnv* env, jobject thiz,
jlong contextHandle) {
PerfMeasurementContext *context = getContext(contextHandle);
delete(context);
}
static jboolean PerfMeasurement_nativeQuerySupport(JNIEnv* env, jobject thiz) {
bool supported = PerfMeasurementContext::isMeasurementSupported();
checkGlError(env);
return static_cast<jboolean>(supported);
}
static void PerfMeasurement_nativeStartGlTimer(JNIEnv* env, jobject thiz,
jlong contextHandle) {
PerfMeasurementContext *context = getContext(contextHandle);
status_t err = context->startGlTimer();
if (err != OK) {
switch (err) {
case INVALID_OPERATION:
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Mismatched start/end GL timing calls");
return;
case BAD_VALUE:
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Too many timing queries in progress, max %d",
context->getMaxQueryCount());
return;
default:
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Unknown error starting GL timing");
return;
}
}
checkGlError(env);
}
static void PerfMeasurement_nativeStopGlTimer(JNIEnv* env, jobject thiz,
jlong contextHandle) {
PerfMeasurementContext *context = getContext(contextHandle);
status_t err = context->stopGlTimer();
if (err != OK) {
switch (err) {
case INVALID_OPERATION:
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Mismatched start/end GL timing calls");
return;
default:
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Unknown error ending GL timing");
return;
}
}
checkGlError(env);
}
static jlong PerfMeasurement_nativeGetNextGlDuration(JNIEnv* env,
jobject thiz, jlong contextHandle) {
PerfMeasurementContext *context = getContext(contextHandle);
nsecs_t duration = context->getNextGlDuration();
checkGlError(env);
return static_cast<jlong>(duration);
}
} // extern "C"
static const JNINativeMethod gPerfMeasurementMethods[] = {
{ "nativeCreateContext",
"(I)J",
(jlong *)PerfMeasurement_nativeCreateContext },
{ "nativeDeleteContext",
"(J)V",
(void *)PerfMeasurement_nativeDeleteContext },
{ "nativeQuerySupport",
"()Z",
(jboolean *)PerfMeasurement_nativeQuerySupport },
{ "nativeStartGlTimer",
"(J)V",
(void *)PerfMeasurement_nativeStartGlTimer },
{ "nativeStopGlTimer",
"(J)V",
(void *)PerfMeasurement_nativeStopGlTimer },
{ "nativeGetNextGlDuration",
"(J)J",
(jlong *)PerfMeasurement_nativeGetNextGlDuration }
};
// Get all the required offsets in java class and register native functions
int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv* env)
{
// Register native functions
return RegisterMethodsOrDie(env,
PERF_MEASUREMENT_CLASS_NAME,
gPerfMeasurementMethods,
NELEM(gPerfMeasurementMethods));
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright (C) 2020 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.
*/
#define LOG_TAG "Camera-SurfaceUtils-JNI"
// #define LOG_NDEBUG 0
#include <camera/CameraUtils.h>
#include <utils/Errors.h>
#include <utils/Log.h>
#include <utils/Trace.h>
#include <nativehelper/JNIHelp.h>
#include "android_runtime/android_graphics_SurfaceTexture.h"
#include "android_runtime/android_view_Surface.h"
#include "core_jni_helpers.h"
#include "jni.h"
#include <gui/IGraphicBufferProducer.h>
#include <gui/IProducerListener.h>
#include <gui/Surface.h>
#include <system/window.h>
#include <ui/GraphicBuffer.h>
#include <inttypes.h>
#include <stdint.h>
using namespace android;
// fully-qualified class name
#define CAMERA_UTILS_CLASS_NAME "android/hardware/camera2/utils/SurfaceUtils"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
#define OVERRIDE_SURFACE_ERROR(err) \
do { \
if (err == -ENODEV) { \
err = BAD_VALUE; \
} \
} while (0)
static sp<ANativeWindow> getNativeWindow(JNIEnv* env, jobject surface) {
sp<ANativeWindow> anw;
if (surface) {
anw = android_view_Surface_getNativeWindow(env, surface);
if (env->ExceptionCheck()) {
return NULL;
}
} else {
jniThrowNullPointerException(env, "surface");
return NULL;
}
if (anw == NULL) {
ALOGE("%s: Surface had no valid native window.", __FUNCTION__);
return NULL;
}
return anw;
}
static sp<Surface> getSurface(JNIEnv* env, jobject surface) {
sp<Surface> s;
if (surface) {
s = android_view_Surface_getSurface(env, surface);
if (env->ExceptionCheck()) {
return NULL;
}
} else {
jniThrowNullPointerException(env, "surface");
return NULL;
}
if (s == NULL) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
"Surface had no valid native Surface.");
return NULL;
}
return s;
}
extern "C" {
static jint SurfaceUtils_nativeDetectSurfaceType(JNIEnv* env, jobject thiz, jobject surface) {
ALOGV("nativeDetectSurfaceType");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
int32_t fmt = 0;
status_t err = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &fmt);
if (err != NO_ERROR) {
ALOGE("%s: Error while querying surface pixel format %s (%d).", __FUNCTION__,
strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return fmt;
}
static jint SurfaceUtils_nativeDetectSurfaceDataspace(JNIEnv* env, jobject thiz, jobject surface) {
ALOGV("nativeDetectSurfaceDataspace");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
int32_t fmt = 0;
status_t err = anw->query(anw.get(), NATIVE_WINDOW_DEFAULT_DATASPACE, &fmt);
if (err != NO_ERROR) {
ALOGE("%s: Error while querying surface dataspace %s (%d).", __FUNCTION__, strerror(-err),
err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return fmt;
}
static jint SurfaceUtils_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz, jobject surface,
jintArray dimens) {
ALOGV("nativeGetSurfaceDimens");
if (dimens == NULL) {
ALOGE("%s: Null dimens argument passed to nativeDetectSurfaceDimens", __FUNCTION__);
return BAD_VALUE;
}
if (env->GetArrayLength(dimens) < 2) {
ALOGE("%s: Invalid length of dimens argument in nativeDetectSurfaceDimens", __FUNCTION__);
return BAD_VALUE;
}
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
int32_t dimenBuf[2];
status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf);
if (err != NO_ERROR) {
ALOGE("%s: Error while querying surface width %s (%d).", __FUNCTION__, strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1);
if (err != NO_ERROR) {
ALOGE("%s: Error while querying surface height %s (%d).", __FUNCTION__, strerror(-err),
err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
env->SetIntArrayRegion(dimens, /*start*/ 0, /*length*/ ARRAY_SIZE(dimenBuf), dimenBuf);
return NO_ERROR;
}
static jlong SurfaceUtils_nativeDetectSurfaceUsageFlags(JNIEnv* env, jobject thiz,
jobject surface) {
ALOGV("nativeDetectSurfaceUsageFlags");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
jniThrowException(env, "java/lang/UnsupportedOperationException",
"Could not retrieve native window from surface.");
return BAD_VALUE;
}
uint64_t usage = 0;
status_t err = native_window_get_consumer_usage(anw.get(), &usage);
if (err != NO_ERROR) {
jniThrowException(env, "java/lang/UnsupportedOperationException",
"Error while querying surface usage bits");
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return usage;
}
static jlong SurfaceUtils_nativeGetSurfaceId(JNIEnv* env, jobject thiz, jobject surface) {
ALOGV("nativeGetSurfaceId");
sp<Surface> s;
if ((s = getSurface(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native Surface from surface.", __FUNCTION__);
return 0;
}
sp<IGraphicBufferProducer> gbp = s->getIGraphicBufferProducer();
if (gbp == NULL) {
ALOGE("%s: Could not retrieve IGraphicBufferProducer from surface.", __FUNCTION__);
return 0;
}
sp<IBinder> b = IInterface::asBinder(gbp);
if (b == NULL) {
ALOGE("%s: Could not retrieve IBinder from surface.", __FUNCTION__);
return 0;
}
/*
* FIXME: Use better unique ID for surfaces than native IBinder pointer. Fix also in the camera
* service (CameraDeviceClient.h).
*/
return reinterpret_cast<jlong>(b.get());
}
} // extern "C"
static const JNINativeMethod gCameraSurfaceUtilsMethods[] = {
{"nativeDetectSurfaceType", "(Landroid/view/Surface;)I",
(void*)SurfaceUtils_nativeDetectSurfaceType},
{"nativeDetectSurfaceDataspace", "(Landroid/view/Surface;)I",
(void*)SurfaceUtils_nativeDetectSurfaceDataspace},
{"nativeDetectSurfaceDimens", "(Landroid/view/Surface;[I)I",
(void*)SurfaceUtils_nativeDetectSurfaceDimens},
{"nativeDetectSurfaceUsageFlags", "(Landroid/view/Surface;)J",
(void*)SurfaceUtils_nativeDetectSurfaceUsageFlags},
{"nativeGetSurfaceId", "(Landroid/view/Surface;)J", (void*)SurfaceUtils_nativeGetSurfaceId},
};
// Get all the required offsets in java class and register native functions
int register_android_hardware_camera2_utils_SurfaceUtils(JNIEnv* env) {
// Register native functions
return RegisterMethodsOrDie(env, CAMERA_UTILS_CLASS_NAME, gCameraSurfaceUtilsMethods,
NELEM(gCameraSurfaceUtilsMethods));
}

View File

@@ -167,34 +167,6 @@ public class CameraBinderTest extends AndroidTestCase {
}
}
@SmallTest
public void testConnectLegacy() throws Exception {
final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
ICamera cameraUser = null;
ICameraClient dummyCallbacks = new DummyCameraClient();
String clientPackageName = getContext().getPackageName();
try {
cameraUser = mUtils.getCameraService()
.connectLegacy(dummyCallbacks, cameraId, CAMERA_HAL_API_VERSION_1_0,
clientPackageName,
ICameraService.USE_CALLING_UID);
assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
Log.v(TAG, String.format("Camera %s connected as HAL1 legacy device", cameraId));
} catch (RuntimeException e) {
// Not all camera device support openLegacy.
Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
} finally {
if (cameraUser != null) {
cameraUser.disconnect();
}
}
}
}
static class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
/*

View File

@@ -1,53 +0,0 @@
/*
* Copyright (C) 2013 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 com.android.mediaframeworktest.unit;
import android.hardware.Camera;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
/**
* <pre>
* adb shell am instrument \
* -e class 'com.android.mediaframeworktest.unit.CameraOpenTest' \
* -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner
* </pre>
*/
public class CameraOpenTest extends junit.framework.TestCase {
private static String TAG = "CameraOpenTest";
private Camera mCamera;
/**
* Test @hide android.hardware.Camera#openLegacy API that cannot be tested in CTS.
*/
@SmallTest
public void testOpenLegacy() {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
try {
mCamera.openLegacy(id, Camera.CAMERA_HAL_API_VERSION_1_0);
} catch (RuntimeException e) {
Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
} finally {
if (mCamera != null) {
mCamera.release();
}
}
}
}
}