[{"id":12222,"web_url":"https://patchwork.libcamera.org/comment/12222/","msgid":"<20200828183346.GQ10034@pendragon.ideasonboard.com>","date":"2020-08-28T18:33:46","subject":"Re: [libcamera-devel] [PATCH v4 2/2] android: jpeg: Support an\n\tinitial set of EXIF metadata tags","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Umang,\n\nThank you for the patch.\n\nOn Fri, Aug 28, 2020 at 06:57:40AM +0000, Umang Jain wrote:\n> Create a Exif object with various metadata tags set, just before\n> the encoder starts to encode the frame. The object is passed\n> directly as libcamera::Span<> to make sure EXIF tags can be set\n> in a single place i.e. in CameraDevice and the encoder only has\n> the job to write the data in the final output.\n> \n> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> Signed-off-by: Umang Jain <email@uajain.com>\n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> ---\n>  src/android/camera_device.cpp        | 18 +++++++-\n>  src/android/jpeg/encoder.h           |  3 +-\n>  src/android/jpeg/encoder_libjpeg.cpp |  9 +++-\n>  src/android/jpeg/encoder_libjpeg.h   |  3 +-\n>  src/android/jpeg/exif.cpp            | 62 ++++++++++++++++++++++++++++\n>  src/android/jpeg/exif.h              |  9 ++++\n>  6 files changed, 100 insertions(+), 4 deletions(-)\n> \n> diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp\n> index de6f86f..54ca9c6 100644\n> --- a/src/android/camera_device.cpp\n> +++ b/src/android/camera_device.cpp\n> @@ -24,6 +24,7 @@\n>  #include \"system/graphics.h\"\n>  \n>  #include \"jpeg/encoder_libjpeg.h\"\n> +#include \"jpeg/exif.h\"\n>  \n>  using namespace libcamera;\n>  \n> @@ -1434,7 +1435,22 @@ void CameraDevice::requestComplete(Request *request)\n>  \t\t\tcontinue;\n>  \t\t}\n>  \n> -\t\tint jpeg_size = encoder->encode(buffer, mapped.maps()[0]);\n> +\t\t/* Set EXIF metadata for various tags. */\n> +\t\tExif exif;\n> +\t\t/* \\todo Set Make and Model from external vendor tags. */\n> +\t\texif.setMake(\"libcamera\");\n> +\t\texif.setModel(\"cameraModel\");\n> +\t\texif.setOrientation(orientation_);\n> +\t\texif.setSize(cameraStream->size);\n> +\t\t/*\n> +\t\t * We set the frame's EXIF timestamp as the time of encode.\n> +\t\t * Since the precision we need for EXIF timestamp is only one\n> +\t\t * second, it is good enough.\n> +\t\t */\n> +\t\texif.setTimestamp(std::time(nullptr));\n> +\t\texif.generate();\n> +\n> +\t\tint jpeg_size = encoder->encode(buffer, mapped.maps()[0], exif.data());\n>  \t\tif (jpeg_size < 0) {\n>  \t\t\tLOG(HAL, Error) << \"Failed to encode stream image\";\n>  \t\t\tstatus = CAMERA3_BUFFER_STATUS_ERROR;\n> diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h\n> index f9eb88e..cf26d67 100644\n> --- a/src/android/jpeg/encoder.h\n> +++ b/src/android/jpeg/encoder.h\n> @@ -18,7 +18,8 @@ public:\n>  \n>  \tvirtual int configure(const libcamera::StreamConfiguration &cfg) = 0;\n>  \tvirtual int encode(const libcamera::FrameBuffer *source,\n> -\t\t\t   const libcamera::Span<uint8_t> &destination) = 0;\n> +\t\t\t   const libcamera::Span<uint8_t> &destination,\n> +\t\t\t   const libcamera::Span<const uint8_t> &exifData) = 0;\n>  };\n>  \n>  #endif /* __ANDROID_JPEG_ENCODER_H__ */\n> diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp\n> index 977538c..510613c 100644\n> --- a/src/android/jpeg/encoder_libjpeg.cpp\n> +++ b/src/android/jpeg/encoder_libjpeg.cpp\n> @@ -180,7 +180,8 @@ void EncoderLibJpeg::compressNV(const libcamera::MappedBuffer *frame)\n>  }\n>  \n>  int EncoderLibJpeg::encode(const FrameBuffer *source,\n> -\t\t\t   const libcamera::Span<uint8_t> &dest)\n> +\t\t\t   const libcamera::Span<uint8_t> &dest,\n> +\t\t\t   const libcamera::Span<const uint8_t> &exifData)\n>  {\n>  \tMappedFrameBuffer frame(source, PROT_READ);\n>  \tif (!frame.isValid()) {\n> @@ -204,6 +205,12 @@ int EncoderLibJpeg::encode(const FrameBuffer *source,\n>  \n>  \tjpeg_start_compress(&compress_, TRUE);\n>  \n> +\tif (exifData.size())\n> +\t\t/* Store Exif data in the JPEG_APP1 data block. */\n> +\t\tjpeg_write_marker(&compress_, JPEG_APP0 + 1,\n> +\t\t\t\t  static_cast<const JOCTET *>(exifData.data()),\n> +\t\t\t\t  exifData.size());\n> +\n>  \tLOG(JPEG, Debug) << \"JPEG Encode Starting:\" << compress_.image_width\n>  \t\t\t << \"x\" << compress_.image_height;\n>  \n> diff --git a/src/android/jpeg/encoder_libjpeg.h b/src/android/jpeg/encoder_libjpeg.h\n> index aed081a..1e8df05 100644\n> --- a/src/android/jpeg/encoder_libjpeg.h\n> +++ b/src/android/jpeg/encoder_libjpeg.h\n> @@ -22,7 +22,8 @@ public:\n>  \n>  \tint configure(const libcamera::StreamConfiguration &cfg) override;\n>  \tint encode(const libcamera::FrameBuffer *source,\n> -\t\t   const libcamera::Span<uint8_t> &destination) override;\n> +\t\t   const libcamera::Span<uint8_t> &destination,\n> +\t\t   const libcamera::Span<const uint8_t> &exifData) override;\n>  \n>  private:\n>  \tvoid compressRGB(const libcamera::MappedBuffer *frame);\n> diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp\n> index b49b538..3fbb117 100644\n> --- a/src/android/jpeg/exif.cpp\n> +++ b/src/android/jpeg/exif.cpp\n> @@ -157,6 +157,68 @@ int Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::stri\n>  \treturn 0;\n>  }\n>  \n> +int Exif::setMake(const std::string &make)\n> +{\n> +\treturn setString(EXIF_IFD_0, EXIF_TAG_MAKE, EXIF_FORMAT_ASCII, make);\n> +}\n> +\n> +int Exif::setModel(const std::string &model)\n> +{\n> +\treturn setString(EXIF_IFD_0, EXIF_TAG_MODEL, EXIF_FORMAT_ASCII, model);\n> +}\n> +\n> +int Exif::setSize(Size size)\n\nconst Size &size ?\n\n> +{\n> +\tsetShort(EXIF_IFD_0, EXIF_TAG_IMAGE_LENGTH, size.height);\n\nThe EXIF specification says that EXIF_TAG_IMAGE_LENGTH and\nEXIF_TAG_IMAGE_WIDTH should not be set for JPEG compressed images.\nX_DIMENSION and Y_DIMENSION are fine.\n\n> +\tsetLong(EXIF_IFD_EXIF, EXIF_TAG_PIXEL_Y_DIMENSION, size.height);\n> +\n> +\tsetShort(EXIF_IFD_0, EXIF_TAG_IMAGE_WIDTH, size.width);\n> +\tsetLong(EXIF_IFD_EXIF, EXIF_TAG_PIXEL_X_DIMENSION, size.width);\n> +\n> +\treturn 0;\n> +}\n> +\n> +int Exif::setTimestamp(const time_t timestamp)\n> +{\n> +\tchar str[20];\n> +\tstd::strftime(str, sizeof(str), \"%Y:%m:%d %H:%M:%S\",\n> +\t\t      std::localtime(&timestamp));\n> +\tstd::string ts(str);\n> +\tint ret = -1;\n> +\n> +\tret = setString(EXIF_IFD_0, EXIF_TAG_DATE_TIME, EXIF_FORMAT_ASCII, ts);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tret = setString(EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_ORIGINAL, EXIF_FORMAT_ASCII, ts);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tret = setString(EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_DIGITIZED, EXIF_FORMAT_ASCII, ts);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n\nThis makes me think that skipping error handling and relying on valid_\nwould be a good idea :-)\n\nDoesn't have to be part of this series, but adding the timezone would be\ngreat (EXIF_TAG_TIME_ZONE_OFFSET).\n\n> +\n> +\treturn ret;\n> +}\n> +\n> +int Exif::setOrientation(int orientation)\n> +{\n> +\tint value = 1;\n> +\tswitch (orientation) {\n> +\tcase 90:\n> +\t\tvalue = 6;\n> +\t\tbreak;\n> +\tcase 180:\n> +\t\tvalue = 3;\n> +\t\tbreak;\n> +\tcase 270:\n> +\t\tvalue = 8;\n> +\t\tbreak;\n> +\t}\n> +\n> +\treturn setShort(EXIF_IFD_0, EXIF_TAG_ORIENTATION, value);\n> +}\n> +\n>  int Exif::generate()\n>  {\n>  \tif (exifData_) {\n> diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h\n> index 6c10f94..f15afb9 100644\n> --- a/src/android/jpeg/exif.h\n> +++ b/src/android/jpeg/exif.h\n> @@ -9,8 +9,10 @@\n>  \n>  #include <libexif/exif-data.h>\n>  \n> +#include <libcamera/geometry.h>\n>  #include <libcamera/span.h>\n>  \n> +#include <ctime>\n>  #include <string>\n\nI missed this on 1/2, but we usually put the C/C++ headers first.\n\n>  \n>  class Exif\n> @@ -19,6 +21,13 @@ public:\n>  \tExif();\n>  \t~Exif();\n>  \n> +\tint setMake(const std::string &make);\n> +\tint setModel(const std::string &model);\n> +\n> +\tint setOrientation(int orientation);\n> +\tint setSize(libcamera::Size size);\n> +\tint setTimestamp(const time_t timestamp);\n\nNo need for const when passing by value :-)\n\n> +\n>  \tlibcamera::Span<const uint8_t> data() const { return { exifData_, size_ }; }\n>  \tint generate();\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 3CA1CBF019\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 28 Aug 2020 18:34:09 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A059D62912;\n\tFri, 28 Aug 2020 20:34:08 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2681960379\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 28 Aug 2020 20:34:07 +0200 (CEST)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id ABDC9303;\n\tFri, 28 Aug 2020 20:34:06 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"J+rro0Vq\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1598639646;\n\tbh=Ab8kiGI84a5+UY3fXUfjqmQHHz7eksE8kmMDTiRiYZk=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=J+rro0VqGVtnQ+FjQxfbPdyIWBZAr45b1JpMUlQiyiEF+aKC2kViJ4pMwdmG9aaOU\n\tLJkCGNa0YIFHbp/qmROuUoHcuyumwe35GjSgx86Dh/GxB103LLJPhGviD7/7UsktJK\n\tyqJUf2VpYgPG4M/nrc6cgH4djwvNmNYKlWhKUuCk=","Date":"Fri, 28 Aug 2020 21:33:46 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Umang Jain <email@uajain.com>","Message-ID":"<20200828183346.GQ10034@pendragon.ideasonboard.com>","References":"<20200828065727.9909-1-email@uajain.com>\n\t<20200828065727.9909-3-email@uajain.com>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20200828065727.9909-3-email@uajain.com>","Subject":"Re: [libcamera-devel] [PATCH v4 2/2] android: jpeg: Support an\n\tinitial set of EXIF metadata tags","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]