summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java150
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java22
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java278
-rw-r--r--core/java/android/hardware/camera2/legacy/RequestThreadManager.java121
-rw-r--r--core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp36
-rw-r--r--media/jni/android_media_ImageReader.cpp19
6 files changed, 604 insertions, 22 deletions
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index abd69c151efd..03dd3540370c 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -17,7 +17,9 @@
package android.hardware.camera2.legacy;
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;
@@ -25,7 +27,9 @@ import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
+import android.os.ConditionVariable;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
@@ -33,7 +37,6 @@ import android.view.Surface;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* Compatibility implementation of the Camera2 API binder interface.
@@ -53,6 +56,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
private static final String TAG = "CameraDeviceUserShim";
private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
+ private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout)
private final LegacyCameraDevice mLegacyDevice;
@@ -60,29 +64,143 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
private int mSurfaceIdCounter;
private boolean mConfiguring;
private final SparseArray<Surface> mSurfaces;
+ private final CameraCharacteristics mCameraCharacteristics;
+ private final CameraLooper mCameraInit;
- protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera) {
+ protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera,
+ CameraCharacteristics characteristics, CameraLooper cameraInit) {
mLegacyDevice = legacyCamera;
mConfiguring = false;
mSurfaces = new SparseArray<Surface>();
+ mCameraCharacteristics = characteristics;
+ mCameraInit = cameraInit;
mSurfaceIdCounter = 0;
}
+ /**
+ * 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);
+ 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 CameraRuntimeException 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 CameraRuntimeException(CameraAccessException.CAMERA_ERROR);
+ }
+
+ return mInitErrors;
+ }
+ }
+
public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
int cameraId) {
if (DEBUG) {
Log.d(TAG, "Opening shim Camera device");
}
- // TODO: Move open/init into LegacyCameraDevice thread when API is switched to async.
- Camera legacyCamera = Camera.openUninitialized();
- int initErrors = legacyCamera.cameraInitUnspecified(cameraId);
+
+ /*
+ * 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);
+
+ // TODO: Make this async instead of blocking
+ int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
+ Camera legacyCamera = init.getCamera();
// Check errors old HAL initialization
CameraBinderDecorator.throwOnError(initErrors);
+ CameraInfo info = new CameraInfo();
+ Camera.getCameraInfo(cameraId, info);
+
+ CameraCharacteristics characteristics =
+ LegacyMetadataMapper.createCharacteristics(legacyCamera.getParameters(), info);
LegacyCameraDevice device = new LegacyCameraDevice(cameraId, legacyCamera, callbacks);
- return new CameraDeviceUserShim(cameraId, device);
+ return new CameraDeviceUserShim(cameraId, device, characteristics, init);
}
@Override
@@ -90,7 +208,12 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "disconnect called.");
}
- mLegacyDevice.close();
+
+ try {
+ mLegacyDevice.close();
+ } finally {
+ mCameraInit.close();
+ }
}
@Override
@@ -218,8 +341,17 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
if (DEBUG) {
Log.d(TAG, "createDefaultRequest called.");
}
- // TODO: implement createDefaultRequest.
- Log.e(TAG, "createDefaultRequest unimplemented.");
+
+ CameraMetadataNative template;
+ try {
+ template =
+ LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "createDefaultRequest - invalid templateId specified");
+ return CameraBinderDecorator.BAD_VALUE;
+ }
+
+ request.swap(template);
return CameraBinderDecorator.NO_ERROR;
}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index cb951b320b6a..b6264dc54da7 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -29,12 +29,14 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Size;
import android.view.Surface;
import java.util.ArrayList;
import java.util.List;
import static android.hardware.camera2.utils.CameraBinderDecorator.*;
+import static com.android.internal.util.Preconditions.*;
/**
* This class emulates the functionality of a Camera2 device using a the old Camera class.
@@ -367,9 +369,27 @@ public class LegacyCameraDevice implements AutoCloseable {
}
}
+ /**
+ * 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 IllegalStateException if the {@code surface} was invalid
+ */
+ static Size getSurfaceSize(Surface surface) {
+ checkNotNull(surface);
+
+ int[] dimens = new int[2];
+ nativeDetectSurfaceDimens(surface, /*out*/dimens);
+
+ return new Size(dimens[0], dimens[1]);
+ }
+
protected static native int nativeDetectSurfaceType(Surface surface);
- protected static native void nativeDetectSurfaceDimens(Surface surface, int[] dimens);
+ protected static native void nativeDetectSurfaceDimens(Surface surface,
+ /*out*/int[/*2*/] dimens);
protected static native void nativeConfigureSurface(Surface surface, int width, int height,
int pixelFormat);
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 1cde2c4df7d5..f702556bc14a 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -21,6 +21,8 @@ import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Size;
import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.impl.CameraMetadataNative;
@@ -32,6 +34,7 @@ import android.util.Range;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import static com.android.internal.util.Preconditions.*;
import static android.hardware.camera2.CameraCharacteristics.*;
@@ -56,6 +59,42 @@ public class LegacyMetadataMapper {
private static final long APPROXIMATE_JPEG_ENCODE_TIME = 600; // ms
private static final long NS_PER_MS = 1000000;
+ /*
+ * Development hijinks: Lie about not supporting certain capabilities
+ *
+ * - Unblock some CTS tests from running whose main intent is not the metadata itself
+ *
+ * TODO: Remove these constants and strip out any code that previously relied on them
+ * being set to true.
+ */
+ private static final boolean LIE_ABOUT_FLASH = true;
+ private static final boolean LIE_ABOUT_AE = true;
+ private static final boolean LIE_ABOUT_AF = true;
+ private static final boolean LIE_ABOUT_AWB = true;
+
+ /**
+ * Create characteristics for a legacy device by mapping the {@code parameters}
+ * and {@code info}
+ *
+ * @param parameters A non-{@code null} parameters set
+ * @param info Camera info with camera facing direction and angle of orientation
+ *
+ * @return static camera characteristics for a camera device
+ *
+ * @throws NullPointerException if any of the args were {@code null}
+ */
+ public static CameraCharacteristics createCharacteristics(Camera.Parameters parameters,
+ CameraInfo info) {
+ checkNotNull(parameters, "parameters must not be null");
+ checkNotNull(info, "info must not be null");
+
+ String paramStr = parameters.flatten();
+ android.hardware.CameraInfo outerInfo = new android.hardware.CameraInfo();
+ outerInfo.info = info;
+
+ return createCharacteristics(paramStr, outerInfo);
+ }
+
/**
* Create characteristics for a legacy device by mapping the {@code parameters}
* and {@code info}
@@ -99,7 +138,8 @@ public class LegacyMetadataMapper {
private static void mapCameraParameters(CameraMetadataNative m, Camera.Parameters p) {
m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
mapStreamConfigs(m, p);
- mapAeConfig(m, p);
+ mapControlAe(m, p);
+ mapControlAwb(m, p);
mapCapabilities(m, p);
mapLens(m, p);
mapFlash(m, p);
@@ -164,8 +204,10 @@ public class LegacyMetadataMapper {
}
@SuppressWarnings({"unchecked"})
- private static void mapAeConfig(CameraMetadataNative m, Camera.Parameters p) {
-
+ private static void mapControlAe(CameraMetadataNative m, Camera.Parameters p) {
+ /*
+ * control.aeAvailableTargetFpsRanges
+ */
List<int[]> fpsRanges = p.getSupportedPreviewFpsRange();
if (fpsRanges == null) {
throw new AssertionError("Supported FPS ranges cannot be null.");
@@ -182,6 +224,10 @@ public class LegacyMetadataMapper {
}
m.set(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, ranges);
+ /*
+ * control.aeAvailableAntiBandingModes
+ */
+
List<String> antiBandingModes = p.getSupportedAntibanding();
int antiBandingModesSize = antiBandingModes.size();
if (antiBandingModesSize > 0) {
@@ -198,6 +244,49 @@ public class LegacyMetadataMapper {
}
m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, Arrays.copyOf(modes, j));
}
+
+ /*
+ * control.aeAvailableModes
+ */
+ List<String> flashModes = p.getSupportedFlashModes();
+
+ String[] flashModeStrings = new String[] {
+ Camera.Parameters.FLASH_MODE_AUTO,
+ Camera.Parameters.FLASH_MODE_ON,
+ Camera.Parameters.FLASH_MODE_RED_EYE,
+ // Map these manually
+ Camera.Parameters.FLASH_MODE_TORCH,
+ Camera.Parameters.FLASH_MODE_OFF,
+ };
+ int[] flashModeInts = new int[] {
+ CONTROL_AE_MODE_ON,
+ CONTROL_AE_MODE_ON_AUTO_FLASH,
+ CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
+ };
+ int[] aeAvail = convertStringListToIntArray(flashModes, flashModeStrings, flashModeInts);
+
+ // No flash control -> AE is always on
+ if (aeAvail == null || aeAvail.length == 0) {
+ aeAvail = new int[] {
+ CONTROL_AE_MODE_ON
+ };
+ }
+
+ if (LIE_ABOUT_FLASH) {
+ // TODO: Remove this branch
+ Log.w(TAG, "mapControlAe - lying; saying we only support CONTROL_AE_MODE_ON");
+ aeAvail = new int[] {
+ CONTROL_AE_MODE_ON
+ };
+ }
+
+ m.set(CONTROL_AE_AVAILABLE_MODES, aeAvail);
+ }
+
+ private static void mapControlAwb(CameraMetadataNative m, Camera.Parameters p) {
+ if (!LIE_ABOUT_AWB) {
+ throw new AssertionError("Not implemented yet");
+ }
}
private static void mapCapabilities(CameraMetadataNative m, Camera.Parameters p) {
@@ -226,6 +315,12 @@ public class LegacyMetadataMapper {
}
}
+ if (LIE_ABOUT_FLASH && flashAvailable) {
+ // TODO: remove this branch
+ Log.w(TAG, "mapFlash - lying; saying we never support flash");
+ flashAvailable = false;
+ }
+
m.set(FLASH_INFO_AVAILABLE, flashAvailable);
}
@@ -320,7 +415,52 @@ public class LegacyMetadataMapper {
CaptureRequest request,
long timestamp) {
CameraMetadataNative result = new CameraMetadataNative();
+
+ /*
+ * control
+ */
+ // control.afState
+ if (LIE_ABOUT_AF) {
+ // TODO: Implement autofocus state machine
+ result.set(CaptureResult.CONTROL_AF_MODE, request.get(CaptureRequest.CONTROL_AF_MODE));
+ }
+
+ // control.aeState
+ if (LIE_ABOUT_AE) {
+ // Lie to pass CTS temporarily.
+ // TODO: Implement precapture trigger, after which we can report CONVERGED ourselves
+ result.set(CaptureResult.CONTROL_AE_STATE,
+ CONTROL_AE_STATE_CONVERGED);
+
+ result.set(CaptureResult.CONTROL_AE_MODE,
+ request.get(CaptureRequest.CONTROL_AE_MODE));
+ }
+
+ // control.awbLock
+ result.set(CaptureResult.CONTROL_AWB_LOCK, params.getAutoWhiteBalanceLock());
+
+ // control.awbState
+ if (LIE_ABOUT_AWB) {
+ // Lie to pass CTS temporarily.
+ // TODO: CTS needs to be updated not to query this value
+ // for LIMITED devices unless its guaranteed to be available.
+ result.set(CaptureResult.CONTROL_AWB_STATE,
+ CameraMetadata.CONTROL_AWB_STATE_CONVERGED);
+ // TODO: Read the awb mode from parameters instead
+ result.set(CaptureResult.CONTROL_AWB_MODE,
+ request.get(CaptureRequest.CONTROL_AWB_MODE));
+ }
+
+ /*
+ * lens
+ */
+ // lens.focalLength
result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
+
+ /*
+ * sensor
+ */
+ // sensor.timestamp
result.set(CaptureResult.SENSOR_TIMESTAMP, timestamp);
// TODO: Remaining result metadata tags conversions.
@@ -335,17 +475,149 @@ public class LegacyMetadataMapper {
*/
public static void convertRequestMetadata(CaptureRequest request,
/*out*/Camera.Parameters params) {
+
+ /*
+ * control.ae*
+ */
+ // control.aeAntibandingMode
Integer antiBandingMode = request.get(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE);
if (antiBandingMode != null) {
String legacyMode = convertAntiBandingModeToLegacy(antiBandingMode);
if (legacyMode != null) params.setAntibanding(legacyMode);
}
+ // control.aeTargetFpsRange
Range<Integer> aeFpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
if (aeFpsRange != null) {
int[] legacyFps = convertAeFpsRangeToLegacy(aeFpsRange);
params.setPreviewFpsRange(legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
}
+
+ /*
+ * control
+ */
+ // control.awbLock
+ Boolean awbLock = request.get(CaptureRequest.CONTROL_AWB_LOCK);
+ params.setAutoWhiteBalanceLock(awbLock == null ? false : awbLock);
+ }
+
+ /**
+ * Create an int[] from the List<> by using {@code convertFrom} and {@code convertTo}
+ * as a one-to-one map (via the index).
+ *
+ * <p>Strings not appearing in {@code convertFrom} are ignored (with a warning);
+ * strings appearing in {@code convertFrom} but not {@code convertTo} are silently
+ * dropped.</p>
+ *
+ * @param list Source list of strings
+ * @param convertFrom Conversion list of strings
+ * @param convertTo Conversion list of ints
+ * @return An array of ints where the values correspond to the ones in {@code convertTo}
+ * or {@code null} if {@code list} was {@code null}
+ */
+ private static int[] convertStringListToIntArray(
+ List<String> list, String[] convertFrom, int[] convertTo) {
+ if (list == null) {
+ return null;
+ }
+
+ List<Integer> convertedList = new ArrayList<>(list.size());
+
+ for (String str : list) {
+ int strIndex = getArrayIndex(convertFrom, str);
+
+ // Guard against bad API1 values
+ if (strIndex < 0) {
+ Log.w(TAG, "Ignoring invalid parameter " + str);
+ continue;
+ }
+
+ // Ignore values we can't map into (intentional)
+ if (strIndex < convertTo.length) {
+ convertedList.add(convertTo[strIndex]);
+ }
+ }
+
+ int[] returnArray = new int[convertedList.size()];
+ for (int i = 0; i < returnArray.length; ++i) {
+ returnArray[i] = convertedList.get(i);
+ }
+
+ return returnArray;
+ }
+
+ /** Return the index of {@code needle} in the {@code array}, or else {@code -1} */
+ private static <T> int getArrayIndex(T[] array, T needle) {
+ if (needle == null) {
+ return -1;
+ }
+
+ int index = 0;
+ for (T elem : array) {
+ if (Objects.equals(elem, needle)) {
+ return index;
+ }
+ index++;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Create a request template
+ *
+ * @param c a non-{@code null} camera characteristics for this camera
+ * @param templateId a non-negative template ID
+ *
+ * @return a non-{@code null} request template
+ *
+ * @throws IllegalArgumentException if {@code templateId} was invalid
+ *
+ * @see android.hardware.camera2.CameraDevice#TEMPLATE_MANUAL
+ */
+ public static CameraMetadataNative createRequestTemplate(
+ CameraCharacteristics c, int templateId) {
+ if (templateId < 0 || templateId > CameraDevice.TEMPLATE_MANUAL) {
+ throw new IllegalArgumentException("templateId out of range");
+ }
+
+ CameraMetadataNative m = new CameraMetadataNative();
+
+ /*
+ * NOTE: If adding new code here and it needs to query the static info,
+ * query the camera characteristics, so we can reuse this for api2 code later
+ * to create our own templates in the framework
+ */
+
+ if (LIE_ABOUT_AWB) {
+ m.set(CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_AUTO);
+ } else {
+ throw new AssertionError("Valid control.awbMode not implemented yet");
+ }
+
+ // control.aeMode
+ m.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON);
+ // AE is always unconditionally available in API1 devices
+
+ // control.afMode
+ {
+ Float minimumFocusDistance = c.get(LENS_INFO_MINIMUM_FOCUS_DISTANCE);
+
+ int afMode;
+ if (minimumFocusDistance != null &&
+ minimumFocusDistance == LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS) {
+ // Cannot control auto-focus with fixed-focus cameras
+ afMode = CameraMetadata.CONTROL_AF_MODE_OFF;
+ } else {
+ // If a minimum focus distance is reported; the camera must have AF
+ afMode = CameraMetadata.CONTROL_AF_MODE_AUTO;
+ }
+
+ m.set(CaptureRequest.CONTROL_AF_MODE, afMode);
+ }
+
+ // TODO: map other request template values
+ return m;
}
}
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index a4b10997125d..efc2b0eec2b4 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -64,7 +64,7 @@ public class RequestThreadManager {
private static final int MSG_CLEANUP = 3;
private static final int PREVIEW_FRAME_TIMEOUT = 300; // ms
- private static final int JPEG_FRAME_TIMEOUT = 1000; // ms
+ private static final int JPEG_FRAME_TIMEOUT = 3000; // ms (same as CTS for API2)
private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
private boolean mPreviewRunning = false;
@@ -105,11 +105,11 @@ public class RequestThreadManager {
/**
- * Comparator for {@link Size} objects.
+ * Comparator for {@link Size} objects by the area.
*
- * <p>This comparator compares by rectangle area. Tiebreaks on width.</p>
+ * <p>This comparator totally orders by rectangle area. Tiebreaks on width.</p>
*/
- private static class SizeComparator implements Comparator<Size> {
+ private static class SizeAreaComparator implements Comparator<Size> {
@Override
public int compare(Size size, Size size2) {
if (size == null || size2 == null) {
@@ -262,7 +262,11 @@ public class RequestThreadManager {
}
private void doJpegCapture(RequestHolder request) throws IOException {
+ if (DEBUG) Log.d(TAG, "doJpegCapture");
+
if (!mPreviewRunning) {
+ if (DEBUG) Log.d(TAG, "doJpegCapture - create fake surface");
+
createDummySurface();
mCamera.setPreviewTexture(mDummyTexture);
startPreview();
@@ -373,6 +377,18 @@ public class RequestThreadManager {
}
}
+ Size smallestSupportedJpegSize = calculatePictureSize(mCallbackOutputs, mParams);
+ if (smallestSupportedJpegSize != null) {
+ /*
+ * Set takePicture size to the smallest supported JPEG size large enough
+ * to scale/crop out of for the bounding rectangle of the configured JPEG sizes.
+ */
+
+ Log.i(TAG, "configureOutputs - set take picture size to " + smallestSupportedJpegSize);
+ mParams.setPictureSize(
+ smallestSupportedJpegSize.getWidth(), smallestSupportedJpegSize.getHeight());
+ }
+
// TODO: Detect and optimize single-output paths here to skip stream teeing.
if (mGLThreadManager == null) {
mGLThreadManager = new GLThreadManager(mCameraId);
@@ -385,10 +401,105 @@ public class RequestThreadManager {
if (mPreviewTexture != null) {
mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
}
+
+ // TODO: configure the JPEG surface with some arbitrary size
+ // using LegacyCameraDevice.nativeConfigureSurface
+ }
+
+ /**
+ * Find a JPEG size (that is supported by the legacy camera device) which is equal to or larger
+ * than all of the configured {@code JPEG} outputs (by both width and height).
+ *
+ * <p>If multiple supported JPEG sizes are larger, select the smallest of them which
+ * still satisfies the above constraint.</p>
+ *
+ * <p>As a result, the returned size is guaranteed to be usable without needing
+ * to upscale any of the outputs. If only one {@code JPEG} surface is used,
+ * then no scaling/cropping is necessary between the taken picture and
+ * the {@code JPEG} output surface.</p>
+ *
+ * @param callbackOutputs a non-{@code null} list of {@code Surface}s with any image formats
+ * @param params api1 parameters (used for reading only)
+ *
+ * @return a size large enough to fit all of the configured {@code JPEG} outputs, or
+ * {@code null} if the {@code callbackOutputs} did not have any {@code JPEG}
+ * surfaces.
+ */
+ private Size calculatePictureSize(
+ Collection<Surface> callbackOutputs, Camera.Parameters params) {
+ /*
+ * Find the largest JPEG size (if any), from the configured outputs:
+ * - the api1 picture size should be set to the smallest legal size that's at least as large
+ * as the largest configured JPEG size
+ */
+ List<Size> configuredJpegSizes = new ArrayList<Size>();
+ for (Surface callbackSurface : callbackOutputs) {
+ int format = LegacyCameraDevice.nativeDetectSurfaceType(callbackSurface);
+
+ if (format != CameraMetadataNative.NATIVE_JPEG_FORMAT) {
+ continue; // Ignore non-JPEG callback formats
+ }
+
+ Size jpegSize = LegacyCameraDevice.getSurfaceSize(callbackSurface);
+ configuredJpegSizes.add(jpegSize);
+ }
+ if (!configuredJpegSizes.isEmpty()) {
+ /*
+ * Find the largest configured JPEG width, and height, independently
+ * of the rest.
+ *
+ * The rest of the JPEG streams can be cropped out of this smallest bounding
+ * rectangle.
+ */
+ int maxConfiguredJpegWidth = -1;
+ int maxConfiguredJpegHeight = -1;
+ for (Size jpegSize : configuredJpegSizes) {
+ maxConfiguredJpegWidth = jpegSize.getWidth() > maxConfiguredJpegWidth ?
+ jpegSize.getWidth() : maxConfiguredJpegWidth;
+ maxConfiguredJpegHeight = jpegSize.getHeight() > maxConfiguredJpegHeight ?
+ jpegSize.getHeight() : maxConfiguredJpegHeight;
+ }
+ Size smallestBoundJpegSize = new Size(maxConfiguredJpegWidth, maxConfiguredJpegHeight);
+
+ List<Size> supportedJpegSizes = convertSizeList(params.getSupportedPictureSizes());
+
+ /*
+ * Find the smallest supported JPEG size that can fit the smallest bounding
+ * rectangle for the configured JPEG sizes.
+ */
+ List<Size> candidateSupportedJpegSizes = new ArrayList<>();
+ for (Size supportedJpegSize : supportedJpegSizes) {
+ if (supportedJpegSize.getWidth() >= maxConfiguredJpegWidth &&
+ supportedJpegSize.getHeight() >= maxConfiguredJpegHeight) {
+ candidateSupportedJpegSizes.add(supportedJpegSize);
+ }
+ }
+
+ if (candidateSupportedJpegSizes.isEmpty()) {
+ throw new AssertionError(
+ "Could not find any supported JPEG sizes large enough to fit " +
+ smallestBoundJpegSize);
+ }
+
+ Size smallestSupportedJpegSize = Collections.min(candidateSupportedJpegSizes,
+ new SizeAreaComparator());
+
+ if (!smallestSupportedJpegSize.equals(smallestBoundJpegSize)) {
+ Log.w(TAG,
+ String.format(
+ "configureOutputs - Will need to crop picture %s into "
+ + "smallest bound size %s",
+ smallestSupportedJpegSize, smallestBoundJpegSize));
+ }
+
+ return smallestSupportedJpegSize;
+ }
+
+ return null;
}
private static Size findLargestByArea(List<Size> sizes) {
- return Collections.max(sizes, new SizeComparator());
+ return Collections.max(sizes, new SizeAreaComparator());
}
private static boolean checkAspectRatiosMatch(Size a, Size b) {
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index 004842649788..57058a6269a2 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "Legacy-CameraDevice-JNI"
+// #define LOG_NDEBUG 0
#include <utils/Log.h>
#include <utils/Errors.h>
#include <utils/Trace.h>
@@ -26,6 +27,7 @@
#include <ui/GraphicBuffer.h>
#include <system/window.h>
+#include <hardware/camera3.h>
using namespace android;
@@ -118,8 +120,9 @@ static status_t configureSurface(const sp<ANativeWindow>& anw,
return err;
}
- ALOGV("%s: Setting buffer count to %d", __FUNCTION__,
- maxBufferSlack + 1 + minUndequeuedBuffers);
+ ALOGV("%s: Setting buffer count to %d, size to (%dx%d), fmt (0x%x)", __FUNCTION__,
+ maxBufferSlack + 1 + minUndequeuedBuffers,
+ width, height, pixelFmt);
err = native_window_set_buffer_count(anw.get(), maxBufferSlack + 1 + minUndequeuedBuffers);
if (err != NO_ERROR) {
ALOGE("%s: Failed to set native window buffer count, error %s (%d).", __FUNCTION__,
@@ -148,11 +151,29 @@ static status_t produceFrame(const sp<ANativeWindow>& anw,
int32_t width, // Width of the pixelBuffer
int32_t height, // Height of the pixelBuffer
int32_t pixelFmt, // Format of the pixelBuffer
- int64_t bufSize) {
+ int32_t bufSize) {
ATRACE_CALL();
status_t err = NO_ERROR;
ANativeWindowBuffer* anb;
- ALOGV("%s: Dequeue buffer from %p",__FUNCTION__, anw.get());
+ ALOGV("%s: Dequeue buffer from %p %dx%d (fmt=%x, size=%x)",
+ __FUNCTION__, anw.get(), width, height, 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 (width < 0) {
+ ALOGE("%s: width must be non-negative", __FUNCTION__);
+ return BAD_VALUE;
+ } else if (height < 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;
+ }
if (width < 0 || height < 0 || bufSize < 0) {
ALOGE("%s: Illegal argument, negative dimension passed to produceFrame", __FUNCTION__);
@@ -163,6 +184,8 @@ static status_t produceFrame(const sp<ANativeWindow>& anw,
err = native_window_dequeue_buffer_and_wait(anw.get(), &anb);
if (err != NO_ERROR) return err;
+ // TODO: check anb is large enough to store the results
+
sp<GraphicBuffer> buf(new GraphicBuffer(anb, /*keepOwnership*/false));
switch(pixelFmt) {
@@ -257,7 +280,12 @@ static status_t produceFrame(const sp<ANativeWindow>& anw,
err);
return err;
}
+ struct camera3_jpeg_blob footer = {
+ jpeg_blob_id: CAMERA3_JPEG_BLOB_ID,
+ jpeg_size: (uint32_t)width
+ };
memcpy(img, pixelBuffer, width);
+ memcpy(img + anb->width - sizeof(footer), &footer, sizeof(footer));
break;
}
default: {
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 41ed9e133706..ad7ee7ad091e 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -33,6 +33,9 @@
#include <jni.h>
#include <JNIHelp.h>
+#include <stdint.h>
+#include <inttypes.h>
+
#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
#define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID "mNativeContext"
@@ -300,6 +303,14 @@ static uint32_t Image_getJpegSize(CpuConsumer::LockedBuffer* buffer)
// failed to find size, default to whole buffer
if (size == 0) {
+ /*
+ * This is a problem because not including the JPEG header
+ * means that in certain rare situations a regular JPEG blob
+ * will be misidentified as having a header, in which case
+ * we will get a garbage size value.
+ */
+ ALOGW("%s: No JPEG header detected, defaulting to size=width=%d",
+ __FUNCTION__, width);
size = width;
}
@@ -848,6 +859,14 @@ static jobject Image_getByteBuffer(JNIEnv* env, jobject thiz, int idx)
// Create byteBuffer from native buffer
Image_getLockedBufferInfo(env, buffer, idx, &base, &size);
+
+ if (size > static_cast<uint32_t>(INT32_MAX)) {
+ // Byte buffer have 'int capacity', so check the range
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Size too large for bytebuffer capacity " PRIu32, size);
+ return NULL;
+ }
+
byteBuffer = env->NewDirectByteBuffer(base, size);
// TODO: throw dvm exOutOfMemoryError?
if ((byteBuffer == NULL) && (env->ExceptionCheck() == false)) {