Loading core/java/android/hardware/camera2/CaptureResult.java +0 −5 Original line number Diff line number Diff line Loading @@ -60,11 +60,6 @@ public final class CaptureResult extends CameraMetadata { @Override public <T> T get(Key<T> key) { if (key == STATISTICS_FACES) { // Don't throw IllegalArgumentException // TODO: Implement android.statistics.faces return null; } return mResults.get(key); } Loading core/java/android/hardware/camera2/Face.java +13 −3 Original line number Diff line number Diff line Loading @@ -58,6 +58,9 @@ public final class Face { * Create a new face with all fields set. * * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional. * They are only required when the {@link #CaptureResult} reports that the value of key * {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} is * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_FULL}. * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, * rightEyePosition, and mouthPosition may be independently null or not-null.</p> Loading Loading @@ -107,7 +110,11 @@ public final class Face { * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional. * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, * rightEyePosition, and mouthPosition may be independently null or not-null.</p> * rightEyePosition, and mouthPosition may be independently null or not-null. When devices * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link #CaptureResult}, * the face id of each face is expected to be {@value #ID_UNSUPPORTED}, the leftEyePosition, * rightEyePosition, and mouthPositions are expected to be {@code null} for each face.</p> * * @param bounds Bounds of the face. * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}. Loading Loading @@ -168,7 +175,10 @@ public final class Face { * <p>This is an optional field, may not be supported on all devices. * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, * rightEyePosition, and mouthPosition may be independently null or not-null.</p> * rightEyePosition, and mouthPosition may be independently null or not-null. When devices * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link #CaptureResult}, * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p> * * <p>This value will either be {@value #ID_UNSUPPORTED} or * otherwise greater than {@code 0}.</p> Loading Loading @@ -219,7 +229,7 @@ public final class Face { * field, may not be supported on all devices. If not * supported, the value will always be set to null. * This value will always be null only if {@link #getId()} returns * {@value #ID_UNSUPPORTED}.</p> them are. * {@value #ID_UNSUPPORTED}.</p> * </p> * * @return The mouth position, or {@code null} if unknown. Loading core/java/android/hardware/camera2/impl/CameraMetadataNative.java +166 −24 Original line number Diff line number Diff line Loading @@ -16,7 +16,13 @@ package android.hardware.camera2.impl; import android.graphics.ImageFormat; import android.graphics.Point; import android.graphics.Rect; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.Face; import android.hardware.camera2.Rational; import android.os.Parcelable; import android.os.Parcel; Loading @@ -36,6 +42,8 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { private static final String TAG = "CameraMetadataJV"; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); // this should be in sync with HAL_PIXEL_FORMAT_BLOB defined in graphics.h private static final int NATIVE_JPEG_FORMAT = 0x21; public CameraMetadataNative() { super(); Loading Loading @@ -84,16 +92,12 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { @SuppressWarnings("unchecked") @Override public <T> T get(Key<T> key) { int tag = key.getTag(); byte[] values = readValues(tag); if (values == null) { return null; T value = getOverride(key); if (value != null) { return value; } int nativeType = getNativeType(tag); ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); return unpackSingle(buffer, key.getType(), nativeType); return getBase(key); } public void readFromParcel(Parcel in) { Loading @@ -110,24 +114,11 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { * type to the key. */ public <T> void set(Key<T> key, T value) { int tag = key.getTag(); if (value == null) { writeValues(tag, null); if (setOverride(key, value)) { return; } int nativeType = getNativeType(tag); int size = packSingle(value, null, key.getType(), nativeType, /* sizeOnly */true); // TODO: Optimization. Cache the byte[] and reuse if the size is big enough. byte[] values = new byte[size]; ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); packSingle(value, buffer, key.getType(), nativeType, /*sizeOnly*/false); writeValues(tag, values); setBase(key, value); } // Keep up-to-date with camera_metadata.h Loading Loading @@ -435,6 +426,157 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { return (T) array; } private <T> T getBase(Key<T> key) { int tag = key.getTag(); byte[] values = readValues(tag); if (values == null) { return null; } int nativeType = getNativeType(tag); ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); return unpackSingle(buffer, key.getType(), nativeType); } // Need overwrite some metadata that has different definitions between native // and managed sides. @SuppressWarnings("unchecked") private <T> T getOverride(Key<T> key) { if (key == CameraCharacteristics.SCALER_AVAILABLE_FORMATS) { return (T) getAvailableFormats(); } else if (key == CaptureResult.STATISTICS_FACES) { return (T) getFaces(); } // For other keys, get() falls back to getBase() return null; } private int[] getAvailableFormats() { int[] availableFormats = getBase(CameraCharacteristics.SCALER_AVAILABLE_FORMATS); for (int i = 0; i < availableFormats.length; i++) { // JPEG has different value between native and managed side, need override. if (availableFormats[i] == NATIVE_JPEG_FORMAT) { availableFormats[i] = ImageFormat.JPEG; } } return availableFormats; } private Face[] getFaces() { final int FACE_LANDMARK_SIZE = 6; Integer faceDetectMode = getBase(CaptureResult.STATISTICS_FACE_DETECT_MODE); if (faceDetectMode == null) { throw new AssertionError("Expect non-null face detect mode"); } if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF) { return new Face[0]; } if (faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE && faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) { throw new AssertionError("Unknown face detect mode: " + faceDetectMode); } // Face scores and rectangles are required by SIMPLE and FULL mode. byte[] faceScores = getBase(CaptureResult.STATISTICS_FACE_SCORES); Rect[] faceRectangles = getBase(CaptureResult.STATISTICS_FACE_RECTANGLES); if (faceScores == null || faceRectangles == null) { throw new AssertionError("Expect face scores and rectangles to be non-null"); } else if (faceScores.length != faceRectangles.length) { throw new AssertionError( String.format("Face score size(%d) doesn match face rectangle size(%d)!", faceScores.length, faceRectangles.length)); } // Face id and landmarks are only required by FULL mode. int[] faceIds = getBase(CaptureResult.STATISTICS_FACE_IDS); int[] faceLandmarks = getBase(CaptureResult.STATISTICS_FACE_LANDMARKS); int numFaces = faceScores.length; if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) { if (faceIds == null || faceLandmarks == null) { throw new AssertionError("Expect face ids and landmarks to be non-null for " + "FULL mode"); } else if (faceIds.length != numFaces || faceLandmarks.length != numFaces * FACE_LANDMARK_SIZE) { throw new AssertionError( String.format("Face id size(%d), or face landmark size(%d) don't match " + "face number(%d)!", faceIds.length, faceLandmarks.length * FACE_LANDMARK_SIZE, numFaces)); } } Face[] faces = new Face[numFaces]; if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE) { for (int i = 0; i < numFaces; i++) { faces[i] = new Face(faceRectangles[i], faceScores[i]); } } else { // CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL for (int i = 0; i < numFaces; i++) { Point leftEye = new Point(faceLandmarks[i*6], faceLandmarks[i*6+1]); Point rightEye = new Point(faceLandmarks[i*6+2], faceLandmarks[i*6+3]); Point mouth = new Point(faceLandmarks[i*6+4], faceLandmarks[i*6+5]); faces[i] = new Face(faceRectangles[i], faceScores[i], faceIds[i], leftEye, rightEye, mouth); } } return faces; } private <T> void setBase(Key<T> key, T value) { int tag = key.getTag(); if (value == null) { writeValues(tag, null); return; } int nativeType = getNativeType(tag); int size = packSingle(value, null, key.getType(), nativeType, /* sizeOnly */true); // TODO: Optimization. Cache the byte[] and reuse if the size is big enough. byte[] values = new byte[size]; ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); packSingle(value, buffer, key.getType(), nativeType, /*sizeOnly*/false); writeValues(tag, values); } // Set the camera metadata override. private <T> boolean setOverride(Key<T> key, T value) { if (key == CameraCharacteristics.SCALER_AVAILABLE_FORMATS) { return setAvailableFormats((int[]) value); } // For other keys, set() falls back to setBase(). return false; } private boolean setAvailableFormats(int[] value) { int[] availableFormat = value; if (value == null) { // Let setBase() to handle the null value case. return false; } int[] newValues = new int[availableFormat.length]; for (int i = 0; i < availableFormat.length; i++) { newValues[i] = availableFormat[i]; if (availableFormat[i] == ImageFormat.JPEG) { newValues[i] = NATIVE_JPEG_FORMAT; } } setBase(CameraCharacteristics.SCALER_AVAILABLE_FORMATS, newValues); return true; } private long mMetadataPtr; // native CameraMetadata* private native long nativeAllocate(); Loading media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java +142 −26 Original line number Diff line number Diff line Loading @@ -18,9 +18,11 @@ package com.android.mediaframeworktest.unit; import android.os.Parcel; import android.test.suitebuilder.annotation.SmallTest; import android.graphics.ImageFormat; import android.graphics.Point; import android.graphics.Rect; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.Face; import android.hardware.camera2.Rational; import android.hardware.camera2.Size; import android.hardware.camera2.impl.CameraMetadataNative; Loading @@ -30,9 +32,6 @@ import static android.hardware.camera2.impl.CameraMetadataNative.*; import java.lang.reflect.Array; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import static org.junit.Assert.assertArrayEquals; /** * <pre> Loading @@ -57,6 +56,7 @@ public class CameraMetadataTest extends junit.framework.TestCase { // Tags static final int ANDROID_COLOR_CORRECTION_MODE = ANDROID_COLOR_CORRECTION_START; static final int ANDROID_COLOR_CORRECTION_TRANSFORM = ANDROID_COLOR_CORRECTION_START + 1; static final int ANDROID_COLOR_CORRECTION_GAINS = ANDROID_COLOR_CORRECTION_START + 2; static final int ANDROID_CONTROL_AE_ANTIBANDING_MODE = ANDROID_CONTROL_START; static final int ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION = ANDROID_CONTROL_START + 1; Loading Loading @@ -131,7 +131,8 @@ public class CameraMetadataTest extends junit.framework.TestCase { @SmallTest public void testGetTypeFromTag() { assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_MODE)); assertEquals(TYPE_FLOAT, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM)); assertEquals(TYPE_RATIONAL, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM)); assertEquals(TYPE_FLOAT, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_GAINS)); assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE)); assertEquals(TYPE_INT32, CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION)); Loading Loading @@ -187,30 +188,30 @@ public class CameraMetadataTest extends junit.framework.TestCase { assertEquals(false, mMetadata.isEmpty()); // // android.colorCorrection.transform (3x3 matrix) // android.colorCorrection.colorCorrectionGains (float x 4 array) // final float[] transformMatrix = new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; byte[] transformMatrixAsByteArray = new byte[transformMatrix.length * 4]; ByteBuffer transformMatrixByteBuffer = ByteBuffer.wrap(transformMatrixAsByteArray).order(ByteOrder.nativeOrder()); for (float f : transformMatrix) transformMatrixByteBuffer.putFloat(f); final float[] colorCorrectionGains = new float[] { 1.0f, 2.0f, 3.0f, 4.0f}; byte[] colorCorrectionGainsAsByteArray = new byte[colorCorrectionGains.length * 4]; ByteBuffer colorCorrectionGainsByteBuffer = ByteBuffer.wrap(colorCorrectionGainsAsByteArray).order(ByteOrder.nativeOrder()); for (float f : colorCorrectionGains) colorCorrectionGainsByteBuffer.putFloat(f); // Read assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM)); mMetadata.writeValues(ANDROID_COLOR_CORRECTION_TRANSFORM, transformMatrixAsByteArray); assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); mMetadata.writeValues(ANDROID_COLOR_CORRECTION_GAINS, colorCorrectionGainsAsByteArray); // Write assertArrayEquals(transformMatrixAsByteArray, mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM)); assertArrayEquals(colorCorrectionGainsAsByteArray, mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); assertEquals(2, mMetadata.getEntryCount()); assertEquals(false, mMetadata.isEmpty()); // Erase mMetadata.writeValues(ANDROID_COLOR_CORRECTION_TRANSFORM, null); assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM)); mMetadata.writeValues(ANDROID_COLOR_CORRECTION_GAINS, null); assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); assertEquals(1, mMetadata.getEntryCount()); } Loading Loading @@ -279,7 +280,7 @@ public class CameraMetadataTest extends junit.framework.TestCase { @SmallTest public void testReadWritePrimitiveArray() { // int32 (n) checkKeyGetAndSetArray("android.sensor.info.availableSensitivities", int[].class, checkKeyGetAndSetArray("android.sensor.info.sensitivityRange", int[].class, new int[] { 0xC0FFEE, 0xDEADF00D }); Loading Loading @@ -379,7 +380,9 @@ public class CameraMetadataTest extends junit.framework.TestCase { new AvailableFormat[] { AvailableFormat.RAW_SENSOR, AvailableFormat.YV12, AvailableFormat.IMPLEMENTATION_DEFINED AvailableFormat.IMPLEMENTATION_DEFINED, AvailableFormat.YCbCr_420_888, AvailableFormat.BLOB }); } Loading Loading @@ -431,12 +434,13 @@ public class CameraMetadataTest extends junit.framework.TestCase { AvailableFormat.RAW_SENSOR, AvailableFormat.YV12, AvailableFormat.IMPLEMENTATION_DEFINED, AvailableFormat.YCbCr_420_888 AvailableFormat.YCbCr_420_888, AvailableFormat.BLOB }); Key<AeAntibandingMode> availableFormatsKey = new Key<AeAntibandingMode>("android.scaler.availableFormats", AeAntibandingMode.class); Key<AvailableFormat[]> availableFormatsKey = new Key<AvailableFormat[]>("android.scaler.availableFormats", AvailableFormat[].class); byte[] availableFormatValues = mMetadata.readValues(CameraMetadataNative .getTag(availableFormatsKey.getName())); Loading @@ -444,7 +448,8 @@ public class CameraMetadataTest extends junit.framework.TestCase { 0x20, 0x32315659, 0x22, 0x23 0x23, 0x21 }; ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder()); Loading Loading @@ -523,4 +528,115 @@ public class CameraMetadataTest extends junit.framework.TestCase { <T> void compareGeneric(T expected, T actual) { assertEquals(expected, actual); } @SmallTest public void testReadWriteOverride() { // // android.scaler.availableFormats (int x n array) // int[] availableFormats = new int[] { 0x20, // RAW_SENSOR 0x32315659, // YV12 0x11, // YCrCb_420_SP 0x100, // ImageFormat.JPEG 0x22, // IMPLEMENTATION_DEFINED 0x23, // YCbCr_420_888 }; int[] expectedIntValues = new int[] { 0x20, // RAW_SENSOR 0x32315659, // YV12 0x11, // YCrCb_420_SP 0x21, // BLOB 0x22, // IMPLEMENTATION_DEFINED 0x23, // YCbCr_420_888 }; int availableFormatTag = CameraMetadataNative.getTag("android.scaler.availableFormats"); // Write mMetadata.set(CameraCharacteristics.SCALER_AVAILABLE_FORMATS, availableFormats); byte[] availableFormatValues = mMetadata.readValues(availableFormatTag); ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder()); assertEquals(expectedIntValues.length * 4, availableFormatValues.length); for (int i = 0; i < expectedIntValues.length; ++i) { assertEquals(expectedIntValues[i], bf.getInt()); } // Read byte[] availableFormatsAsByteArray = new byte[expectedIntValues.length * 4]; ByteBuffer availableFormatsByteBuffer = ByteBuffer.wrap(availableFormatsAsByteArray).order(ByteOrder.nativeOrder()); for (int value : expectedIntValues) { availableFormatsByteBuffer.putInt(value); } mMetadata.writeValues(availableFormatTag, availableFormatsAsByteArray); int[] resultFormats = mMetadata.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS); assertNotNull("result available formats shouldn't be null", resultFormats); assertArrayEquals(availableFormats, resultFormats); // // android.statistics.faces (Face x n array) // int[] expectedFaceIds = new int[] {1, 2, 3, 4, 5}; byte[] expectedFaceScores = new byte[] {10, 20, 30, 40, 50}; int numFaces = expectedFaceIds.length; Rect[] expectedRects = new Rect[numFaces]; for (int i = 0; i < numFaces; i++) { expectedRects[i] = new Rect(i*4 + 1, i * 4 + 2, i * 4 + 3, i * 4 + 4); } int[] expectedFaceLM = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, }; Point[] expectedFaceLMPoints = new Point[numFaces * 3]; for (int i = 0; i < numFaces; i++) { expectedFaceLMPoints[i*3] = new Point(expectedFaceLM[i*6], expectedFaceLM[i*6+1]); expectedFaceLMPoints[i*3+1] = new Point(expectedFaceLM[i*6+2], expectedFaceLM[i*6+3]); expectedFaceLMPoints[i*3+2] = new Point(expectedFaceLM[i*6+4], expectedFaceLM[i*6+5]); } /** * Read - FACE_DETECT_MODE == FULL */ mMetadata.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL); mMetadata.set(CaptureResult.STATISTICS_FACE_IDS, expectedFaceIds); mMetadata.set(CaptureResult.STATISTICS_FACE_SCORES, expectedFaceScores); mMetadata.set(CaptureResult.STATISTICS_FACE_RECTANGLES, expectedRects); mMetadata.set(CaptureResult.STATISTICS_FACE_LANDMARKS, expectedFaceLM); Face[] resultFaces = mMetadata.get(CaptureResult.STATISTICS_FACES); assertEquals(numFaces, resultFaces.length); for (int i = 0; i < numFaces; i++) { assertEquals(expectedFaceIds[i], resultFaces[i].getId()); assertEquals(expectedFaceScores[i], resultFaces[i].getScore()); assertEquals(expectedRects[i], resultFaces[i].getBounds()); assertEquals(expectedFaceLMPoints[i*3], resultFaces[i].getLeftEyePosition()); assertEquals(expectedFaceLMPoints[i*3+1], resultFaces[i].getRightEyePosition()); assertEquals(expectedFaceLMPoints[i*3+2], resultFaces[i].getMouthPosition()); } /** * Read - FACE_DETECT_MODE == SIMPLE */ mMetadata.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE); mMetadata.set(CaptureResult.STATISTICS_FACE_SCORES, expectedFaceScores); mMetadata.set(CaptureResult.STATISTICS_FACE_RECTANGLES, expectedRects); Face[] resultSimpleFaces = mMetadata.get(CaptureResult.STATISTICS_FACES); assertEquals(numFaces, resultSimpleFaces.length); for (int i = 0; i < numFaces; i++) { assertEquals(Face.ID_UNSUPPORTED, resultSimpleFaces[i].getId()); assertEquals(expectedFaceScores[i], resultSimpleFaces[i].getScore()); assertEquals(expectedRects[i], resultSimpleFaces[i].getBounds()); assertNull(resultSimpleFaces[i].getLeftEyePosition()); assertNull(resultSimpleFaces[i].getRightEyePosition()); assertNull(resultSimpleFaces[i].getMouthPosition()); } } } Loading
core/java/android/hardware/camera2/CaptureResult.java +0 −5 Original line number Diff line number Diff line Loading @@ -60,11 +60,6 @@ public final class CaptureResult extends CameraMetadata { @Override public <T> T get(Key<T> key) { if (key == STATISTICS_FACES) { // Don't throw IllegalArgumentException // TODO: Implement android.statistics.faces return null; } return mResults.get(key); } Loading
core/java/android/hardware/camera2/Face.java +13 −3 Original line number Diff line number Diff line Loading @@ -58,6 +58,9 @@ public final class Face { * Create a new face with all fields set. * * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional. * They are only required when the {@link #CaptureResult} reports that the value of key * {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} is * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_FULL}. * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, * rightEyePosition, and mouthPosition may be independently null or not-null.</p> Loading Loading @@ -107,7 +110,11 @@ public final class Face { * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional. * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, * rightEyePosition, and mouthPosition may be independently null or not-null.</p> * rightEyePosition, and mouthPosition may be independently null or not-null. When devices * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link #CaptureResult}, * the face id of each face is expected to be {@value #ID_UNSUPPORTED}, the leftEyePosition, * rightEyePosition, and mouthPositions are expected to be {@code null} for each face.</p> * * @param bounds Bounds of the face. * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}. Loading Loading @@ -168,7 +175,10 @@ public final class Face { * <p>This is an optional field, may not be supported on all devices. * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, * rightEyePosition, and mouthPosition may be independently null or not-null.</p> * rightEyePosition, and mouthPosition may be independently null or not-null. When devices * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link #CaptureResult}, * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p> * * <p>This value will either be {@value #ID_UNSUPPORTED} or * otherwise greater than {@code 0}.</p> Loading Loading @@ -219,7 +229,7 @@ public final class Face { * field, may not be supported on all devices. If not * supported, the value will always be set to null. * This value will always be null only if {@link #getId()} returns * {@value #ID_UNSUPPORTED}.</p> them are. * {@value #ID_UNSUPPORTED}.</p> * </p> * * @return The mouth position, or {@code null} if unknown. Loading
core/java/android/hardware/camera2/impl/CameraMetadataNative.java +166 −24 Original line number Diff line number Diff line Loading @@ -16,7 +16,13 @@ package android.hardware.camera2.impl; import android.graphics.ImageFormat; import android.graphics.Point; import android.graphics.Rect; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.Face; import android.hardware.camera2.Rational; import android.os.Parcelable; import android.os.Parcel; Loading @@ -36,6 +42,8 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { private static final String TAG = "CameraMetadataJV"; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); // this should be in sync with HAL_PIXEL_FORMAT_BLOB defined in graphics.h private static final int NATIVE_JPEG_FORMAT = 0x21; public CameraMetadataNative() { super(); Loading Loading @@ -84,16 +92,12 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { @SuppressWarnings("unchecked") @Override public <T> T get(Key<T> key) { int tag = key.getTag(); byte[] values = readValues(tag); if (values == null) { return null; T value = getOverride(key); if (value != null) { return value; } int nativeType = getNativeType(tag); ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); return unpackSingle(buffer, key.getType(), nativeType); return getBase(key); } public void readFromParcel(Parcel in) { Loading @@ -110,24 +114,11 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { * type to the key. */ public <T> void set(Key<T> key, T value) { int tag = key.getTag(); if (value == null) { writeValues(tag, null); if (setOverride(key, value)) { return; } int nativeType = getNativeType(tag); int size = packSingle(value, null, key.getType(), nativeType, /* sizeOnly */true); // TODO: Optimization. Cache the byte[] and reuse if the size is big enough. byte[] values = new byte[size]; ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); packSingle(value, buffer, key.getType(), nativeType, /*sizeOnly*/false); writeValues(tag, values); setBase(key, value); } // Keep up-to-date with camera_metadata.h Loading Loading @@ -435,6 +426,157 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { return (T) array; } private <T> T getBase(Key<T> key) { int tag = key.getTag(); byte[] values = readValues(tag); if (values == null) { return null; } int nativeType = getNativeType(tag); ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); return unpackSingle(buffer, key.getType(), nativeType); } // Need overwrite some metadata that has different definitions between native // and managed sides. @SuppressWarnings("unchecked") private <T> T getOverride(Key<T> key) { if (key == CameraCharacteristics.SCALER_AVAILABLE_FORMATS) { return (T) getAvailableFormats(); } else if (key == CaptureResult.STATISTICS_FACES) { return (T) getFaces(); } // For other keys, get() falls back to getBase() return null; } private int[] getAvailableFormats() { int[] availableFormats = getBase(CameraCharacteristics.SCALER_AVAILABLE_FORMATS); for (int i = 0; i < availableFormats.length; i++) { // JPEG has different value between native and managed side, need override. if (availableFormats[i] == NATIVE_JPEG_FORMAT) { availableFormats[i] = ImageFormat.JPEG; } } return availableFormats; } private Face[] getFaces() { final int FACE_LANDMARK_SIZE = 6; Integer faceDetectMode = getBase(CaptureResult.STATISTICS_FACE_DETECT_MODE); if (faceDetectMode == null) { throw new AssertionError("Expect non-null face detect mode"); } if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF) { return new Face[0]; } if (faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE && faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) { throw new AssertionError("Unknown face detect mode: " + faceDetectMode); } // Face scores and rectangles are required by SIMPLE and FULL mode. byte[] faceScores = getBase(CaptureResult.STATISTICS_FACE_SCORES); Rect[] faceRectangles = getBase(CaptureResult.STATISTICS_FACE_RECTANGLES); if (faceScores == null || faceRectangles == null) { throw new AssertionError("Expect face scores and rectangles to be non-null"); } else if (faceScores.length != faceRectangles.length) { throw new AssertionError( String.format("Face score size(%d) doesn match face rectangle size(%d)!", faceScores.length, faceRectangles.length)); } // Face id and landmarks are only required by FULL mode. int[] faceIds = getBase(CaptureResult.STATISTICS_FACE_IDS); int[] faceLandmarks = getBase(CaptureResult.STATISTICS_FACE_LANDMARKS); int numFaces = faceScores.length; if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) { if (faceIds == null || faceLandmarks == null) { throw new AssertionError("Expect face ids and landmarks to be non-null for " + "FULL mode"); } else if (faceIds.length != numFaces || faceLandmarks.length != numFaces * FACE_LANDMARK_SIZE) { throw new AssertionError( String.format("Face id size(%d), or face landmark size(%d) don't match " + "face number(%d)!", faceIds.length, faceLandmarks.length * FACE_LANDMARK_SIZE, numFaces)); } } Face[] faces = new Face[numFaces]; if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE) { for (int i = 0; i < numFaces; i++) { faces[i] = new Face(faceRectangles[i], faceScores[i]); } } else { // CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL for (int i = 0; i < numFaces; i++) { Point leftEye = new Point(faceLandmarks[i*6], faceLandmarks[i*6+1]); Point rightEye = new Point(faceLandmarks[i*6+2], faceLandmarks[i*6+3]); Point mouth = new Point(faceLandmarks[i*6+4], faceLandmarks[i*6+5]); faces[i] = new Face(faceRectangles[i], faceScores[i], faceIds[i], leftEye, rightEye, mouth); } } return faces; } private <T> void setBase(Key<T> key, T value) { int tag = key.getTag(); if (value == null) { writeValues(tag, null); return; } int nativeType = getNativeType(tag); int size = packSingle(value, null, key.getType(), nativeType, /* sizeOnly */true); // TODO: Optimization. Cache the byte[] and reuse if the size is big enough. byte[] values = new byte[size]; ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); packSingle(value, buffer, key.getType(), nativeType, /*sizeOnly*/false); writeValues(tag, values); } // Set the camera metadata override. private <T> boolean setOverride(Key<T> key, T value) { if (key == CameraCharacteristics.SCALER_AVAILABLE_FORMATS) { return setAvailableFormats((int[]) value); } // For other keys, set() falls back to setBase(). return false; } private boolean setAvailableFormats(int[] value) { int[] availableFormat = value; if (value == null) { // Let setBase() to handle the null value case. return false; } int[] newValues = new int[availableFormat.length]; for (int i = 0; i < availableFormat.length; i++) { newValues[i] = availableFormat[i]; if (availableFormat[i] == ImageFormat.JPEG) { newValues[i] = NATIVE_JPEG_FORMAT; } } setBase(CameraCharacteristics.SCALER_AVAILABLE_FORMATS, newValues); return true; } private long mMetadataPtr; // native CameraMetadata* private native long nativeAllocate(); Loading
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java +142 −26 Original line number Diff line number Diff line Loading @@ -18,9 +18,11 @@ package com.android.mediaframeworktest.unit; import android.os.Parcel; import android.test.suitebuilder.annotation.SmallTest; import android.graphics.ImageFormat; import android.graphics.Point; import android.graphics.Rect; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.Face; import android.hardware.camera2.Rational; import android.hardware.camera2.Size; import android.hardware.camera2.impl.CameraMetadataNative; Loading @@ -30,9 +32,6 @@ import static android.hardware.camera2.impl.CameraMetadataNative.*; import java.lang.reflect.Array; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import static org.junit.Assert.assertArrayEquals; /** * <pre> Loading @@ -57,6 +56,7 @@ public class CameraMetadataTest extends junit.framework.TestCase { // Tags static final int ANDROID_COLOR_CORRECTION_MODE = ANDROID_COLOR_CORRECTION_START; static final int ANDROID_COLOR_CORRECTION_TRANSFORM = ANDROID_COLOR_CORRECTION_START + 1; static final int ANDROID_COLOR_CORRECTION_GAINS = ANDROID_COLOR_CORRECTION_START + 2; static final int ANDROID_CONTROL_AE_ANTIBANDING_MODE = ANDROID_CONTROL_START; static final int ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION = ANDROID_CONTROL_START + 1; Loading Loading @@ -131,7 +131,8 @@ public class CameraMetadataTest extends junit.framework.TestCase { @SmallTest public void testGetTypeFromTag() { assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_MODE)); assertEquals(TYPE_FLOAT, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM)); assertEquals(TYPE_RATIONAL, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM)); assertEquals(TYPE_FLOAT, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_GAINS)); assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE)); assertEquals(TYPE_INT32, CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION)); Loading Loading @@ -187,30 +188,30 @@ public class CameraMetadataTest extends junit.framework.TestCase { assertEquals(false, mMetadata.isEmpty()); // // android.colorCorrection.transform (3x3 matrix) // android.colorCorrection.colorCorrectionGains (float x 4 array) // final float[] transformMatrix = new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; byte[] transformMatrixAsByteArray = new byte[transformMatrix.length * 4]; ByteBuffer transformMatrixByteBuffer = ByteBuffer.wrap(transformMatrixAsByteArray).order(ByteOrder.nativeOrder()); for (float f : transformMatrix) transformMatrixByteBuffer.putFloat(f); final float[] colorCorrectionGains = new float[] { 1.0f, 2.0f, 3.0f, 4.0f}; byte[] colorCorrectionGainsAsByteArray = new byte[colorCorrectionGains.length * 4]; ByteBuffer colorCorrectionGainsByteBuffer = ByteBuffer.wrap(colorCorrectionGainsAsByteArray).order(ByteOrder.nativeOrder()); for (float f : colorCorrectionGains) colorCorrectionGainsByteBuffer.putFloat(f); // Read assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM)); mMetadata.writeValues(ANDROID_COLOR_CORRECTION_TRANSFORM, transformMatrixAsByteArray); assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); mMetadata.writeValues(ANDROID_COLOR_CORRECTION_GAINS, colorCorrectionGainsAsByteArray); // Write assertArrayEquals(transformMatrixAsByteArray, mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM)); assertArrayEquals(colorCorrectionGainsAsByteArray, mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); assertEquals(2, mMetadata.getEntryCount()); assertEquals(false, mMetadata.isEmpty()); // Erase mMetadata.writeValues(ANDROID_COLOR_CORRECTION_TRANSFORM, null); assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM)); mMetadata.writeValues(ANDROID_COLOR_CORRECTION_GAINS, null); assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); assertEquals(1, mMetadata.getEntryCount()); } Loading Loading @@ -279,7 +280,7 @@ public class CameraMetadataTest extends junit.framework.TestCase { @SmallTest public void testReadWritePrimitiveArray() { // int32 (n) checkKeyGetAndSetArray("android.sensor.info.availableSensitivities", int[].class, checkKeyGetAndSetArray("android.sensor.info.sensitivityRange", int[].class, new int[] { 0xC0FFEE, 0xDEADF00D }); Loading Loading @@ -379,7 +380,9 @@ public class CameraMetadataTest extends junit.framework.TestCase { new AvailableFormat[] { AvailableFormat.RAW_SENSOR, AvailableFormat.YV12, AvailableFormat.IMPLEMENTATION_DEFINED AvailableFormat.IMPLEMENTATION_DEFINED, AvailableFormat.YCbCr_420_888, AvailableFormat.BLOB }); } Loading Loading @@ -431,12 +434,13 @@ public class CameraMetadataTest extends junit.framework.TestCase { AvailableFormat.RAW_SENSOR, AvailableFormat.YV12, AvailableFormat.IMPLEMENTATION_DEFINED, AvailableFormat.YCbCr_420_888 AvailableFormat.YCbCr_420_888, AvailableFormat.BLOB }); Key<AeAntibandingMode> availableFormatsKey = new Key<AeAntibandingMode>("android.scaler.availableFormats", AeAntibandingMode.class); Key<AvailableFormat[]> availableFormatsKey = new Key<AvailableFormat[]>("android.scaler.availableFormats", AvailableFormat[].class); byte[] availableFormatValues = mMetadata.readValues(CameraMetadataNative .getTag(availableFormatsKey.getName())); Loading @@ -444,7 +448,8 @@ public class CameraMetadataTest extends junit.framework.TestCase { 0x20, 0x32315659, 0x22, 0x23 0x23, 0x21 }; ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder()); Loading Loading @@ -523,4 +528,115 @@ public class CameraMetadataTest extends junit.framework.TestCase { <T> void compareGeneric(T expected, T actual) { assertEquals(expected, actual); } @SmallTest public void testReadWriteOverride() { // // android.scaler.availableFormats (int x n array) // int[] availableFormats = new int[] { 0x20, // RAW_SENSOR 0x32315659, // YV12 0x11, // YCrCb_420_SP 0x100, // ImageFormat.JPEG 0x22, // IMPLEMENTATION_DEFINED 0x23, // YCbCr_420_888 }; int[] expectedIntValues = new int[] { 0x20, // RAW_SENSOR 0x32315659, // YV12 0x11, // YCrCb_420_SP 0x21, // BLOB 0x22, // IMPLEMENTATION_DEFINED 0x23, // YCbCr_420_888 }; int availableFormatTag = CameraMetadataNative.getTag("android.scaler.availableFormats"); // Write mMetadata.set(CameraCharacteristics.SCALER_AVAILABLE_FORMATS, availableFormats); byte[] availableFormatValues = mMetadata.readValues(availableFormatTag); ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder()); assertEquals(expectedIntValues.length * 4, availableFormatValues.length); for (int i = 0; i < expectedIntValues.length; ++i) { assertEquals(expectedIntValues[i], bf.getInt()); } // Read byte[] availableFormatsAsByteArray = new byte[expectedIntValues.length * 4]; ByteBuffer availableFormatsByteBuffer = ByteBuffer.wrap(availableFormatsAsByteArray).order(ByteOrder.nativeOrder()); for (int value : expectedIntValues) { availableFormatsByteBuffer.putInt(value); } mMetadata.writeValues(availableFormatTag, availableFormatsAsByteArray); int[] resultFormats = mMetadata.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS); assertNotNull("result available formats shouldn't be null", resultFormats); assertArrayEquals(availableFormats, resultFormats); // // android.statistics.faces (Face x n array) // int[] expectedFaceIds = new int[] {1, 2, 3, 4, 5}; byte[] expectedFaceScores = new byte[] {10, 20, 30, 40, 50}; int numFaces = expectedFaceIds.length; Rect[] expectedRects = new Rect[numFaces]; for (int i = 0; i < numFaces; i++) { expectedRects[i] = new Rect(i*4 + 1, i * 4 + 2, i * 4 + 3, i * 4 + 4); } int[] expectedFaceLM = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, }; Point[] expectedFaceLMPoints = new Point[numFaces * 3]; for (int i = 0; i < numFaces; i++) { expectedFaceLMPoints[i*3] = new Point(expectedFaceLM[i*6], expectedFaceLM[i*6+1]); expectedFaceLMPoints[i*3+1] = new Point(expectedFaceLM[i*6+2], expectedFaceLM[i*6+3]); expectedFaceLMPoints[i*3+2] = new Point(expectedFaceLM[i*6+4], expectedFaceLM[i*6+5]); } /** * Read - FACE_DETECT_MODE == FULL */ mMetadata.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL); mMetadata.set(CaptureResult.STATISTICS_FACE_IDS, expectedFaceIds); mMetadata.set(CaptureResult.STATISTICS_FACE_SCORES, expectedFaceScores); mMetadata.set(CaptureResult.STATISTICS_FACE_RECTANGLES, expectedRects); mMetadata.set(CaptureResult.STATISTICS_FACE_LANDMARKS, expectedFaceLM); Face[] resultFaces = mMetadata.get(CaptureResult.STATISTICS_FACES); assertEquals(numFaces, resultFaces.length); for (int i = 0; i < numFaces; i++) { assertEquals(expectedFaceIds[i], resultFaces[i].getId()); assertEquals(expectedFaceScores[i], resultFaces[i].getScore()); assertEquals(expectedRects[i], resultFaces[i].getBounds()); assertEquals(expectedFaceLMPoints[i*3], resultFaces[i].getLeftEyePosition()); assertEquals(expectedFaceLMPoints[i*3+1], resultFaces[i].getRightEyePosition()); assertEquals(expectedFaceLMPoints[i*3+2], resultFaces[i].getMouthPosition()); } /** * Read - FACE_DETECT_MODE == SIMPLE */ mMetadata.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE); mMetadata.set(CaptureResult.STATISTICS_FACE_SCORES, expectedFaceScores); mMetadata.set(CaptureResult.STATISTICS_FACE_RECTANGLES, expectedRects); Face[] resultSimpleFaces = mMetadata.get(CaptureResult.STATISTICS_FACES); assertEquals(numFaces, resultSimpleFaces.length); for (int i = 0; i < numFaces; i++) { assertEquals(Face.ID_UNSUPPORTED, resultSimpleFaces[i].getId()); assertEquals(expectedFaceScores[i], resultSimpleFaces[i].getScore()); assertEquals(expectedRects[i], resultSimpleFaces[i].getBounds()); assertNull(resultSimpleFaces[i].getLeftEyePosition()); assertNull(resultSimpleFaces[i].getRightEyePosition()); assertNull(resultSimpleFaces[i].getMouthPosition()); } } }