| // 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 |