/* * 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 android.hardware.camera2.impl; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.Rational; import android.os.Parcelable; import android.os.Parcel; import android.util.Log; import java.lang.reflect.Array; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; /** * Implementation of camera metadata marshal/unmarshal across Binder to * the camera service */ public class CameraMetadataNative extends CameraMetadata implements Parcelable { private static final String TAG = "CameraMetadataJV"; private final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); public CameraMetadataNative() { super(); mMetadataPtr = nativeAllocate(); if (mMetadataPtr == 0) { throw new OutOfMemoryError("Failed to allocate native CameraMetadata"); } } /** * Copy constructor - clone metadata */ public CameraMetadataNative(CameraMetadataNative other) { super(); mMetadataPtr = nativeAllocateCopy(other); if (mMetadataPtr == 0) { throw new OutOfMemoryError("Failed to allocate native CameraMetadata"); } } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public CameraMetadataNative createFromParcel(Parcel in) { CameraMetadataNative metadata = new CameraMetadataNative(); metadata.readFromParcel(in); return metadata; } @Override public CameraMetadataNative[] newArray(int size) { return new CameraMetadataNative[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { nativeWriteToParcel(dest); } @SuppressWarnings("unchecked") @Override public T get(Key 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); } public void readFromParcel(Parcel in) { nativeReadFromParcel(in); } /** * Set a camera metadata field to a value. The field definitions can be * found in {@link CameraCharacteristics}, {@link CaptureResult}, and * {@link CaptureRequest}. * * @param key The metadata field to write. * @param value The value to set the field to, which must be of a matching * type to the key. */ public void set(Key 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); } // Keep up-to-date with camera_metadata.h /** * @hide */ public static final int TYPE_BYTE = 0; /** * @hide */ public static final int TYPE_INT32 = 1; /** * @hide */ public static final int TYPE_FLOAT = 2; /** * @hide */ public static final int TYPE_INT64 = 3; /** * @hide */ public static final int TYPE_DOUBLE = 4; /** * @hide */ public static final int TYPE_RATIONAL = 5; /** * @hide */ public static final int NUM_TYPES = 6; private void close() { // this sets mMetadataPtr to 0 nativeClose(); mMetadataPtr = 0; // set it to 0 again to prevent eclipse from making this field final } private static int getTypeSize(int nativeType) { switch(nativeType) { case TYPE_BYTE: return 1; case TYPE_INT32: case TYPE_FLOAT: return 4; case TYPE_INT64: case TYPE_DOUBLE: case TYPE_RATIONAL: return 8; } throw new UnsupportedOperationException("Unknown type, can't get size " + nativeType); } private static Class getExpectedType(int nativeType) { switch(nativeType) { case TYPE_BYTE: return Byte.TYPE; case TYPE_INT32: return Integer.TYPE; case TYPE_FLOAT: return Float.TYPE; case TYPE_INT64: return Long.TYPE; case TYPE_DOUBLE: return Double.TYPE; case TYPE_RATIONAL: return Rational.class; } throw new UnsupportedOperationException("Unknown type, can't map to Java type " + nativeType); } @SuppressWarnings("unchecked") private static int packSingleNative(T value, ByteBuffer buffer, Class type, int nativeType, boolean sizeOnly) { if (!sizeOnly) { /** * Rewrite types when the native type doesn't match the managed type * - Boolean -> Byte * - Integer -> Byte */ if (nativeType == TYPE_BYTE && type == Boolean.TYPE) { // Since a boolean can't be cast to byte, and we don't want to use putBoolean boolean asBool = (Boolean) value; byte asByte = (byte) (asBool ? 1 : 0); value = (T) (Byte) asByte; } else if (nativeType == TYPE_BYTE && type == Integer.TYPE) { int asInt = (Integer) value; byte asByte = (byte) asInt; value = (T) (Byte) asByte; } else if (type != getExpectedType(nativeType)) { throw new UnsupportedOperationException("Tried to pack a type of " + type + " but we expected the type to be " + getExpectedType(nativeType)); } if (nativeType == TYPE_BYTE) { buffer.put((Byte) value); } else if (nativeType == TYPE_INT32) { buffer.putInt((Integer) value); } else if (nativeType == TYPE_FLOAT) { buffer.putFloat((Float) value); } else if (nativeType == TYPE_INT64) { buffer.putLong((Long) value); } else if (nativeType == TYPE_DOUBLE) { buffer.putDouble((Double) value); } else if (nativeType == TYPE_RATIONAL) { Rational r = (Rational) value; buffer.putInt(r.getNumerator()); buffer.putInt(r.getDenominator()); } } return getTypeSize(nativeType); } @SuppressWarnings({"unchecked", "rawtypes"}) private static int packSingle(T value, ByteBuffer buffer, Class type, int nativeType, boolean sizeOnly) { int size = 0; if (type.isPrimitive() || type == Rational.class) { size = packSingleNative(value, buffer, type, nativeType, sizeOnly); } else if (type.isEnum()) { size = packEnum((Enum)value, buffer, (Class)type, nativeType, sizeOnly); } else if (type.isArray()) { size = packArray(value, buffer, type, nativeType, sizeOnly); } else { size = packClass(value, buffer, type, nativeType, sizeOnly); } return size; } private static > int packEnum(T value, ByteBuffer buffer, Class type, int nativeType, boolean sizeOnly) { // TODO: add support for enums with their own values. return packSingleNative(getEnumValue(value), buffer, Integer.TYPE, nativeType, sizeOnly); } @SuppressWarnings("unchecked") private static int packClass(T value, ByteBuffer buffer, Class type, int nativeType, boolean sizeOnly) { MetadataMarshalClass marshaler = getMarshaler(type, nativeType); if (marshaler == null) { throw new IllegalArgumentException(String.format("Unknown Key type: %s", type)); } return marshaler.marshal(value, buffer, nativeType, sizeOnly); } private static int packArray(T value, ByteBuffer buffer, Class type, int nativeType, boolean sizeOnly) { int size = 0; int arrayLength = Array.getLength(value); @SuppressWarnings("unchecked") Class componentType = (Class)type.getComponentType(); for (int i = 0; i < arrayLength; ++i) { size += packSingle(Array.get(value, i), buffer, componentType, nativeType, sizeOnly); } return size; } @SuppressWarnings("unchecked") private static T unpackSingleNative(ByteBuffer buffer, Class type, int nativeType) { T val; if (nativeType == TYPE_BYTE) { val = (T) (Byte) buffer.get(); } else if (nativeType == TYPE_INT32) { val = (T) (Integer) buffer.getInt(); } else if (nativeType == TYPE_FLOAT) { val = (T) (Float) buffer.getFloat(); } else if (nativeType == TYPE_INT64) { val = (T) (Long) buffer.getLong(); } else if (nativeType == TYPE_DOUBLE) { val = (T) (Double) buffer.getDouble(); } else if (nativeType == TYPE_RATIONAL) { val = (T) new Rational(buffer.getInt(), buffer.getInt()); } else { throw new UnsupportedOperationException("Unknown type, can't unpack a native type " + nativeType); } /** * Rewrite types when the native type doesn't match the managed type * - Byte -> Boolean * - Byte -> Integer */ if (nativeType == TYPE_BYTE && type == Boolean.TYPE) { // Since a boolean can't be cast to byte, and we don't want to use getBoolean byte asByte = (Byte) val; boolean asBool = asByte != 0; val = (T) (Boolean) asBool; } else if (nativeType == TYPE_BYTE && type == Integer.TYPE) { byte asByte = (Byte) val; int asInt = asByte; val = (T) (Integer) asInt; } else if (type != getExpectedType(nativeType)) { throw new UnsupportedOperationException("Tried to unpack a type of " + type + " but we expected the type to be " + getExpectedType(nativeType)); } return val; } @SuppressWarnings({"unchecked", "rawtypes"}) private static T unpackSingle(ByteBuffer buffer, Class type, int nativeType) { if (type.isPrimitive() || type == Rational.class) { return unpackSingleNative(buffer, type, nativeType); } if (type.isEnum()) { return (T) unpackEnum(buffer, (Class)type, nativeType); } if (type.isArray()) { return unpackArray(buffer, type, nativeType); } T instance = unpackClass(buffer, type, nativeType); return instance; } private static > T unpackEnum(ByteBuffer buffer, Class type, int nativeType) { int ordinal = unpackSingleNative(buffer, Integer.TYPE, nativeType); return getEnumFromValue(type, ordinal); } private static T unpackClass(ByteBuffer buffer, Class type, int nativeType) { MetadataMarshalClass marshaler = getMarshaler(type, nativeType); if (marshaler == null) { throw new IllegalArgumentException("Unknown class type: " + type); } return marshaler.unmarshal(buffer, nativeType); } @SuppressWarnings("unchecked") private static T unpackArray(ByteBuffer buffer, Class type, int nativeType) { Class componentType = type.getComponentType(); Object array; int elementSize = getTypeSize(nativeType); MetadataMarshalClass marshaler = getMarshaler(componentType, nativeType); if (marshaler != null) { elementSize = marshaler.getNativeSize(nativeType); } if (elementSize != MetadataMarshalClass.NATIVE_SIZE_DYNAMIC) { int remaining = buffer.remaining(); int arraySize = remaining / elementSize; if (VERBOSE) { Log.v(TAG, String.format( "Attempting to unpack array (count = %d, element size = %d, bytes " + "remaining = %d) for type %s", arraySize, elementSize, remaining, type)); } array = Array.newInstance(componentType, arraySize); for (int i = 0; i < arraySize; ++i) { Object elem = unpackSingle(buffer, componentType, nativeType); Array.set(array, i, elem); } } else { // Dynamic size, use an array list. ArrayList arrayList = new ArrayList(); int primitiveSize = getTypeSize(nativeType); while (buffer.remaining() >= primitiveSize) { Object elem = unpackSingle(buffer, componentType, nativeType); arrayList.add(elem); } array = arrayList.toArray((T[]) Array.newInstance(componentType, 0)); } if (buffer.remaining() != 0) { Log.e(TAG, "Trailing bytes (" + buffer.remaining() + ") left over after unpacking " + type); } return (T) array; } private long mMetadataPtr; // native CameraMetadata* private native long nativeAllocate(); private native long nativeAllocateCopy(CameraMetadataNative other) throws NullPointerException; private native synchronized void nativeWriteToParcel(Parcel dest); private native synchronized void nativeReadFromParcel(Parcel source); private native synchronized void nativeSwap(CameraMetadataNative other) throws NullPointerException; private native synchronized void nativeClose(); private native synchronized boolean nativeIsEmpty(); private native synchronized int nativeGetEntryCount(); private native synchronized byte[] nativeReadValues(int tag); private native synchronized void nativeWriteValues(int tag, byte[] src); private static native int nativeGetTagFromKey(String keyName) throws IllegalArgumentException; private static native int nativeGetTypeFromTag(int tag) throws IllegalArgumentException; private static native void nativeClassInit(); /** *

