Generate correct Exif data from capture metadata
The GPS metadata are supposed to by piped from request to result and
included in the jpeg Exif.
Additionnaly, flash, make and model are added in the exif metadata with
some default value since they are required.
Test: atest 'android.hardware.camera2.cts.StillCaptureTest#testJpegExif[1]'
Fixes: 324383963
Change-Id: Iaf161a2d9816115a0daba19dc2d9a911bc879330
diff --git a/services/camera/virtualcamera/VirtualCameraDevice.h b/services/camera/virtualcamera/VirtualCameraDevice.h
index 4c50041..0aebf6e 100644
--- a/services/camera/virtualcamera/VirtualCameraDevice.h
+++ b/services/camera/virtualcamera/VirtualCameraDevice.h
@@ -122,6 +122,12 @@
// Default JPEG compression quality.
static constexpr uint8_t kDefaultJpegQuality = 80;
+ // Default JPEG orientation.
+ static constexpr uint8_t kDefaultJpegOrientation = 0;
+
+ // Default Make and Model for Exif
+ static constexpr char kDefaultMakeAndModel[] = "Android Virtual Camera";
+
static constexpr camera_metadata_enum_android_control_capture_intent_t
kDefaultCaptureIntent = ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW;
diff --git a/services/camera/virtualcamera/VirtualCameraRenderThread.cc b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
index 6e89b5f..164580f 100644
--- a/services/camera/virtualcamera/VirtualCameraRenderThread.cc
+++ b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include "system/camera_metadata.h"
#define LOG_TAG "VirtualCameraRenderThread"
#include "VirtualCameraRenderThread.h"
@@ -45,6 +44,7 @@
#include "android-base/thread_annotations.h"
#include "android/binder_auto_utils.h"
#include "android/hardware_buffer.h"
+#include "system/camera_metadata.h"
#include "ui/GraphicBuffer.h"
#include "util/EglFramebuffer.h"
#include "util/JpegUtil.h"
@@ -128,6 +128,7 @@
.setFlashMode(ANDROID_FLASH_MODE_OFF)
.setFocalLength(VirtualCameraDevice::kFocalLength)
.setJpegQuality(requestSettings.jpegQuality)
+ .setJpegOrientation(requestSettings.jpegOrientation)
.setJpegThumbnailSize(requestSettings.thumbnailResolution.width,
requestSettings.thumbnailResolution.height)
.setJpegThumbnailQuality(requestSettings.thumbnailJpegQuality)
@@ -146,6 +147,11 @@
builder.setControlAeTargetFpsRange(requestSettings.fpsRange.value());
}
+ if (requestSettings.gpsCoordinates.has_value()) {
+ const GpsCoordinates& coordinates = requestSettings.gpsCoordinates.value();
+ builder.setJpegGpsCoordinates(coordinates);
+ }
+
std::unique_ptr<CameraMetadata> metadata = builder.build();
if (metadata == nullptr) {
@@ -224,12 +230,24 @@
}
std::vector<uint8_t> createExif(
- Resolution imageSize, const std::vector<uint8_t>& compressedThumbnail = {}) {
+ Resolution imageSize, const CameraMetadata resultMetadata,
+ const std::vector<uint8_t>& compressedThumbnail = {}) {
std::unique_ptr<ExifUtils> exifUtils(ExifUtils::create());
exifUtils->initialize();
- exifUtils->setImageWidth(imageSize.width);
- exifUtils->setImageHeight(imageSize.height);
- // TODO(b/324383963) Set Make/Model and orientation.
+
+ // Make a copy of the metadata in order to converting it the HAL metadata
+ // format (as opposed to the AIDL class) and use the setFromMetadata method
+ // from ExifUtil
+ camera_metadata_t* rawSettings =
+ clone_camera_metadata((camera_metadata_t*)resultMetadata.metadata.data());
+ if (rawSettings != nullptr) {
+ android::hardware::camera::common::helper::CameraMetadata halMetadata(
+ rawSettings);
+ exifUtils->setFromMetadata(halMetadata, imageSize.width, imageSize.height);
+ }
+ exifUtils->setMake(VirtualCameraDevice::kDefaultMakeAndModel);
+ exifUtils->setModel(VirtualCameraDevice::kDefaultMakeAndModel);
+ exifUtils->setFlash(0);
std::vector<uint8_t> app1Data;
@@ -427,7 +445,8 @@
auto status = streamConfig->format == PixelFormat::BLOB
? renderIntoBlobStreamBuffer(
reqBuffer.getStreamId(), reqBuffer.getBufferId(),
- request.getRequestSettings(), reqBuffer.getFence())
+ captureResult.result, request.getRequestSettings(),
+ reqBuffer.getFence())
: renderIntoImageStreamBuffer(reqBuffer.getStreamId(),
reqBuffer.getBufferId(),
reqBuffer.getFence());
@@ -569,7 +588,7 @@
}
ndk::ScopedAStatus VirtualCameraRenderThread::renderIntoBlobStreamBuffer(
- const int streamId, const int bufferId,
+ const int streamId, const int bufferId, const CameraMetadata& resultMetadata,
const RequestSettings& requestSettings, sp<Fence> fence) {
std::shared_ptr<AHardwareBuffer> hwBuffer =
mSessionContext.fetchHardwareBuffer(streamId, bufferId);
@@ -635,7 +654,7 @@
}
std::vector<uint8_t> app1ExifData =
- createExif(Resolution(stream->width, stream->height),
+ createExif(Resolution(stream->width, stream->height), resultMetadata,
createThumbnail(requestSettings.thumbnailResolution,
requestSettings.thumbnailJpegQuality));
std::optional<size_t> compressedSize = compressJpeg(
diff --git a/services/camera/virtualcamera/VirtualCameraRenderThread.h b/services/camera/virtualcamera/VirtualCameraRenderThread.h
index e8e0bd4..2576556 100644
--- a/services/camera/virtualcamera/VirtualCameraRenderThread.h
+++ b/services/camera/virtualcamera/VirtualCameraRenderThread.h
@@ -57,11 +57,13 @@
struct RequestSettings {
int jpegQuality = VirtualCameraDevice::kDefaultJpegQuality;
+ int jpegOrientation = VirtualCameraDevice::kDefaultJpegOrientation;
Resolution thumbnailResolution = Resolution(0, 0);
int thumbnailJpegQuality = VirtualCameraDevice::kDefaultJpegQuality;
- std::optional<FpsRange> fpsRange = {};
+ std::optional<FpsRange> fpsRange;
camera_metadata_enum_android_control_capture_intent_t captureIntent =
VirtualCameraDevice::kDefaultCaptureIntent;
+ std::optional<GpsCoordinates> gpsCoordinates;
};
// Represents single capture request to fill set of buffers.
@@ -153,6 +155,8 @@
// Always called on render thread.
ndk::ScopedAStatus renderIntoBlobStreamBuffer(
const int streamId, const int bufferId,
+ const ::aidl::android::hardware::camera::device::CameraMetadata&
+ resultMetadata,
const RequestSettings& requestSettings, sp<Fence> fence = nullptr);
// Render current image to the YCbCr buffer.
diff --git a/services/camera/virtualcamera/VirtualCameraSession.cc b/services/camera/virtualcamera/VirtualCameraSession.cc
index a0bb72e..05fa5ae 100644
--- a/services/camera/virtualcamera/VirtualCameraSession.cc
+++ b/services/camera/virtualcamera/VirtualCameraSession.cc
@@ -257,13 +257,15 @@
return RequestSettings{
.jpegQuality = getJpegQuality(metadata).value_or(
VirtualCameraDevice::kDefaultJpegQuality),
+ .jpegOrientation = getJpegOrientation(metadata),
.thumbnailResolution =
getJpegThumbnailSize(metadata).value_or(Resolution(0, 0)),
.thumbnailJpegQuality = getJpegThumbnailQuality(metadata).value_or(
VirtualCameraDevice::kDefaultJpegQuality),
.fpsRange = getFpsRange(metadata),
.captureIntent = getCaptureIntent(metadata).value_or(
- ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW)};
+ ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW),
+ .gpsCoordinates = getGpsCoordinates(metadata)};
}
} // namespace
diff --git a/services/camera/virtualcamera/util/MetadataUtil.cc b/services/camera/virtualcamera/util/MetadataUtil.cc
index 119260f..c7dc80e 100644
--- a/services/camera/virtualcamera/util/MetadataUtil.cc
+++ b/services/camera/virtualcamera/util/MetadataUtil.cc
@@ -24,6 +24,7 @@
#include <iterator>
#include <memory>
#include <optional>
+#include <string>
#include <utility>
#include <variant>
#include <vector>
@@ -435,6 +436,29 @@
return *this;
}
+MetadataBuilder& MetadataBuilder::setJpegGpsCoordinates(
+ const GpsCoordinates& gpsCoordinates) {
+ mEntryMap[ANDROID_JPEG_GPS_COORDINATES] =
+ std::vector<double>({gpsCoordinates.latitude, gpsCoordinates.longitude,
+ gpsCoordinates.altitude});
+
+ if (!gpsCoordinates.provider.empty()) {
+ mEntryMap[ANDROID_JPEG_GPS_PROCESSING_METHOD] = std::vector<uint8_t>{
+ gpsCoordinates.provider.begin(), gpsCoordinates.provider.end()};
+ }
+
+ if (gpsCoordinates.timestamp.has_value()) {
+ mEntryMap[ANDROID_JPEG_GPS_TIMESTAMP] =
+ asVectorOf<int64_t>(gpsCoordinates.timestamp.value());
+ }
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setJpegOrientation(const int32_t orientation) {
+ mEntryMap[ANDROID_JPEG_ORIENTATION] = asVectorOf<int32_t>(orientation);
+ return *this;
+}
+
MetadataBuilder& MetadataBuilder::setJpegQuality(const uint8_t quality) {
mEntryMap[ANDROID_JPEG_QUALITY] = asVectorOf<uint8_t>(quality);
return *this;
@@ -753,6 +777,20 @@
return *entry.data.i32;
}
+int32_t getJpegOrientation(
+ const aidl::android::hardware::camera::device::CameraMetadata& cameraMetadata) {
+ auto metadata =
+ reinterpret_cast<const camera_metadata_t*>(cameraMetadata.metadata.data());
+
+ camera_metadata_ro_entry_t entry;
+ if (find_camera_metadata_ro_entry(metadata, ANDROID_JPEG_ORIENTATION,
+ &entry) != OK) {
+ return 0;
+ }
+
+ return *entry.data.i32;
+}
+
std::optional<Resolution> getJpegThumbnailSize(
const aidl::android::hardware::camera::device::CameraMetadata& cameraMetadata) {
auto metadata =
@@ -832,6 +870,39 @@
entry.data.u8[0]);
}
+std::optional<GpsCoordinates> getGpsCoordinates(
+ const aidl::android::hardware::camera::device::CameraMetadata& cameraMetadata) {
+ auto metadata =
+ reinterpret_cast<const camera_metadata_t*>(cameraMetadata.metadata.data());
+
+ camera_metadata_ro_entry_t entry;
+ if (find_camera_metadata_ro_entry(metadata, ANDROID_JPEG_GPS_COORDINATES,
+ &entry) != OK) {
+ return std::nullopt;
+ }
+
+ GpsCoordinates coordinates{.latitude = entry.data.d[0],
+ .longitude = entry.data.d[1],
+ .altitude = entry.data.d[2]};
+
+ if (find_camera_metadata_ro_entry(metadata, ANDROID_JPEG_GPS_TIMESTAMP,
+ &entry) == OK) {
+ coordinates.timestamp = entry.data.i64[0];
+ }
+
+ // According to types.hal, the string describing the GPS processing method has
+ // a 32 characters size
+ static constexpr float kGpsProviderStringLength = 32;
+ if (find_camera_metadata_ro_entry(
+ metadata, ANDROID_JPEG_GPS_PROCESSING_METHOD, &entry) == OK) {
+ coordinates.provider.assign(
+ reinterpret_cast<const char*>(entry.data.u8),
+ std::min(entry.count, static_cast<size_t>(kGpsProviderStringLength)));
+ }
+
+ return coordinates;
+}
+
} // namespace virtualcamera
} // namespace companion
} // namespace android
diff --git a/services/camera/virtualcamera/util/MetadataUtil.h b/services/camera/virtualcamera/util/MetadataUtil.h
index f6848cd..5d16506 100644
--- a/services/camera/virtualcamera/util/MetadataUtil.h
+++ b/services/camera/virtualcamera/util/MetadataUtil.h
@@ -328,6 +328,12 @@
MetadataBuilder& setJpegAvailableThumbnailSizes(
const std::vector<Resolution>& thumbnailSizes);
+ // See ANDROID_JPEG_GPS_COORDINATES.
+ MetadataBuilder& setJpegGpsCoordinates(const GpsCoordinates& gpsCoordinates);
+
+ // See JPEG_ORIENTATION in CaptureRequest.java.
+ MetadataBuilder& setJpegOrientation(int32_t orientation);
+
// See JPEG_QUALITY in CaptureRequest.java.
MetadataBuilder& setJpegQuality(uint8_t quality);
@@ -421,10 +427,11 @@
private:
// Maps metadata tags to vectors of values for the given tag.
- std::map<camera_metadata_tag_t,
- std::variant<std::vector<int64_t>, std::vector<int32_t>,
- std::vector<uint8_t>, std::vector<float>,
- std::vector<camera_metadata_rational_t>>>
+ std::map<
+ camera_metadata_tag_t,
+ std::variant<std::vector<int64_t>, std::vector<int32_t>,
+ std::vector<uint8_t>, std::vector<float>,
+ std::vector<camera_metadata_rational_t>, std::vector<double>>>
mEntryMap;
// Extend metadata with ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS.
bool mExtendWithAvailableCharacteristicsKeys = false;
@@ -434,6 +441,10 @@
std::optional<int32_t> getJpegQuality(
const aidl::android::hardware::camera::device::CameraMetadata& metadata);
+// Return JPEG_ORIENTATION from metadata, or 0 if the key is not present
+int32_t getJpegOrientation(
+ const aidl::android::hardware::camera::device::CameraMetadata& metadata);
+
// Returns JPEG_THUMBNAIL_SIZE from metadata, or nullopt if the key is not present.
std::optional<Resolution> getJpegThumbnailSize(
const aidl::android::hardware::camera::device::CameraMetadata& metadata);
@@ -453,6 +464,11 @@
std::optional<camera_metadata_enum_android_control_capture_intent> getCaptureIntent(
const aidl::android::hardware::camera::device::CameraMetadata& metadata);
+// Returns ANDROID_JPEG_GPS_COORDINATES in a GpsCoordinate object or nullopt if
+// the key is not present.
+std::optional<GpsCoordinates> getGpsCoordinates(
+ const aidl::android::hardware::camera::device::CameraMetadata& metadata);
+
} // namespace virtualcamera
} // namespace companion
} // namespace android
diff --git a/services/camera/virtualcamera/util/Util.h b/services/camera/virtualcamera/util/Util.h
index f08eb1c..291e105 100644
--- a/services/camera/virtualcamera/util/Util.h
+++ b/services/camera/virtualcamera/util/Util.h
@@ -20,6 +20,8 @@
#include <cmath>
#include <cstdint>
#include <memory>
+#include <optional>
+#include <string>
#include "aidl/android/companion/virtualcamera/Format.h"
#include "aidl/android/hardware/camera/common/Status.h"
@@ -152,6 +154,16 @@
}
};
+struct GpsCoordinates {
+ // Represented by a double[] in metadata with index 0 for
+ // latitude and index 1 for longitude, 2 for altitude.
+ double_t latitude;
+ double_t longitude;
+ double_t altitude;
+ std::optional<int64_t> timestamp;
+ std::string provider;
+};
+
inline bool isApproximatellySameAspectRatio(const Resolution r1,
const Resolution r2) {
static constexpr float kAspectRatioEpsilon = 0.05;