diff options
7 files changed, 454 insertions, 9 deletions
diff --git a/core/java/android/hardware/photography/CameraMetadata.java b/core/java/android/hardware/photography/CameraMetadata.java index 4633b2f..c024c05 100644 --- a/core/java/android/hardware/photography/CameraMetadata.java +++ b/core/java/android/hardware/photography/CameraMetadata.java @@ -16,6 +16,10 @@ package android.hardware.photography; +import android.hardware.photography.impl.MetadataMarshalClass; +import android.hardware.photography.impl.MetadataMarshalRect; +import android.hardware.photography.impl.MetadataMarshalSize; +import android.hardware.photography.impl.MetadataMarshalString; import android.os.Parcelable; import android.os.Parcel; import android.util.Log; @@ -85,6 +89,11 @@ public class CameraMetadata implements Parcelable, AutoCloseable { public <T> void set(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.mType, nativeType, /* sizeOnly */true); @@ -265,6 +274,11 @@ public class CameraMetadata implements Parcelable, AutoCloseable { private static <T> int packClass(T value, ByteBuffer buffer, Class<T> type, int nativeType, boolean sizeOnly) { + MetadataMarshalClass<T> marshaler = getMarshaler(type, nativeType); + if (marshaler != null) { + return marshaler.marshal(value, buffer, nativeType, sizeOnly); + } + /** * FIXME: This doesn't actually work because getFields() returns fields in an unordered * manner. Although we could sort and get the data to come out correctly on the *java* side, @@ -558,6 +572,11 @@ public class CameraMetadata implements Parcelable, AutoCloseable { private static <T> T unpackClass(ByteBuffer buffer, Class<T> type, int nativeType) { + MetadataMarshalClass<T> marshaler = getMarshaler(type, nativeType); + if (marshaler != null) { + return marshaler.unmarshal(buffer, nativeType); + } + /** * FIXME: This doesn't actually work because getFields() returns fields in an unordered * manner. Although we could sort and get the data to come out correctly on the *java* side, @@ -611,14 +630,44 @@ public class CameraMetadata implements Parcelable, AutoCloseable { Class<?> componentType = type.getComponentType(); Object array; - int remaining = buffer.remaining(); - // FIXME: Assumes that the rest of the ByteBuffer is part of the array. - int arraySize = remaining / getTypeSize(nativeType); + 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; + + 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<Object> arrayList = new ArrayList<Object>(); + + int primitiveSize = getTypeSize(nativeType); + while (buffer.remaining() >= primitiveSize) { + Object elem = unpackSingle(buffer, componentType, nativeType); + arrayList.add(elem); + } - array = Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; ++i) { - Object elem = unpackSingle(buffer, componentType, nativeType); - Array.set(array, i, 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; @@ -927,11 +976,39 @@ public class CameraMetadata implements Parcelable, AutoCloseable { return values[ordinal]; } + static HashMap<Class<?>, MetadataMarshalClass<?>> sMarshalerMap = new + HashMap<Class<?>, MetadataMarshalClass<?>>(); + + private static <T> void registerMarshaler(MetadataMarshalClass<T> marshaler) { + sMarshalerMap.put(marshaler.getMarshalingClass(), marshaler); + } + + @SuppressWarnings("unchecked") + private static <T> MetadataMarshalClass<T> getMarshaler(Class<T> type, int nativeType) { + MetadataMarshalClass<T> marshaler = (MetadataMarshalClass<T>) 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 { System.loadLibrary("media_jni"); nativeClassInit(); + + Log.v(TAG, "Shall register metadata marshalers"); + + // load built-in marshallers + registerMarshaler(new MetadataMarshalRect()); + registerMarshaler(new MetadataMarshalSize()); + registerMarshaler(new MetadataMarshalString()); + + Log.v(TAG, "Registered metadata marshalers"); } } diff --git a/core/java/android/hardware/photography/impl/MetadataMarshalClass.java b/core/java/android/hardware/photography/impl/MetadataMarshalClass.java new file mode 100644 index 0000000..a70784d --- /dev/null +++ b/core/java/android/hardware/photography/impl/MetadataMarshalClass.java @@ -0,0 +1,67 @@ +/* + * 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.photography.impl; + +import java.nio.ByteBuffer; + +public interface MetadataMarshalClass<T> { + + /** + * Marshal the specified object instance (value) into a byte buffer. + * + * @param value the value of type T that we wish to write into the byte buffer + * @param buffer the byte buffer into which the marshalled object will be written + * @param nativeType the native type, e.g. + * {@link android.hardware.photography.CameraMetadata#TYPE_BYTE TYPE_BYTE}. + * Guaranteed to be one for which isNativeTypeSupported returns true. + * @param sizeOnly if this is true, don't write to the byte buffer. calculate the size only. + * @return the size that needs to be written to the byte buffer + */ + int marshal(T value, ByteBuffer buffer, int nativeType, boolean sizeOnly); + + /** + * Unmarshal a new object instance from the byte buffer. + * @param buffer the byte buffer, from which we will read the object + * @param nativeType the native type, e.g. + * {@link android.hardware.photography.CameraMetadata#TYPE_BYTE TYPE_BYTE}. + * Guaranteed to be one for which isNativeTypeSupported returns true. + * @return a new instance of type T read from the byte buffer + */ + T unmarshal(ByteBuffer buffer, int nativeType); + + Class<T> getMarshalingClass(); + + /** + * Determines whether or not this marshaller supports this native type. Most marshallers + * will are likely to only support one type. + * + * @param nativeType the native type, e.g. + * {@link android.hardware.photography.CameraMetadata#TYPE_BYTE TYPE_BYTE} + * @return true if it supports, false otherwise + */ + boolean isNativeTypeSupported(int nativeType); + + public static int NATIVE_SIZE_DYNAMIC = -1; + + /** + * How many bytes T will take up if marshalled to/from nativeType + * @param nativeType the native type, e.g. + * {@link android.hardware.photography.CameraMetadata#TYPE_BYTE TYPE_BYTE} + * @return a size in bytes, or NATIVE_SIZE_DYNAMIC if the size is dynamic + */ + int getNativeSize(int nativeType); +} diff --git a/core/java/android/hardware/photography/impl/MetadataMarshalRect.java b/core/java/android/hardware/photography/impl/MetadataMarshalRect.java new file mode 100644 index 0000000..d6636ac --- /dev/null +++ b/core/java/android/hardware/photography/impl/MetadataMarshalRect.java @@ -0,0 +1,68 @@ +/* + * 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.photography.impl; + +import android.graphics.Rect; +import android.hardware.photography.CameraMetadata; + +import java.nio.ByteBuffer; + +public class MetadataMarshalRect implements MetadataMarshalClass<Rect> { + private static final int SIZE = 16; + + @Override + public int marshal(Rect value, ByteBuffer buffer, int nativeType, boolean sizeOnly) { + if (sizeOnly) { + return SIZE; + } + + buffer.putInt(value.left); + buffer.putInt(value.top); + buffer.putInt(value.width()); + buffer.putInt(value.height()); + + return SIZE; + } + + @Override + public Rect unmarshal(ByteBuffer buffer, int nativeType) { + + int left = buffer.getInt(); + int top = buffer.getInt(); + int width = buffer.getInt(); + int height = buffer.getInt(); + + int right = left + width; + int bottom = top + height; + + return new Rect(left, top, right, bottom); + } + + @Override + public Class<Rect> getMarshalingClass() { + return Rect.class; + } + + @Override + public boolean isNativeTypeSupported(int nativeType) { + return nativeType == CameraMetadata.TYPE_INT32; + } + + @Override + public int getNativeSize(int nativeType) { + return SIZE; + } +} diff --git a/core/java/android/hardware/photography/impl/MetadataMarshalSize.java b/core/java/android/hardware/photography/impl/MetadataMarshalSize.java new file mode 100644 index 0000000..430219c --- /dev/null +++ b/core/java/android/hardware/photography/impl/MetadataMarshalSize.java @@ -0,0 +1,61 @@ +/* + * 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.photography.impl; + +import android.hardware.photography.CameraMetadata; +import android.hardware.photography.Size; + +import java.nio.ByteBuffer; + +public class MetadataMarshalSize implements MetadataMarshalClass<Size> { + + private static final int SIZE = 8; + + @Override + public int marshal(Size value, ByteBuffer buffer, int nativeType, boolean sizeOnly) { + if (sizeOnly) { + return SIZE; + } + + buffer.putInt(value.getWidth()); + buffer.putInt(value.getHeight()); + + return SIZE; + } + + @Override + public Size unmarshal(ByteBuffer buffer, int nativeType) { + int width = buffer.getInt(); + int height = buffer.getInt(); + + return new Size(width, height); + } + + @Override + public Class<Size> getMarshalingClass() { + return Size.class; + } + + @Override + public boolean isNativeTypeSupported(int nativeType) { + return nativeType == CameraMetadata.TYPE_INT32; + } + + @Override + public int getNativeSize(int nativeType) { + return SIZE; + } +} diff --git a/core/java/android/hardware/photography/impl/MetadataMarshalString.java b/core/java/android/hardware/photography/impl/MetadataMarshalString.java new file mode 100644 index 0000000..81123ee --- /dev/null +++ b/core/java/android/hardware/photography/impl/MetadataMarshalString.java @@ -0,0 +1,81 @@ +/* + * 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.photography.impl; + +import android.hardware.photography.CameraMetadata; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +public class MetadataMarshalString implements MetadataMarshalClass<String> { + + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + + @Override + public int marshal(String value, ByteBuffer buffer, int nativeType, boolean sizeOnly) { + byte[] arr = value.getBytes(UTF8_CHARSET); + + if (!sizeOnly) { + buffer.put(arr); + buffer.put((byte)0); // metadata strings are NULL-terminated + } + + return arr.length + 1; + } + + @Override + public String unmarshal(ByteBuffer buffer, int nativeType) { + + buffer.mark(); // save the current position + + boolean foundNull = false; + int stringLength = 0; + while (buffer.hasRemaining()) { + if (buffer.get() == (byte)0) { + foundNull = true; + break; + } + + stringLength++; + } + if (!foundNull) { + throw new IllegalArgumentException("Strings must be null-terminated"); + } + + buffer.reset(); // go back to the previously marked position + + byte[] strBytes = new byte[stringLength + 1]; + buffer.get(strBytes, /*dstOffset*/0, stringLength + 1); // including null character + + // not including null character + return new String(strBytes, /*offset*/0, stringLength, UTF8_CHARSET); + } + + @Override + public Class<String> getMarshalingClass() { + return String.class; + } + + @Override + public boolean isNativeTypeSupported(int nativeType) { + return nativeType == CameraMetadata.TYPE_BYTE; + } + + @Override + public int getNativeSize(int nativeType) { + return NATIVE_SIZE_DYNAMIC; + } +} diff --git a/core/jni/android_hardware_photography_CameraMetadata.cpp b/core/jni/android_hardware_photography_CameraMetadata.cpp index 5070d2c..5190a37 100644 --- a/core/jni/android_hardware_photography_CameraMetadata.cpp +++ b/core/jni/android_hardware_photography_CameraMetadata.cpp @@ -267,7 +267,13 @@ static void CameraMetadata_writeValues(JNIEnv *env, jobject thiz, jint tag, jbyt if (src == NULL) { // If array is NULL, delete the entry - res = metadata->erase(tag); + if (metadata->exists(tag)) { + res = metadata->erase(tag); + ALOGV("%s: Erase values (res = %d)", __FUNCTION__, res); + } else { + res = OK; + ALOGV("%s: Don't need to erase", __FUNCTION__); + } } else { // Copy from java array into native array ScopedByteArrayRO arrayReader(env, src); @@ -275,6 +281,8 @@ static void CameraMetadata_writeValues(JNIEnv *env, jobject thiz, jint tag, jbyt res = Helpers::updateAny(metadata, static_cast<uint32_t>(tag), tagType, arrayReader.get(), arrayReader.size()); + + ALOGV("%s: Update values (res = %d)", __FUNCTION__, res); } if (res == OK) { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java index 0c2f3a3..131441b 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java @@ -19,8 +19,10 @@ package com.android.mediaframeworktest.unit; import android.os.Parcel; import android.test.suitebuilder.annotation.SmallTest; import android.graphics.ImageFormat; +import android.graphics.Rect; import android.hardware.photography.CameraMetadata; import android.hardware.photography.Rational; +import android.hardware.photography.Size; import static android.hardware.photography.CameraMetadata.*; @@ -223,6 +225,10 @@ public class CameraMetadataTest extends junit.framework.TestCase { assertEquals(null, mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE)); + // Write/read null values + mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, null); + assertEquals(null, mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE)); + // Write 0 values mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, new byte[] {}); @@ -286,13 +292,21 @@ public class CameraMetadataTest extends junit.framework.TestCase { } private <T> void checkKeyGetAndSet(String keyStr, Class<T> type, T value) { + assertFalse("Use checkKeyGetAndSetArray to compare array Keys", type.isArray()); + Key<T> key = new Key<T>(keyStr, type); assertNull(mMetadata.get(key)); + mMetadata.set(key, null); + assertNull(mMetadata.get(key)); mMetadata.set(key, value); - assertEquals(value, mMetadata.get(key)); + + T actual = mMetadata.get(key); + assertEquals(value, actual); } private <T> void checkKeyGetAndSetArray(String keyStr, Class<T> type, T value) { + assertTrue(type.isArray()); + Key<T> key = new Key<T>(keyStr, type); assertNull(mMetadata.get(key)); mMetadata.set(key, value); @@ -508,4 +522,73 @@ public class CameraMetadataTest extends junit.framework.TestCase { assertEquals(expectedIntValues[i], bf.getInt()); } } + + @SmallTest + public void testReadWriteSize() { + // int32 x n + checkKeyGetAndSet("android.jpeg.thumbnailSize", Size.class, new Size(123, 456)); + + // int32 x 2 x n + checkKeyGetAndSetArray("android.scaler.availableJpegSizes", Size[].class, new Size[] { + new Size(123, 456), + new Size(0xDEAD, 0xF00D), + new Size(0xF00, 0xB00) + }); + } + + @SmallTest + public void testReadWriteRectangle() { + // int32 x n + checkKeyGetAndSet("android.scaler.cropRegion", Rect.class, new Rect(10, 11, 1280, 1024)); + + // int32 x 2 x n + checkKeyGetAndSetArray("android.statistics.faceRectangles", Rect[].class, new Rect[] { + new Rect(110, 111, 11280, 11024), + new Rect(210, 111, 21280, 21024), + new Rect(310, 111, 31280, 31024) + }); + } + + @SmallTest + public void testReadWriteString() { + // (byte) string + Key<String> gpsProcessingMethodKey = + new Key<String>("android.jpeg.gpsProcessingMethod", String.class); + + String helloWorld = new String("HelloWorld"); + byte[] helloWorldBytes = new byte[] { + 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\0' }; + + mMetadata.set(gpsProcessingMethodKey, helloWorld); + + String actual = mMetadata.get(gpsProcessingMethodKey); + assertEquals(helloWorld, actual); + + byte[] actualBytes = mMetadata.readValues(getTag(gpsProcessingMethodKey.getName())); + assertArrayEquals(helloWorldBytes, actualBytes); + + // Does not yet test as a string[] since we don't support that in native code. + + // (byte) string + Key<String[]> gpsProcessingMethodKeyArray = + new Key<String[]>("android.jpeg.gpsProcessingMethod", String[].class); + + String[] gpsStrings = new String[] { "HelloWorld", "FooBar", "Shazbot" }; + byte[] gpsBytes = new byte[] { + 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\0', + 'F', 'o', 'o', 'B', 'a', 'r', '\0', + 'S', 'h', 'a', 'z', 'b', 'o', 't', '\0'}; + + mMetadata.set(gpsProcessingMethodKeyArray, gpsStrings); + + String[] actualArray = mMetadata.get(gpsProcessingMethodKeyArray); + assertArrayEquals(gpsStrings, actualArray); + + byte[] actualBytes2 = mMetadata.readValues(getTag(gpsProcessingMethodKeyArray.getName())); + assertArrayEquals(gpsBytes, actualBytes2); + } + + <T> void compareGeneric(T expected, T actual) { + assertEquals(expected, actual); + } } |