blob: d235e2e5de46702b6d6d5b9ff2ad5637afb38dde [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#include "camera3_test/camera3_exif_validator.h"
#include <algorithm>
#include <limits>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <jpeglib.h>
namespace camera3_test {
enum {
ORIENTATION_UNDEFINED,
ORIENTATION_NORMAL,
ORIENTATION_FLIP_HORIZONTAL,
ORIENTATION_ROTATE_180,
ORIENTATION_FLIP_VERTICAL,
ORIENTATION_TRANSPOSE,
ORIENTATION_ROTATE_90,
ORIENTATION_TRANSVERSE,
ORIENTATION_ROTATE_270,
};
static int32_t GetJpegInfo(uint8_t* data,
size_t size,
ResolutionInfo* resolution,
ExifData** exif_data) {
#define JPEG_APP1 (JPEG_APP0 + 1)
const unsigned int kMaxMarkerLength = 0xffff;
struct jpeg_decompress_struct jpeg_info;
struct jpeg_error_mgr jpeg_error;
jpeg_info.err = jpeg_std_error(&jpeg_error);
jpeg_create_decompress(&jpeg_info);
jpeg_mem_src(&jpeg_info, data, size);
jpeg_save_markers(&jpeg_info, JPEG_APP1, kMaxMarkerLength);
if (jpeg_read_header(&jpeg_info, TRUE) != JPEG_HEADER_OK) {
jpeg_destroy_decompress(&jpeg_info);
return -EINVAL;
}
if (resolution) {
*resolution = ResolutionInfo(jpeg_info.image_width, jpeg_info.image_height);
}
if (exif_data) {
for (auto marker = jpeg_info.marker_list; marker != nullptr;
marker = marker->next) {
if (marker->marker == JPEG_APP1) {
*exif_data = exif_data_new_from_data(marker->data, marker->data_length);
break;
}
}
}
jpeg_destroy_decompress(&jpeg_info);
return 0;
}
static int32_t GetExifOrientationInDegree(int32_t exif_orientation) {
switch (exif_orientation) {
case ORIENTATION_NORMAL:
return 0;
case ORIENTATION_ROTATE_90:
return 90;
case ORIENTATION_ROTATE_180:
return 180;
case ORIENTATION_ROTATE_270:
return 270;
default:
ADD_FAILURE() << "It is impossible to get non 0, 90, 180, 270 degress "
"exif info based on the request orientation range";
return 0;
}
}
static int32_t GetExifTagInteger(const ExifData& exif_data,
const ExifIfd& ifd,
const ExifTag& tag,
const ExifByteOrder& byte_order) {
ExifEntry* entry = exif_data_get_entry((&exif_data), tag);
if (!entry) {
ADD_FAILURE() << "Failed to get EXIF tag "
<< exif_tag_get_name_in_ifd(tag, ifd);
return -EINVAL;
}
switch (entry->format) {
case EXIF_FORMAT_SHORT:
return exif_get_short(entry->data, byte_order);
case EXIF_FORMAT_LONG:
return exif_get_long(entry->data, byte_order);
case EXIF_FORMAT_SLONG:
return exif_get_slong(entry->data, byte_order);
default:
ADD_FAILURE() << "Invalid EXIF entry format " << entry->format;
return -EINVAL;
}
}
static const char* GetExifTagString(const ExifData& exif_data,
const ExifIfd& ifd,
const ExifTag& tag) {
ExifEntry* entry = exif_data_get_entry((&exif_data), tag);
if (!entry) {
ADD_FAILURE() << "Failed to get EXIF tag "
<< exif_tag_get_name_in_ifd(tag, ifd);
return nullptr;
}
EXPECT_EQ(EXIF_FORMAT_ASCII, entry->format)
<< "Invalid EXIF entry string format " << entry->format;
return reinterpret_cast<char*>(entry->data);
}
static float GetExifTagFloat(const ExifData& exif_data,
const ExifIfd& ifd,
const ExifTag& tag,
const ExifByteOrder& byte_order) {
ExifEntry* entry = exif_data_get_entry((&exif_data), tag);
if (!entry) {
ADD_FAILURE() << "Failed to get EXIF tag "
<< exif_tag_get_name_in_ifd(tag, ifd);
return -EINVAL;
}
switch (entry->format) {
case EXIF_FORMAT_RATIONAL: {
ExifRational rational = exif_get_rational(entry->data, byte_order);
return static_cast<float>(rational.numerator) /
static_cast<float>(rational.denominator);
}
case EXIF_FORMAT_SRATIONAL: {
ExifSRational srational = exif_get_srational(entry->data, byte_order);
return static_cast<float>(srational.numerator) /
static_cast<float>(srational.denominator);
}
default:
ADD_FAILURE() << "Invalid EXIF entry format " << entry->format;
return -EINVAL;
}
}
Camera3ExifValidator::JpegExifInfo::JpegExifInfo(
const ScopedBufferHandle& buffer, size_t size)
: buffer_handle(buffer),
buffer_size(size),
buffer_addr(nullptr),
jpeg_resolution(ResolutionInfo(0, 0)),
exif_data(nullptr) {
// Get JPEG image address and size
if (Camera3TestGralloc::GetInstance()->Lock(*buffer_handle, 0, 0, 0, size, 1,
&buffer_addr) != 0 ||
!buffer_addr) {
ADD_FAILURE() << "Failed to map buffer";
}
}
Camera3ExifValidator::JpegExifInfo::~JpegExifInfo() {
if (exif_data) {
exif_data_unref(exif_data);
}
Camera3TestGralloc::GetInstance()->Unlock(*buffer_handle);
}
bool Camera3ExifValidator::JpegExifInfo::Initialize() {
if (!buffer_addr) {
return false;
}
auto jpeg_blob = reinterpret_cast<camera3_jpeg_blob_t*>(
static_cast<uint8_t*>(buffer_addr) + buffer_size -
sizeof(camera3_jpeg_blob_t));
if (static_cast<void*>(jpeg_blob) < buffer_addr ||
jpeg_blob->jpeg_blob_id != CAMERA3_JPEG_BLOB_ID) {
ADD_FAILURE() << "Invalid JPEG BLOB ID";
return false;
}
if (GetJpegInfo(static_cast<uint8_t*>(buffer_addr), jpeg_blob->jpeg_size,
&jpeg_resolution, &exif_data) != 0) {
ADD_FAILURE() << "No valid JPEG image found in the buffer";
return false;
}
return true;
}
static ResolutionInfo GetMetadataThumbnailSize(
const camera_metadata_t& metadata) {
const size_t kNumOfElementsInSizeEntry = 2;
enum { SIZE_ENTRY_WIDTH_INDEX, SIZE_ENTRY_HEIGHT_INDEX };
camera_metadata_ro_entry_t entry;
if (find_camera_metadata_ro_entry(&metadata, ANDROID_JPEG_THUMBNAIL_SIZE,
&entry)) {
ADD_FAILURE() << "Cannot find the metadata ANDROID_JPEG_THUMBNAIL_SIZE";
return ResolutionInfo(-1, -1);
}
if (entry.count != kNumOfElementsInSizeEntry) {
ADD_FAILURE()
<< "Invalid entry count of metadata ANDROID_JPEG_THUMBNAIL_SIZE ("
<< entry.count << ")";
return ResolutionInfo(-1, -1);
}
return ResolutionInfo(entry.data.i32[SIZE_ENTRY_WIDTH_INDEX],
entry.data.i32[SIZE_ENTRY_HEIGHT_INDEX]);
}
static int64_t GetMetadataInteger(const camera_metadata_t& metadata,
int32_t key) {
camera_metadata_ro_entry_t entry;
if (find_camera_metadata_ro_entry(&metadata, key, &entry)) {
ADD_FAILURE() << "Cannot find the metadata "
<< get_camera_metadata_tag_name(key);
return -EINVAL;
}
switch (entry.type) {
case TYPE_BYTE:
return entry.data.u8[0];
case TYPE_INT32:
return entry.data.i32[0];
case TYPE_INT64:
return entry.data.i64[0];
default:
ADD_FAILURE() << "Unexpected metadata entry type " << entry.type;
return -EINVAL;
}
}
static float GetMetadataKeyValueFloat(const camera_metadata_t& metadata,
int32_t key) {
camera_metadata_ro_entry_t entry;
if (find_camera_metadata_ro_entry(&metadata, key, &entry)) {
ADD_FAILURE() << "Cannot find the metadata "
<< get_camera_metadata_tag_name(key);
return -EINVAL;
}
if (entry.type != TYPE_FLOAT) {
ADD_FAILURE() << "Unexpected metadata entry type " << entry.type;
return -EINVAL;
}
return entry.data.f[0];
}
void Camera3ExifValidator::ValidateExifKeys(
const ResolutionInfo& jpeg_resolution,
const ExifTestData& exif_test_data,
const ScopedBufferHandle& buffer,
size_t buffer_size,
const camera_metadata_t& metadata,
const time_t& date_time) const {
const int32_t kExifDateTimeStringLength = 19;
const double kExifDateTimeErrorMarginSeconds = 60;
const float kExifFocalLengthErrorMargin = 0.001f;
const float kExifExposureTimeErrorMarginRation = 0.05f;
const float kExifExposureTimeMinErrorMarginSeconds = 0.002f;
const float kOneNanoSecond = 1e-9;
const float kExifApertureErrorMargin = 0.001f;
auto GetClosestValueInArray = [](std::vector<float> values, float target) {
int min_idx = -1;
float min_distance = std::numeric_limits<float>::max();
for (size_t i = 0; i < values.size(); i++) {
float distance = std::abs(values[i] - target);
if (min_distance > distance) {
min_distance = distance;
min_idx = i;
}
}
return (min_idx >= 0) ? values[min_idx]
: -std::numeric_limits<float>::max();
};
auto SwapWidthAndHeight = [](ResolutionInfo* resolution) {
*resolution = ResolutionInfo(resolution->Height(), resolution->Width());
};
JpegExifInfo jpeg_exif_info(buffer, buffer_size);
ASSERT_TRUE(jpeg_exif_info.Initialize());
ExifByteOrder byte_order = exif_data_get_byte_order(jpeg_exif_info.exif_data);
const ExifData& exif_data = *jpeg_exif_info.exif_data;
int32_t exif_orientation = GetExifTagInteger(
exif_data, EXIF_IFD_0, EXIF_TAG_ORIENTATION, byte_order);
EXPECT_LE(ORIENTATION_UNDEFINED, exif_orientation)
<< "Invalid EXIF orientation value";
EXPECT_GE(ORIENTATION_ROTATE_270, exif_orientation)
<< "Invalid EXIF orientation value";
EXPECT_TRUE(exif_orientation == ORIENTATION_UNDEFINED ||
exif_test_data.orientation ==
GetExifOrientationInDegree(exif_orientation))
<< "EXIF orientaiton should match requested orientation";
if (exif_test_data.thumbnail_resolution == ResolutionInfo(0, 0)) {
EXPECT_EQ(nullptr, exif_data.data)
<< "JPEG shouldn't have thumbnail when thumbnail size is (0, 0)";
} else {
EXPECT_NE(nullptr, exif_data.data) << "No thumbnail found in JPEG image";
ResolutionInfo expected_thumbnail_resolution(
exif_test_data.thumbnail_resolution);
if (exif_test_data.orientation % 180 == 90 &&
exif_orientation == ORIENTATION_UNDEFINED) {
// Device physically rotated image+thumbnail data
// Expect thumbnail size to be also rotated
SwapWidthAndHeight(&expected_thumbnail_resolution);
}
EXPECT_EQ(expected_thumbnail_resolution, GetMetadataThumbnailSize(metadata))
<< "JPEG thumbnail size result and request should match";
if (exif_data.data) {
ResolutionInfo actual_thumbnail_resolution(0, 0);
if (GetJpegInfo(exif_data.data, exif_data.size,
&actual_thumbnail_resolution, nullptr) != 0) {
ADD_FAILURE() << "No valid thumbnail image found in the buffer";
} else {
EXPECT_EQ(expected_thumbnail_resolution, actual_thumbnail_resolution)
<< "EXIF thumbnail image size should match requested size";
}
}
}
EXPECT_EQ(exif_test_data.orientation,
GetMetadataInteger(metadata, ANDROID_JPEG_ORIENTATION))
<< "JPEG orientation result and request should match";
EXPECT_EQ(exif_test_data.jpeg_quality,
GetMetadataInteger(metadata, ANDROID_JPEG_QUALITY))
<< "JPEG quality result and request should match";
EXPECT_EQ(exif_test_data.thumbnail_quality,
GetMetadataInteger(metadata, ANDROID_JPEG_THUMBNAIL_QUALITY))
<< "JPEG thumbnail quality result and request should match";
// TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH and TAG_ORIENTATION.
// Orientation and exif width/height need to be tested carefully, two cases:
// 1. Device rotates the image buffer physically, then exif width/height may
// not match the requested still capture size, we need swap them to check.
// 2. Device uses the exif tag to record the image orientation, it doesn't
// rotate the jpeg image buffer itself. In this case, the exif width/height
// should always match the requested still capture size, and the exif
// orientation should always match the requested orientation.
ResolutionInfo expected_jpeg_size(jpeg_resolution);
if (exif_test_data.orientation % 180 == 90 &&
exif_orientation == ORIENTATION_UNDEFINED) {
// Device captured image doesn't respect the requested orientation, which
// means it rotates the image buffer physically. Then we should swap the
// exif width/height accordingly to compare.
SwapWidthAndHeight(&expected_jpeg_size);
}
EXPECT_EQ(
expected_jpeg_size,
ResolutionInfo(GetExifTagInteger(exif_data, EXIF_IFD_0,
EXIF_TAG_IMAGE_WIDTH, byte_order),
GetExifTagInteger(exif_data, EXIF_IFD_0,
EXIF_TAG_IMAGE_LENGTH, byte_order)))
<< "EXIF JPEG size should match requested size";
EXPECT_EQ(
expected_jpeg_size,
ResolutionInfo(GetExifTagInteger(exif_data, EXIF_IFD_EXIF,
EXIF_TAG_PIXEL_X_DIMENSION, byte_order),
GetExifTagInteger(exif_data, EXIF_IFD_EXIF,
EXIF_TAG_PIXEL_Y_DIMENSION, byte_order)))
<< "EXIF JPEG pixel dimension should match requested size";
EXPECT_EQ(expected_jpeg_size, jpeg_exif_info.jpeg_resolution)
<< "JPEG size result and request should match";
// Validate date time between EXIF data and current time
const char* exif_datetime_string =
GetExifTagString(exif_data, EXIF_IFD_0, EXIF_TAG_DATE_TIME);
if (exif_datetime_string) {
EXPECT_EQ(kExifDateTimeStringLength, strlen(exif_datetime_string))
<< "EXIF dateTime is in wrong format";
struct tm exif_tm = {};
strptime(exif_datetime_string, "%Y:%m:%d %H:%M:%S", &exif_tm);
time_t exif_time = mktime(&exif_tm);
EXPECT_GT(kExifDateTimeErrorMarginSeconds, difftime(date_time, exif_time))
<< "Capture time deviates too much from the current time";
const char* exif_datetime_digitized = GetExifTagString(
exif_data, EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_DIGITIZED);
if (exif_datetime_digitized) {
EXPECT_EQ(kExifDateTimeStringLength, strlen(exif_datetime_digitized))
<< "EXIF digitizedTime is in wrong format";
EXPECT_EQ(0, strncmp(exif_datetime_string, exif_datetime_digitized,
kExifDateTimeStringLength))
<< "EXIF dataTime should match digitizedTime";
}
}
// Validate focal length between EXIF data and metadata
std::vector<float> focal_lengths;
if (cam_info_.GetAvailableFocalLengths(&focal_lengths) != 0 ||
focal_lengths.empty()) {
ADD_FAILURE() << "Failed to get available focal lengths";
} else {
float exif_focal_length = GetExifTagFloat(
exif_data, EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, byte_order);
EXPECT_GT(
kExifFocalLengthErrorMargin,
std::abs(GetClosestValueInArray(focal_lengths, exif_focal_length) -
exif_focal_length))
<< "EXIF focal length should be one of the available focal lengths";
float result_focal_length =
GetMetadataKeyValueFloat(metadata, ANDROID_LENS_FOCAL_LENGTH);
EXPECT_LT(0.0f, result_focal_length)
<< "Result Focal length " << result_focal_length
<< " should be positive";
EXPECT_NE(focal_lengths.end(),
std::find(focal_lengths.begin(), focal_lengths.end(),
result_focal_length))
<< "Result Focal length should be one of the available focal lengths";
EXPECT_GT(kExifFocalLengthErrorMargin,
std::abs(result_focal_length - exif_focal_length))
<< "EXIF focal length should match capture result";
}
// Validate exposure time between EXIF data and metadata
float exif_exposure_time = GetExifTagFloat(
exif_data, EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_TIME, byte_order);
if (exif_exposure_time > 0 &&
cam_info_.IsKeyAvailable(ANDROID_SENSOR_EXPOSURE_TIME)) {
float result_exposure_time = static_cast<float>(GetMetadataInteger(
metadata, ANDROID_SENSOR_EXPOSURE_TIME)) *
kOneNanoSecond;
float tolerance =
std::max(result_exposure_time * kExifExposureTimeErrorMarginRation,
kExifExposureTimeMinErrorMarginSeconds);
EXPECT_GT(tolerance, std::abs(result_exposure_time - exif_exposure_time))
<< "Exif exposure time doesn't match";
}
// Validate aperture between EXIF data and metadata
float exif_aperture =
GetExifTagFloat(exif_data, EXIF_IFD_EXIF, EXIF_TAG_FNUMBER, byte_order);
if (exif_aperture > 0 &&
cam_info_.IsKeyAvailable(ANDROID_LENS_INFO_AVAILABLE_APERTURES)) {
std::vector<float> apertures;
if (cam_info_.GetAvailableApertures(&apertures) != 0 || apertures.empty()) {
ADD_FAILURE() << "Failed to get available apertures";
} else {
float result_aperture =
GetMetadataKeyValueFloat(metadata, ANDROID_LENS_APERTURE);
EXPECT_GT(kExifApertureErrorMargin,
std::abs(GetClosestValueInArray(apertures, exif_aperture) -
exif_aperture))
<< "Aperture value should be one of the available apertures";
EXPECT_GT(kExifApertureErrorMargin,
std::abs(result_aperture - exif_aperture))
<< "Exif aperture length should match capture result";
}
}
// Verify EXIF TAG_FLASH is available
GetExifTagInteger(exif_data, EXIF_IFD_EXIF, EXIF_TAG_FLASH, byte_order);
// Verify EXIF TAG_WHITE_BALANCE is available
GetExifTagInteger(exif_data, EXIF_IFD_EXIF, EXIF_TAG_WHITE_BALANCE,
byte_order);
// TODO(hywu): For full devices, validate flash and AWB between EXIF and
// metadata
// Validate ISO between EXIF and metadata
if (cam_info_.IsKeyAvailable(ANDROID_SENSOR_SENSITIVITY)) {
int32_t iso = GetExifTagInteger(exif_data, EXIF_IFD_EXIF,
EXIF_TAG_ISO_SPEED_RATINGS, byte_order);
EXPECT_EQ(GetMetadataInteger(metadata, ANDROID_SENSOR_SENSITIVITY), iso)
<< "EXIF TAG_ISO is incorrect";
}
auto is_number = [&](const ExifIfd& ifd, const ExifTag& tag) {
const char* c = GetExifTagString(exif_data, ifd, tag);
const std::string s(c ? c : "");
return !s.empty() && std::find_if(s.begin(), s.end(), [](char c) {
return !std::isdigit(c);
}) == s.end();
};
EXPECT_TRUE(is_number(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME));
EXPECT_TRUE(is_number(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_ORIGINAL));
EXPECT_TRUE(is_number(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_DIGITIZED));
}
int Camera3ExifValidator::getExifOrientation(const ScopedBufferHandle& buffer,
size_t buffer_size) {
JpegExifInfo jpeg_exif_info(buffer, buffer_size);
jpeg_exif_info.Initialize();
ExifByteOrder byte_order = exif_data_get_byte_order(jpeg_exif_info.exif_data);
const ExifData& exif_data = *jpeg_exif_info.exif_data;
int32_t exif_orientation = GetExifTagInteger(
exif_data, EXIF_IFD_0, EXIF_TAG_ORIENTATION, byte_order);
return exif_orientation;
}
} // namespace camera3_test