[libcamera-devel] [PATCH v2 2/2] android: jpeg: Support a initial set of EXIF metadata tags

Umang Jain email at uajain.com
Tue Aug 25 22:10:53 CEST 2020


From: Kieran Bingham <kieran.bingham at ideasonboard.com>

Add a Exif data placeholder inside CameraStream, to populate it
accordingly to the stream properties (size, orientation etc).
Static EXIF properties (such as make, model, size, orientation)
can be configured once when the stream is being configured.
Per-frame related metadata (such as timestamp) needs to set
just before encoding preferably in CameraDevice::requestComplete().

Signed-off-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
Signed-off-by: Umang Jain <email at uajain.com>
---
 src/android/camera_device.cpp        | 22 +++++++++-
 src/android/camera_device.h          |  2 +
 src/android/jpeg/encoder.h           |  5 ++-
 src/android/jpeg/encoder_libjpeg.cpp |  9 +++-
 src/android/jpeg/encoder_libjpeg.h   |  3 +-
 src/android/jpeg/exif.cpp            | 66 ++++++++++++++++++++++++++++
 src/android/jpeg/exif.h              |  9 ++++
 7 files changed, 112 insertions(+), 4 deletions(-)

diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp
index de6f86f..8789a9e 100644
--- a/src/android/camera_device.cpp
+++ b/src/android/camera_device.cpp
@@ -1245,6 +1245,17 @@ int CameraDevice::configureStreams(camera3_stream_configuration_t *stream_list)
 					<< "Failed to configure encoder";
 				return ret;
 			}
+
+			/*
+			 * Set EXIF metadata for various tags.
+			 * \todo Discuss setMake String, maybe vendor ID?
+			 * KB suggested to leave it to "libcamera" for now.
+			 * setModel should use the 'model' property of the camera.
+			 */
+			cameraStream->exif.setMake("libcamera");
+			cameraStream->exif.setModel("cameraModel");
+			cameraStream->exif.setOrientation(orientation_);
+			cameraStream->exif.setSize(cfg.size);
 		}
 	}
 
@@ -1434,7 +1445,16 @@ void CameraDevice::requestComplete(Request *request)
 			continue;
 		}
 
-		int jpeg_size = encoder->encode(buffer, mapped.maps()[0]);
+		/*
+		 * We set the frame's EXIF timestamp as the time of encode. Since the
+		 * we need for EXIF is only one second, it is good enough.
+		 */
+		struct timespec tp;
+		clock_gettime(CLOCK_REALTIME, &tp);
+		cameraStream->exif.setTimestamp(tp.tv_sec);
+		Span<uint8_t> exif_data = cameraStream->exif.generate();
+
+		int jpeg_size = encoder->encode(buffer, mapped.maps()[0], exif_data);
 		if (jpeg_size < 0) {
 			LOG(HAL, Error) << "Failed to encode stream image";
 			status = CAMERA3_BUFFER_STATUS_ERROR;
diff --git a/src/android/camera_device.h b/src/android/camera_device.h
index 3934f19..3e8a119 100644
--- a/src/android/camera_device.h
+++ b/src/android/camera_device.h
@@ -42,6 +42,8 @@ struct CameraStream {
 	libcamera::Size size;
 
 	Encoder *jpeg;
+
+	Exif exif;
 };
 
 class CameraDevice : protected libcamera::Loggable
diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h
index f9eb88e..969f291 100644
--- a/src/android/jpeg/encoder.h
+++ b/src/android/jpeg/encoder.h
@@ -7,6 +7,8 @@
 #ifndef __ANDROID_JPEG_ENCODER_H__
 #define __ANDROID_JPEG_ENCODER_H__
 
+#include "exif.h"
+
 #include <libcamera/buffer.h>
 #include <libcamera/span.h>
 #include <libcamera/stream.h>
@@ -18,7 +20,8 @@ public:
 
 	virtual int configure(const libcamera::StreamConfiguration &cfg) = 0;
 	virtual int encode(const libcamera::FrameBuffer *source,
-			   const libcamera::Span<uint8_t> &destination) = 0;
+			   const libcamera::Span<uint8_t> &destination,
+			   const libcamera::Span<uint8_t> &exif_data) = 0;
 };
 
 #endif /* __ANDROID_JPEG_ENCODER_H__ */
diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp
index 977538c..87464a4 100644
--- a/src/android/jpeg/encoder_libjpeg.cpp
+++ b/src/android/jpeg/encoder_libjpeg.cpp
@@ -180,7 +180,8 @@ void EncoderLibJpeg::compressNV(const libcamera::MappedBuffer *frame)
 }
 
 int EncoderLibJpeg::encode(const FrameBuffer *source,
-			   const libcamera::Span<uint8_t> &dest)
+			   const libcamera::Span<uint8_t> &dest,
+			   const libcamera::Span<uint8_t> &exif_data)
 {
 	MappedFrameBuffer frame(source, PROT_READ);
 	if (!frame.isValid()) {
@@ -214,5 +215,11 @@ int EncoderLibJpeg::encode(const FrameBuffer *source,
 
 	jpeg_finish_compress(&compress_);
 
+	if (exif_data.size())
+		/* Store Exif data in the JPEG_APP1 data block. */
+		jpeg_write_marker(&compress_, JPEG_APP0 + 1,
+				  static_cast<const JOCTET *>(exif_data.data()),
+				  exif_data.size());
+
 	return size;
 }
diff --git a/src/android/jpeg/encoder_libjpeg.h b/src/android/jpeg/encoder_libjpeg.h
index aed081a..88bc6e0 100644
--- a/src/android/jpeg/encoder_libjpeg.h
+++ b/src/android/jpeg/encoder_libjpeg.h
@@ -22,7 +22,8 @@ public:
 
 	int configure(const libcamera::StreamConfiguration &cfg) override;
 	int encode(const libcamera::FrameBuffer *source,
-		   const libcamera::Span<uint8_t> &destination) override;
+		   const libcamera::Span<uint8_t> &destination,
+		   const libcamera::Span<uint8_t> &exif_data) override;
 
 private:
 	void compressRGB(const libcamera::MappedBuffer *frame);
diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp
index f6a9f5c..d2ef14e 100644
--- a/src/android/jpeg/exif.cpp
+++ b/src/android/jpeg/exif.cpp
@@ -160,6 +160,72 @@ int Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::stri
 	return 0;
 }
 
+int Exif::setMake(const std::string &make)
+{
+	return setString(EXIF_IFD_0, EXIF_TAG_MAKE, EXIF_FORMAT_ASCII, make);
+}
+
+int Exif::setModel(const std::string &model)
+{
+	return setString(EXIF_IFD_0, EXIF_TAG_MODEL, EXIF_FORMAT_ASCII, model);
+}
+
+int Exif::setSize(Size size)
+{
+	setShort(EXIF_IFD_0, EXIF_TAG_IMAGE_LENGTH, size.height);
+	setLong(EXIF_IFD_EXIF, EXIF_TAG_PIXEL_Y_DIMENSION, size.height);
+
+	setShort(EXIF_IFD_0, EXIF_TAG_IMAGE_WIDTH, size.width);
+	setLong(EXIF_IFD_EXIF, EXIF_TAG_PIXEL_X_DIMENSION, size.width);
+
+	return 0;
+}
+
+int Exif::setTimestamp(const time_t timestamp)
+{
+	char str[40];
+	size_t len = std::strftime(str, sizeof(str), "%c %Z",
+				   std::localtime(&timestamp));
+	std::string ts(str);
+	int ret = -1;
+
+	if (len > 0) {
+		ret = setString(EXIF_IFD_0, EXIF_TAG_DATE_TIME, EXIF_FORMAT_ASCII, ts);
+		if (ret < 0)
+			return ret;
+
+		ret = setString(EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_ORIGINAL, EXIF_FORMAT_ASCII, ts);
+		if (ret < 0)
+			return ret;
+
+		ret = setString(EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_DIGITIZED, EXIF_FORMAT_ASCII, ts);
+		if (ret < 0)
+			return ret;
+	}
+
+	return ret;
+}
+
+
+int Exif::setOrientation(int orientation)
+{
+	/* Orientation's tag value computed similar to Chrome HAL. */
+	int value = 1;
+	switch (orientation) {
+	case 90:
+		value = 6;
+		break;
+	case 180:
+		value = 3;
+		break;
+	case 270:
+		value = 8;
+		break;
+	}
+
+	return setShort(EXIF_IFD_0, EXIF_TAG_ORIENTATION, value);
+}
+
 Span<uint8_t> Exif::generate()
 {
 	if (exif_data_) {
diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h
index 7df83c7..6cca578 100644
--- a/src/android/jpeg/exif.h
+++ b/src/android/jpeg/exif.h
@@ -9,8 +9,10 @@
 
 #include <libexif/exif-data.h>
 
+#include <libcamera/geometry.h>
 #include <libcamera/span.h>
 
+#include <ctime>
 #include <string>
 
 class Exif
@@ -24,6 +26,13 @@ public:
 	int setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::string &item);
 	int setRational(ExifIfd ifd, ExifTag tag, uint32_t numerator, uint32_t denominator);
 
+	int setMake(const std::string &make);
+	int setModel(const std::string &model);
+
+	int setOrientation(int orientation);
+	int setSize(libcamera::Size size);
+	int setTimestamp(const time_t timestamp);
+
 	libcamera::Span<uint8_t> generate();
 	unsigned char *data() const { return exif_data_; }
 	unsigned int size() const { return size_; }
-- 
2.25.1



More information about the libcamera-devel mailing list