Camera2: Fix the JPEG format issues

Also add unit test.

Bug: 13836016

Change-Id: I0af4bfe299f99c99e70faf5d3159eb0cbcc147eb
This commit is contained in:
Zhijun He
2014-04-08 22:43:51 -07:00
parent 972d0488a5
commit 8d637b819b
2 changed files with 209 additions and 19 deletions

View File

@@ -461,6 +461,10 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
return (T) getFaces();
} else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) {
return (T) getFaceRectangles();
} else if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS)) {
return (T) getAvailableStreamConfigurations();
} else if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS)) {
return (T) getAvailableMinFrameDurations();
}
// For other keys, get() falls back to getBase()
@@ -481,6 +485,50 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
return availableFormats;
}
private int[] getAvailableStreamConfigurations() {
final int NUM_ELEMENTS_IN_CONFIG = 4;
int[] availableConfigs =
getBase(CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
if (availableConfigs != null) {
if (availableConfigs.length % NUM_ELEMENTS_IN_CONFIG != 0) {
Log.w(TAG, "availableStreamConfigurations is malformed, length must be multiple"
+ " of " + NUM_ELEMENTS_IN_CONFIG);
return availableConfigs;
}
for (int i = 0; i < availableConfigs.length; i += NUM_ELEMENTS_IN_CONFIG) {
// JPEG has different value between native and managed side, need override.
if (availableConfigs[i] == NATIVE_JPEG_FORMAT) {
availableConfigs[i] = ImageFormat.JPEG;
}
}
}
return availableConfigs;
}
private long[] getAvailableMinFrameDurations() {
final int NUM_ELEMENTS_IN_DURATION = 4;
long[] availableMinDurations =
getBase(CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS);
if (availableMinDurations != null) {
if (availableMinDurations.length % NUM_ELEMENTS_IN_DURATION != 0) {
Log.w(TAG, "availableStreamConfigurations is malformed, length must be multiple"
+ " of " + NUM_ELEMENTS_IN_DURATION);
return availableMinDurations;
}
for (int i = 0; i < availableMinDurations.length; i += NUM_ELEMENTS_IN_DURATION) {
// JPEG has different value between native and managed side, need override.
if (availableMinDurations[i] == NATIVE_JPEG_FORMAT) {
availableMinDurations[i] = ImageFormat.JPEG;
}
}
}
return availableMinDurations;
}
private Face[] getFaces() {
final int FACE_LANDMARK_SIZE = 6;
@@ -607,12 +655,56 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
return setAvailableFormats((int[]) value);
} else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) {
return setFaceRectangles((Rect[]) value);
} else if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS)) {
return setAvailableStreamConfigurations((int[])value);
} else if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS)) {
return setAvailableMinFrameDurations((long[])value);
}
// For other keys, set() falls back to setBase().
return false;
}
private boolean setAvailableStreamConfigurations(int[] value) {
final int NUM_ELEMENTS_IN_CONFIG = 4;
int[] availableConfigs = value;
if (value == null) {
// Let setBase() to handle the null value case.
return false;
}
int[] newValues = new int[availableConfigs.length];
for (int i = 0; i < availableConfigs.length; i++) {
newValues[i] = availableConfigs[i];
if (i % NUM_ELEMENTS_IN_CONFIG == 0 && availableConfigs[i] == ImageFormat.JPEG) {
newValues[i] = NATIVE_JPEG_FORMAT;
}
}
setBase(CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS, newValues);
return true;
}
private boolean setAvailableMinFrameDurations(long[] value) {
final int NUM_ELEMENTS_IN_DURATION = 4;
long[] availableDurations = value;
if (value == null) {
// Let setBase() to handle the null value case.
return false;
}
long[] newValues = new long[availableDurations.length];
for (int i = 0; i < availableDurations.length; i++) {
newValues[i] = availableDurations[i];
if (i % NUM_ELEMENTS_IN_DURATION == 0 && availableDurations[i] == ImageFormat.JPEG) {
newValues[i] = NATIVE_JPEG_FORMAT;
}
}
setBase(CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS, newValues);
return true;
}
private boolean setAvailableFormats(int[] value) {
int[] availableFormat = value;
if (value == null) {

View File

@@ -552,29 +552,72 @@ public class CameraMetadataTest extends junit.framework.TestCase {
};
int availableFormatTag = CameraMetadataNative.getTag("android.scaler.availableFormats");
// Write
mMetadata.set(CameraCharacteristics.SCALER_AVAILABLE_FORMATS, availableFormats);
Key<int[]> formatKey = CameraCharacteristics.SCALER_AVAILABLE_FORMATS;
byte[] availableFormatValues = mMetadata.readValues(availableFormatTag);
validateArrayMetadataReadWriteOverride(formatKey, availableFormats,
expectedIntValues, availableFormatTag);
ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder());
//
// android.scaler.availableStreamConfigurations (int x n x 4 array)
//
final int OUTPUT = CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT;
int[] availableStreamConfigs = new int[] {
0x20, 3280, 2464, OUTPUT, // RAW16
0x23, 3264, 2448, OUTPUT, // YCbCr_420_888
0x23, 3200, 2400, OUTPUT, // YCbCr_420_888
0x100, 3264, 2448, OUTPUT, // ImageFormat.JPEG
0x100, 3200, 2400, OUTPUT, // ImageFormat.JPEG
0x100, 2592, 1944, OUTPUT, // ImageFormat.JPEG
0x100, 2048, 1536, OUTPUT, // ImageFormat.JPEG
0x100, 1920, 1080, OUTPUT // ImageFormat.JPEG
};
int[] expectedAvailableStreamConfigs = new int[] {
0x20, 3280, 2464, OUTPUT, // RAW16
0x23, 3264, 2448, OUTPUT, // YCbCr_420_888
0x23, 3200, 2400, OUTPUT, // YCbCr_420_888
0x21, 3264, 2448, OUTPUT, // BLOB
0x21, 3200, 2400, OUTPUT, // BLOB
0x21, 2592, 1944, OUTPUT, // BLOB
0x21, 2048, 1536, OUTPUT, // BLOB
0x21, 1920, 1080, OUTPUT // BLOB
};
int availableStreamConfigTag =
CameraMetadataNative.getTag("android.scaler.availableStreamConfigurations");
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);
Key<int[]> configKey = CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS;
validateArrayMetadataReadWriteOverride(configKey, availableStreamConfigs,
expectedAvailableStreamConfigs, availableStreamConfigTag);
int[] resultFormats = mMetadata.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS);
assertNotNull("result available formats shouldn't be null", resultFormats);
assertArrayEquals(availableFormats, resultFormats);
//
// android.scaler.availableMinFrameDurations (int x n x 4 array)
//
long[] availableMinDurations = new long[] {
0x20, 3280, 2464, 33333336, // RAW16
0x23, 3264, 2448, 33333336, // YCbCr_420_888
0x23, 3200, 2400, 33333336, // YCbCr_420_888
0x100, 3264, 2448, 33333336, // ImageFormat.JPEG
0x100, 3200, 2400, 33333336, // ImageFormat.JPEG
0x100, 2592, 1944, 33333336, // ImageFormat.JPEG
0x100, 2048, 1536, 33333336, // ImageFormat.JPEG
0x100, 1920, 1080, 33333336 // ImageFormat.JPEG
};
long[] expectedAvailableMinDurations = new long[] {
0x20, 3280, 2464, 33333336, // RAW16
0x23, 3264, 2448, 33333336, // YCbCr_420_888
0x23, 3200, 2400, 33333336, // YCbCr_420_888
0x21, 3264, 2448, 33333336, // BLOB
0x21, 3200, 2400, 33333336, // BLOB
0x21, 2592, 1944, 33333336, // BLOB
0x21, 2048, 1536, 33333336, // BLOB
0x21, 1920, 1080, 33333336 // BLOB
};
int availableMinDurationsTag =
CameraMetadataNative.getTag("android.scaler.availableMinFrameDurations");
Key<long[]> durationKey = CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS;
validateArrayMetadataReadWriteOverride(durationKey, availableMinDurations,
expectedAvailableMinDurations, availableMinDurationsTag);
//
// android.statistics.faces (Face x n array)
@@ -639,4 +682,59 @@ public class CameraMetadataTest extends junit.framework.TestCase {
}
}
/**
* Validate metadata array tag read/write override.
*
* <p>Only support long and int array for now, can be easily extend to support other
* primitive arrays.</p>
*/
private <T> void validateArrayMetadataReadWriteOverride(Key<T> key, T writeValues,
T readValues, int tag) {
Class<T> type = key.getType();
if (!type.isArray()) {
throw new IllegalArgumentException("This function expects an key with array type");
} else if (type != int[].class && type != long[].class) {
throw new IllegalArgumentException("This function expects long or int array values");
}
// Write
mMetadata.set(key, writeValues);
byte[] readOutValues = mMetadata.readValues(tag);
ByteBuffer bf = ByteBuffer.wrap(readOutValues).order(ByteOrder.nativeOrder());
int readValuesLength = Array.getLength(readValues);
int readValuesNumBytes = readValuesLength * 4;
if (type == long[].class) {
readValuesNumBytes = readValuesLength * 8;
}
assertEquals(readValuesNumBytes, readOutValues.length);
for (int i = 0; i < readValuesLength; ++i) {
if (type == int[].class) {
assertEquals(Array.getInt(readValues, i), bf.getInt());
} else if (type == long[].class) {
assertEquals(Array.getLong(readValues, i), bf.getLong());
}
}
// Read
byte[] readOutValuesAsByteArray = new byte[readValuesNumBytes];
ByteBuffer readOutValuesByteBuffer =
ByteBuffer.wrap(readOutValuesAsByteArray).order(ByteOrder.nativeOrder());
for (int i = 0; i < readValuesLength; ++i) {
if (type == int[].class) {
readOutValuesByteBuffer.putInt(Array.getInt(readValues, i));
} else if (type == long[].class) {
readOutValuesByteBuffer.putLong(Array.getLong(readValues, i));
}
}
mMetadata.writeValues(tag, readOutValuesAsByteArray);
T result = mMetadata.get(key);
assertNotNull(key.getName() + " result shouldn't be null", result);
assertArrayEquals(writeValues, result);
}
}