From patchwork Tue Aug 25 20:10:53 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Umang Jain X-Patchwork-Id: 9383 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 5D123BD87E for ; Tue, 25 Aug 2020 20:10:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 29CD162882; Tue, 25 Aug 2020 22:10:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=uajain.com header.i=@uajain.com header.b="Wp+ZF9vO"; dkim-atps=neutral Received: from o1.f.az.sendgrid.net (o1.f.az.sendgrid.net [208.117.55.132]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 794336037C for ; Tue, 25 Aug 2020 22:10:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=uajain.com; h=from:subject:mime-version:to:cc:content-transfer-encoding: content-type; s=s1; bh=rGAUH7EYnfYdfa2hbFd6w7VGRyGBf9q3ChbWN7Qr+vM=; b=Wp+ZF9vOAQsppBrjRS6u/a4Jj2bI2lJzQbSrm20KaSDTop4VzLnX2gsbYZrgIEeBvUnL VHAByih52XFnKPUlkm3xEdr9j8UyX3gYeHPSJ7Vt6bt0bRZirmd4uKOow/mEkGMdaoJny3 8kNy1/cNz6g8ergwG8d15jm2TMJ/rlSW4= Received: by filterdrecv-p3iad2-cbd967498-57mkd with SMTP id filterdrecv-p3iad2-cbd967498-57mkd-20-5F45704D-A 2020-08-25 20:10:53.258262099 +0000 UTC m=+2436.898476489 Received: from mail.uajain.com (unknown) by ismtpd0002p1hnd1.sendgrid.net (SG) with ESMTP id qR2JujhPS86VIwUg2eFLUQ Tue, 25 Aug 2020 20:10:52.756 +0000 (UTC) From: Umang Jain Date: Tue, 25 Aug 2020 20:10:53 +0000 (UTC) Message-Id: <20200825201048.13608-1-email@uajain.com> Mime-Version: 1.0 X-SG-EID: 1Q40EQ7YGir8a9gjSIAdTjhngY657NMk9ckeo4dbHZDiOpywc/L3L9rFqlwE4KPc4om/Oij3lMEQBxKPi/30Oy8rTdfU1FlVAi+/uEVNk+vYA3+jQRD58kreR/qA6YDT9CdrnbX9bygO6jjBE6KxqLMOl16UaRpzZFudm6N2zSVFEOHYgl2rbu4eNmr2p94Xm4VHDXMz2UdEY2Tt0UZK9eLqaaRH9bHTj7ENl8YtvR6GDbOodboE+HXbkZUjDcgRUAArBamtJyBTheXVMcbMXw== To: libcamera-devel@lists.libcamera.org Subject: [libcamera-devel] [PATCH v2 2/2] android: jpeg: Support a initial set of EXIF metadata tags X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Kieran Bingham 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 Signed-off-by: Umang Jain --- 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 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 #include #include @@ -18,7 +20,8 @@ public: virtual int configure(const libcamera::StreamConfiguration &cfg) = 0; virtual int encode(const libcamera::FrameBuffer *source, - const libcamera::Span &destination) = 0; + const libcamera::Span &destination, + const libcamera::Span &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 &dest) + const libcamera::Span &dest, + const libcamera::Span &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(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 &destination) override; + const libcamera::Span &destination, + const libcamera::Span &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(×tamp)); + 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 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 +#include #include +#include #include 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 generate(); unsigned char *data() const { return exif_data_; } unsigned int size() const { return size_; }