Perform a 0-copy swap of the internal metadata with another object.

* *

Useful to convert a CameraMetadata into e.g. a CaptureRequest.

* * @param other Metadata to swap with * @throws NullPointerException if other was null * @hide */ public void swap(CameraMetadataNative other) { nativeSwap(other); } /** * @hide */ public int getEntryCount() { return nativeGetEntryCount(); } /** * Does this metadata contain at least 1 entry? * * @hide */ public boolean isEmpty() { return nativeIsEmpty(); } /** * Convert a key string into the equivalent native tag. * * @throws IllegalArgumentException if the key was not recognized * @throws NullPointerException if the key was null * * @hide */ public static int getTag(String key) { return nativeGetTagFromKey(key); } /** * Get the underlying native type for a tag. * * @param tag An integer tag, see e.g. {@link #getTag} * @return An int enum for the metadata type, see e.g. {@link #TYPE_BYTE} * * @hide */ public static int getNativeType(int tag) { return nativeGetTypeFromTag(tag); } /** *

Updates the existing entry for tag with the new bytes pointed by src, erasing * the entry if src was null.

* *

An empty array can be passed in to update the entry to 0 elements.

* * @param tag An integer tag, see e.g. {@link #getTag} * @param src An array of bytes, or null to erase the entry * * @hide */ public void writeValues(int tag, byte[] src) { nativeWriteValues(tag, src); } /** *

Returns a byte[] of data corresponding to this tag. Use a wrapped bytebuffer to unserialize * the data properly.

* *

An empty array can be returned to denote an existing entry with 0 elements.

* * @param tag An integer tag, see e.g. {@link #getTag} * * @return {@code null} if there were 0 entries for this tag, a byte[] otherwise. * @hide */ public byte[] readValues(int tag) { // TODO: Optimization. Native code returns a ByteBuffer instead. return nativeReadValues(tag); } @Override protected void finalize() throws Throwable { try { close(); } finally { super.finalize(); } } private static final HashMap, int[]> sEnumValues = new HashMap, int[]>(); /** * Register a non-sequential set of values to be used with the pack/unpack functions. * This enables get/set to correctly marshal the enum into a value that is C-compatible. * * @param enumType The class for an enum * @param values A list of values mapping to the ordinals of the enum * * @hide */ public static > void registerEnumValues(Class enumType, int[] values) { if (enumType.getEnumConstants().length != values.length) { throw new IllegalArgumentException( "Expected values array to be the same size as the enumTypes values " + values.length + " for type " + enumType); } if (VERBOSE) { Log.v(TAG, "Registered enum values for type " + enumType + " values"); } sEnumValues.put(enumType, values); } /** * Get the numeric value from an enum. This is usually the same as the ordinal value for * enums that have fully sequential values, although for C-style enums the range of values * may not map 1:1. * * @param enumValue Enum instance * @return Int guaranteed to be ABI-compatible with the C enum equivalent */ private static > int getEnumValue(T enumValue) { int[] values; values = sEnumValues.get(enumValue.getClass()); int ordinal = enumValue.ordinal(); if (values != null) { return values[ordinal]; } return ordinal; } /** * Finds the enum corresponding to it's numeric value. Opposite of {@link #getEnumValue} method. * * @param enumType Class of the enum we want to find * @param value The numeric value of the enum * @return An instance of the enum */ private static > T getEnumFromValue(Class enumType, int value) { int ordinal; int[] registeredValues = sEnumValues.get(enumType); if (registeredValues != null) { ordinal = -1; for (int i = 0; i < registeredValues.length; ++i) { if (registeredValues[i] == value) { ordinal = i; break; } } } else { ordinal = value; } T[] values = enumType.getEnumConstants(); if (ordinal < 0 || ordinal >= values.length) { throw new IllegalArgumentException( String.format( "Argument 'value' (%d) was not a valid enum value for type %s " + "(registered? %b)", value, enumType, (registeredValues != null))); } return values[ordinal]; } static HashMap, MetadataMarshalClass> sMarshalerMap = new HashMap, MetadataMarshalClass>(); private static void registerMarshaler(MetadataMarshalClass marshaler) { sMarshalerMap.put(marshaler.getMarshalingClass(), marshaler); } @SuppressWarnings("unchecked") private static MetadataMarshalClass getMarshaler(Class type, int nativeType) { MetadataMarshalClass marshaler = (MetadataMarshalClass) sMarshalerMap.get(type); if (marshaler != null && !marshaler.isNativeTypeSupported(nativeType)) { throw new UnsupportedOperationException("Unsupported type " + nativeType + " to be marshalled to/from a " + type); } return marshaler; } /** * We use a class initializer to allow the native code to cache some field offsets */ static { nativeClassInit(); if (VERBOSE) { Log.v(TAG, "Shall register metadata marshalers"); } // load built-in marshallers registerMarshaler(new MetadataMarshalRect()); registerMarshaler(new MetadataMarshalSize()); registerMarshaler(new MetadataMarshalString()); if (VERBOSE) { Log.v(TAG, "Registered metadata marshalers"); } } }