From patchwork Tue Aug 25 20:10:40 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Umang Jain X-Patchwork-Id: 9382 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 C2801BD87E for ; Tue, 25 Aug 2020 20:10:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8F3B8628CD; Tue, 25 Aug 2020 22:10:42 +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="K9SqdbXJ"; 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 86B096037C for ; Tue, 25 Aug 2020 22:10:41 +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=OhuNKkqUEvX9FzUuIh7SR0Wks/BFouW4fm9gZcOJWg4=; b=K9SqdbXJjr0hshKwplRPsgXAwCWB0EOKT5ebTEww3BZRqim18Q53b4zXcKXo2fXTxKZV dWnBM0K/Sj/mEfT1bHe+aE/HTtS7nDzSxW/Wdn/G31+wqqplHmZj3e+VnGyxOXquEkQQKQ 88tT39dkeclVjajEuCWGdnPXdGl+MO2/w= Received: by filterdrecv-p3mdw1-5b7978bb98-6v6r5 with SMTP id filterdrecv-p3mdw1-5b7978bb98-6v6r5-20-5F45703F-6B 2020-08-25 20:10:40.210581051 +0000 UTC m=+2405.590399306 Received: from mail.uajain.com (unknown) by ismtpd0006p1maa1.sendgrid.net (SG) with ESMTP id xq--UUPxQyW0VKvCYvPv_w Tue, 25 Aug 2020 20:10:39.798 +0000 (UTC) From: Umang Jain Date: Tue, 25 Aug 2020 20:10:40 +0000 (UTC) Message-Id: <20200825201035.13553-1-email@uajain.com> Mime-Version: 1.0 X-SG-EID: 1Q40EQ7YGir8a9gjSIAdTjhngY657NMk9ckeo4dbHZDiOpywc/L3L9rFqlwE4KPcw8DXVBj03b84G8OPZYg80wMKQbpvPfpEOZNY4uwW0elLPjQnFV/+9q+UQgnnbh0oo+2YMdSlQWSyt//1t0t7Mla8fWzM+kbDP71pudGVI3jN6K91Z8EKqZdPNDGTEvqoOPPsdUlFJ32gFHfyWD+xIa6YsfNoCPFkvmfz9akVPEm3/xO/eB581EghPckcB1xgWDHQSBqG45yryOlTkzWPFw== To: libcamera-devel@lists.libcamera.org Subject: [libcamera-devel] [PATCH v2 1/2] libcamera: android: Add EXIF infrastructure 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 Provide helper classes to utilise the libexif interfaces and link against libexif to support tag additions when creating JPEG images. Signed-off-by: Kieran Bingham Signed-off-by: Umang Jain --- src/android/jpeg/exif.cpp | 176 ++++++++++++++++++++++++++++++++++++++ src/android/jpeg/exif.h | 45 ++++++++++ src/android/meson.build | 2 + 3 files changed, 223 insertions(+) create mode 100644 src/android/jpeg/exif.cpp create mode 100644 src/android/jpeg/exif.h diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp new file mode 100644 index 0000000..f6a9f5c --- /dev/null +++ b/src/android/jpeg/exif.cpp @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * exif.cpp - EXIF tag creation and parser using libexif + */ + +#include "exif.h" + +#include "libcamera/internal/log.h" + +using namespace libcamera; + +LOG_DEFINE_CATEGORY(EXIF) + +Exif::Exif() + : valid_(false), exif_data_(0), size_(0) +{ + /* Create an ExifMem allocator to construct entries. */ + mem_ = exif_mem_new_default(); + if (!mem_) { + LOG(EXIF, Fatal) << "Failed to allocate ExifMem Allocator"; + return; + } + + data_ = exif_data_new_mem(mem_); + if (!data_) { + LOG(EXIF, Fatal) << "Failed to allocate an ExifData structure"; + return; + } + + valid_ = true; + + exif_data_set_option(data_, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION); + exif_data_set_data_type(data_, EXIF_DATA_TYPE_COMPRESSED); + + /* + * Big-Endian: EXIF_BYTE_ORDER_MOTOROLA + * Little Endian: EXIF_BYTE_ORDER_INTEL + */ + exif_data_set_byte_order(data_, EXIF_BYTE_ORDER_INTEL); + + /* Create the mandatory EXIF fields with default data */ + exif_data_fix(data_); +} + +Exif::~Exif() +{ + if (exif_data_) + free(exif_data_); + + if (data_) + exif_data_unref(data_); + + if (mem_) + exif_mem_unref(mem_); +} + +ExifEntry *Exif::createEntry(ExifIfd ifd, ExifTag tag) +{ + ExifContent *content = data_->ifd[ifd]; + ExifEntry *entry = exif_content_get_entry(content, tag); + + if (entry) { + exif_entry_ref(entry); + return entry; + } + + entry = exif_entry_new_mem(mem_); + if (!entry) { + LOG(EXIF, Fatal) << "Failed to allocated new entry"; + return nullptr; + } + + entry->tag = tag; + + exif_content_add_entry(content, entry); + exif_entry_initialize(entry, tag); + + return entry; +} + +ExifEntry *Exif::createEntry(ExifIfd ifd, ExifTag tag, ExifFormat format, + uint64_t components, unsigned int size) +{ + ExifContent *content = data_->ifd[ifd]; + + /* Replace any existing entry with the same tag. */ + ExifEntry *existing = exif_content_get_entry(content, tag); + exif_content_remove_entry(content, existing); + + ExifEntry *entry = exif_entry_new_mem(mem_); + if (!entry) { + LOG(EXIF, Fatal) << "Failed to allocated new entry"; + return nullptr; + } + + void *buffer = exif_mem_alloc(mem_, size); + if (!buffer) { + LOG(EXIF, Fatal) << "Failed to allocate buffer for variable entry"; + exif_mem_unref(mem_); + return nullptr; + } + + entry->data = static_cast(buffer); + entry->components = components; + entry->format = format; + entry->size = size; + entry->tag = tag; + + exif_content_add_entry(content, entry); + + return entry; +} + +int Exif::setShort(ExifIfd ifd, ExifTag tag, uint16_t item) +{ + ExifEntry *entry = createEntry(ifd, tag); + + exif_set_short(entry->data, EXIF_BYTE_ORDER_INTEL, item); + exif_entry_unref(entry); + + return 0; +} + +int Exif::setLong(ExifIfd ifd, ExifTag tag, uint32_t item) +{ + ExifEntry *entry = createEntry(ifd, tag); + + exif_set_long(entry->data, EXIF_BYTE_ORDER_INTEL, item); + exif_entry_unref(entry); + + return 0; +} + +int Exif::setRational(ExifIfd ifd, ExifTag tag, uint32_t numerator, uint32_t denominator) +{ + ExifEntry *entry = createEntry(ifd, tag); + ExifRational item{ numerator, denominator }; + + exif_set_rational(entry->data, EXIF_BYTE_ORDER_INTEL, item); + exif_entry_unref(entry); + + return 0; +} + +int Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::string &item) +{ + size_t length = item.length(); + + ExifEntry *entry = createEntry(ifd, tag, format, length, length); + if (!entry) { + LOG(EXIF, Error) << "Failed to add tag: " << tag; + return -ENOMEM; + } + + memcpy(entry->data, item.c_str(), length); + exif_entry_unref(entry); + + return 0; +} + +Span Exif::generate() +{ + if (exif_data_) { + free(exif_data_); + exif_data_ = nullptr; + } + + exif_data_save_data(data_, &exif_data_, &size_); + + LOG(EXIF, Debug) << "Created EXIF instance (" << size_ << " bytes)"; + + return { exif_data_, size_ }; +} + diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h new file mode 100644 index 0000000..7df83c7 --- /dev/null +++ b/src/android/jpeg/exif.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * exif.h - EXIF tag creator and parser using libexif + */ +#ifndef __LIBCAMERA_EXIF_H__ +#define __LIBCAMERA_EXIF_H__ + +#include + +#include + +#include + +class Exif +{ +public: + Exif(); + ~Exif(); + + int setShort(ExifIfd ifd, ExifTag tag, uint16_t item); + int setLong(ExifIfd ifd, ExifTag tag, uint32_t item); + int setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::string &item); + int setRational(ExifIfd ifd, ExifTag tag, uint32_t numerator, uint32_t denominator); + + libcamera::Span generate(); + unsigned char *data() const { return exif_data_; } + unsigned int size() const { return size_; } + +private: + ExifEntry *createEntry(ExifIfd ifd, ExifTag tag); + ExifEntry *createEntry(ExifIfd ifd, ExifTag tag, ExifFormat format, + uint64_t components, unsigned int size); + + bool valid_; + + ExifData *data_; + ExifMem *mem_; + + unsigned char *exif_data_; + unsigned int size_; +}; + +#endif /* __LIBCAMERA_EXIF_H__ */ diff --git a/src/android/meson.build b/src/android/meson.build index f7b81a4..0f49b25 100644 --- a/src/android/meson.build +++ b/src/android/meson.build @@ -7,6 +7,7 @@ android_hal_sources = files([ 'camera_metadata.cpp', 'camera_ops.cpp', 'jpeg/encoder_libjpeg.cpp', + 'jpeg/exif.cpp', ]) android_camera_metadata_sources = files([ @@ -15,6 +16,7 @@ android_camera_metadata_sources = files([ android_deps = [ dependency('libjpeg'), + dependency('libexif'), ] android_camera_metadata = static_library('camera_metadata', 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_; }