From patchwork Thu Jan 14 10:40:30 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10857 X-Patchwork-Delegate: paul.elder@ideasonboard.com 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 9D881BD808 for ; Thu, 14 Jan 2021 10:40:50 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 59B7A680E8; Thu, 14 Jan 2021 11:40:50 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ri8D12IZ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 37C3D680E3 for ; Thu, 14 Jan 2021 11:40:49 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BC85B2B3; Thu, 14 Jan 2021 11:40:47 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1610620848; bh=2rO6jGp2Y1nu6awNfsdYgxsRDJn8UjiYQY07RH1f5Zc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Ri8D12IZDtonTqcr/fAKoRrK3K3MlQCJQxGh1+TDChAIOeU4uoNBE5UZsyrenNWf2 1af/fpmPGxnW9xCSY53oExDonIOZiR0wOtuhbiZ5c6itrFzOErhEcn4lo7emujoVUB DtnYsdptIFu6oe6HlyoCz4dQA6uEm0L9ImcjqCdM= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 14 Jan 2021 19:40:30 +0900 Message-Id: <20210114104035.302968-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210114104035.302968-1-paul.elder@ideasonboard.com> References: <20210114104035.302968-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 1/6] utils: Add function to convert string to UCS-2 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" GPSProcessingMethod and UserComment in EXIF tags can be in Unicode, but are recommended to be in UCS-2. Add a function in utils to help with this. Signed-off-by: Paul Elder --- include/libcamera/internal/utils.h | 2 ++ src/libcamera/utils.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/include/libcamera/internal/utils.h b/include/libcamera/internal/utils.h index f08134af..aa9cc236 100644 --- a/include/libcamera/internal/utils.h +++ b/include/libcamera/internal/utils.h @@ -35,6 +35,8 @@ const char *basename(const char *path); char *secure_getenv(const char *name); std::string dirname(const std::string &path); +std::vector string_to_c16(const std::string &str, bool le); + template std::vector map_keys(const T &map) { diff --git a/src/libcamera/utils.cpp b/src/libcamera/utils.cpp index e90375ae..89cb0f73 100644 --- a/src/libcamera/utils.cpp +++ b/src/libcamera/utils.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include /** @@ -122,6 +123,35 @@ std::string dirname(const std::string &path) return path.substr(0, pos + 1); } +/** + * \brief Convert string to byte array of UCS-2 + * \param[in] str String to convert + * \param[in] le Little-endian (false for Big-endian) + * + * \return Byte array of UCS-2 representation of \a str, without null-terminator + */ +std::vector string_to_c16(const std::string &str, bool le) +{ + std::mbstate_t state{}; + char16_t c16; + const char *ptr = &str[0], *end = &str[0] + str.size(); + + std::vector ret; + while (size_t rc = mbrtoc16(&c16, ptr, end - ptr + 1, &state)) { + if (rc == static_cast(-2) || + rc == static_cast(-1)) + break; + + ret.push_back(le ? (c16 & 0xff) : ((c16 >> 8) & 0xff)); + ret.push_back(le ? ((c16 >> 8) & 0xff) : (c16 & 0xff)); + + if (rc > 0) + ptr += rc; + } + + return ret; +} + /** * \fn std::vector map_keys(const T &map) * \brief Retrieve the keys of a std::map<> From patchwork Thu Jan 14 10:40:31 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10858 X-Patchwork-Delegate: paul.elder@ideasonboard.com 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 E59B8BD808 for ; Thu, 14 Jan 2021 10:40:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AEAAC680ED; Thu, 14 Jan 2021 11:40:52 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nphq3Txh"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 382A9680DF for ; Thu, 14 Jan 2021 11:40:51 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 94DF08D7; Thu, 14 Jan 2021 11:40:49 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1610620850; bh=O3ZevUqiO+qWSjpkBzvMsXGCq+YhyK2zeRIne6/fq3g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nphq3TxhzNqjYE4LHV0qDN8X3w8IDcxSGNnWQCHss/GDKcNSkoQYShsuW1g+RcG+Z A6boARpfp7DxVfkJEtBnKUGOAB6W99qYhDraYIueK86qNg+8ZiD7FmIitmVp9nb2ZW VzKwY71iptm3dAPE/cRkHPBMXwqFyvVGxkFEaUao= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 14 Jan 2021 19:40:31 +0900 Message-Id: <20210114104035.302968-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210114104035.302968-1-paul.elder@ideasonboard.com> References: <20210114104035.302968-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 2/6] android: CameraMetadata: Add copy constructor and getEntry 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" Add a copy constructor to CameraMetadata, as well as a constructor from camera_metadata_t. Also add a function getEntry to allow getting metadata entries from CameraMetadata. This allows us to use CameraMetadata for reading form camera_metadata_t. Signed-off-by: Paul Elder --- src/android/camera_metadata.cpp | 23 +++++++++++++++++++++++ src/android/camera_metadata.h | 3 +++ 2 files changed, 26 insertions(+) diff --git a/src/android/camera_metadata.cpp b/src/android/camera_metadata.cpp index edea48fe..57ac6009 100644 --- a/src/android/camera_metadata.cpp +++ b/src/android/camera_metadata.cpp @@ -19,12 +19,35 @@ CameraMetadata::CameraMetadata(size_t entryCapacity, size_t dataCapacity) valid_ = metadata_ != nullptr; } +CameraMetadata::CameraMetadata(const camera_metadata_t *metadata) +{ + metadata_ = clone_camera_metadata(metadata); + valid_ = metadata_ != nullptr; +} + +CameraMetadata::CameraMetadata(const CameraMetadata &other) +{ + metadata_ = clone_camera_metadata(other.get()); + valid_ = metadata_ != nullptr; +} + CameraMetadata::~CameraMetadata() { if (metadata_) free_camera_metadata(metadata_); } +int CameraMetadata::getEntry(uint32_t tag, camera_metadata_ro_entry_t *entry) const +{ + if (!entry) + return -EINVAL; + + if (find_camera_metadata_ro_entry(metadata_, tag, entry)) + return -EINVAL; + + return 0; +} + bool CameraMetadata::addEntry(uint32_t tag, const void *data, size_t count) { if (!valid_) diff --git a/src/android/camera_metadata.h b/src/android/camera_metadata.h index 9d047b1b..fa1c4983 100644 --- a/src/android/camera_metadata.h +++ b/src/android/camera_metadata.h @@ -15,9 +15,12 @@ class CameraMetadata { public: CameraMetadata(size_t entryCapacity, size_t dataCapacity); + CameraMetadata(const camera_metadata_t *metadata); + CameraMetadata(const CameraMetadata &other); ~CameraMetadata(); bool isValid() const { return valid_; } + int getEntry(uint32_t tag, camera_metadata_ro_entry_t *entry) const; bool addEntry(uint32_t tag, const void *data, size_t data_count); bool updateEntry(uint32_t tag, const void *data, size_t data_count); From patchwork Thu Jan 14 10:40:32 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10859 X-Patchwork-Delegate: paul.elder@ideasonboard.com 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 4E69CBD808 for ; Thu, 14 Jan 2021 10:40:55 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 13908680EE; Thu, 14 Jan 2021 11:40:55 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TqwG8S4f"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CC241680EE for ; Thu, 14 Jan 2021 11:40:52 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7C5572B3; Thu, 14 Jan 2021 11:40:51 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1610620852; bh=YIQQi7xsGwtzDtccnM54ffdg6Vlye9Txn5Xv7DX1SXI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TqwG8S4fYtKsrg7az7rQhgfJLSKbjma2qNsjL7IucaBkqF7xO6GWoWw0hR7WYQyjR v1iEl2CJnM/AWNAHdawzyVsibLFgcpTbeSzl33sA3LnaTAkbeB8ezgGfKBtTYVR+IK pEannnr8ulGddkFGwFG/lFAQ1g82cLjKdGWLWsdU= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 14 Jan 2021 19:40:32 +0900 Message-Id: <20210114104035.302968-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210114104035.302968-1-paul.elder@ideasonboard.com> References: <20210114104035.302968-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 3/6] android: jpeg: exif: Fix setOrientation EXIF values 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" The input to setOrientation is angle clockwise from 12 o'clock, while the EXIF output values were swapped for 90 and 270 degrees. From the EXIF spec: 6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top. 8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom. 6 should be 90 degrees clockwise, while 8 should 270 degrees clockwise. Fix this. Sogned-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- src/android/jpeg/exif.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp index 33b3fa7f..b19cb4cd 100644 --- a/src/android/jpeg/exif.cpp +++ b/src/android/jpeg/exif.cpp @@ -263,13 +263,13 @@ void Exif::setOrientation(int orientation) value = 1; break; case 90: - value = 8; + value = 6; break; case 180: value = 3; break; case 270: - value = 6; + value = 8; break; } From patchwork Thu Jan 14 10:40:33 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10860 X-Patchwork-Delegate: paul.elder@ideasonboard.com 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 B7989BD808 for ; Thu, 14 Jan 2021 10:40:55 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6F96F680F4; Thu, 14 Jan 2021 11:40:55 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="HlriAdxZ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BB9EC680E2 for ; Thu, 14 Jan 2021 11:40:54 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 567278D7; Thu, 14 Jan 2021 11:40:53 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1610620854; bh=zrWLaVR7re63TKBxBOmUkX/284VL9iPXlvYRcV0Iv0M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HlriAdxZLgpNnkc9VAMSKtO+TMNpsqXIAITkDWdl1enwrlUyhF7/jRAn8dG/XIH0n oyPLLh0b/p/hqzm8gxoLcwiaFZsK0F8rc0T64a4ayfTnIa31umknnBuGubT7xYCmMG fkWQJs5ua65mhcb8rzOSCZoTbBvL+TF3YMGW/W2k= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 14 Jan 2021 19:40:33 +0900 Message-Id: <20210114104035.302968-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210114104035.302968-1-paul.elder@ideasonboard.com> References: <20210114104035.302968-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 4/6] android: jpeg: exif: Add functions for setting various values 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" Add functions for setting the following EXIF fields: - GPSDatestamp - GPSTimestamp - GPSLocation - GPSLatitudeRef - GPSLatitude - GPSLongitudeRef - GPSLongitude - GPSAltitudeRef - GPSAltitude - GPSProcessingMethod - FocalLength - ExposureTime - FNumber - ISO - Flash - WhiteBalance - SubsecTime - SubsecTimeOriginal - SubsecTimeDigitized These are in preparation for fixing the following CTS tests: - android.hardware.camera2.cts.StillCaptureTest#testFocalLengths - android.hardware.camera2.cts.StillCaptureTest#testJpegExif Signed-off-by: Paul Elder --- src/android/jpeg/exif.cpp | 187 ++++++++++++++++++++++++++++++++++++++ src/android/jpeg/exif.h | 41 +++++++++ 2 files changed, 228 insertions(+) diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp index b19cb4cd..fde07a63 100644 --- a/src/android/jpeg/exif.cpp +++ b/src/android/jpeg/exif.cpp @@ -7,6 +7,9 @@ #include "exif.h" +#include +#include + #include "libcamera/internal/log.h" #include "libcamera/internal/utils.h" @@ -148,6 +151,16 @@ ExifEntry *Exif::createEntry(ExifIfd ifd, ExifTag tag, ExifFormat format, return entry; } +void Exif::setByte(ExifIfd ifd, ExifTag tag, uint8_t item) +{ + ExifEntry *entry = createEntry(ifd, tag, EXIF_FORMAT_BYTE, 1, 1); + if (!entry) + return; + + memcpy(entry->data, &item, 1); + exif_entry_unref(entry); +} + void Exif::setShort(ExifIfd ifd, ExifTag tag, uint16_t item) { ExifEntry *entry = createEntry(ifd, tag); @@ -178,6 +191,22 @@ void Exif::setRational(ExifIfd ifd, ExifTag tag, ExifRational item) exif_entry_unref(entry); } +/* + * \brief setArray + * \param[in] size sizeof(data[0]) + * \param[in] count Number of elements in data + */ +void Exif::setArray(ExifIfd ifd, ExifTag tag, ExifFormat format, + const void *data, size_t size, size_t count) +{ + ExifEntry *entry = createEntry(ifd, tag, format, count, size * count); + if (!entry) + return; + + memcpy(entry->data, data, size * count); + exif_entry_unref(entry); +} + void Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::string &item) { std::string ascii; @@ -254,6 +283,111 @@ void Exif::setTimestamp(time_t timestamp) } } +void Exif::setGPSTimestamp(ExifIfd ifd, ExifTag tag, const struct tm &tm) +{ + size_t length = 3 * sizeof(ExifRational); + + ExifEntry *entry = createEntry(ifd, tag, EXIF_FORMAT_RATIONAL, 3, length); + if (!entry) + return; + + ExifRational ts[] = { + { static_cast(tm.tm_hour), 1 }, + { static_cast(tm.tm_min), 1 }, + { static_cast(tm.tm_sec), 1 }, + }; + + memcpy(entry->data, ts, length); + exif_entry_unref(entry); +} + +void Exif::setGPSDateTimestamp(time_t timestamp) +{ + struct tm tm; + gmtime_r(×tamp, &tm); + + char str[11]; + strftime(str, sizeof(str), "%Y:%m:%d", &tm); + std::string ts(str); + + setGPSTimestamp(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_TIME_STAMP), tm); + setString(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_DATE_STAMP), EXIF_FORMAT_ASCII, ts); +} + +std::tuple Exif::degreesToDMS(double decimalDegrees) +{ + int degrees = std::trunc(decimalDegrees); + double minutes = std::abs((decimalDegrees - degrees) * 60); + double seconds = (minutes - std::trunc(minutes)) * 60; + + return { degrees, std::trunc(minutes), std::round(seconds) }; +} + +void Exif::setGPSDMS(ExifIfd ifd, ExifTag tag, int deg, int min, int sec) +{ + size_t length = 3 * sizeof(ExifRational); + + ExifEntry *entry = createEntry(ifd, tag, EXIF_FORMAT_RATIONAL, 3, length); + if (!entry) + return; + + ExifRational coords[] = { + { static_cast(deg), 1 }, + { static_cast(min), 1 }, + { static_cast(sec), 1 }, + }; + + memcpy(entry->data, coords, length); + exif_entry_unref(entry); +} + +/* + * \brief Set GPS location (lat, long, alt) from Android JPEG GPS coordinates + * \param[in] coords Pointer to coordinates latitude, longitude, and altitude, + * first two in degrees, the third in meters + */ +void Exif::setGPSLocation(const double *coords) +{ + int latDeg, latMin, latSec, longDeg, longMin, longSec; + + std::tie(latDeg, latMin, latSec) = degreesToDMS(coords[0]); + setString(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LATITUDE_REF), + EXIF_FORMAT_ASCII, latDeg >= 0 ? "N" : "S"); + setGPSDMS(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LATITUDE), + std::abs(latDeg), latMin, latSec); + + std::tie(longDeg, longMin, longSec) = degreesToDMS(coords[1]); + setString(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LONGITUDE_REF), + EXIF_FORMAT_ASCII, longDeg >= 0 ? "E" : "W"); + setGPSDMS(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LATITUDE), + std::abs(longDeg), longMin, longSec); + + setByte(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_ALTITUDE_REF), + coords[2] >= 0 ? 0 : 1); + setRational(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_ALTITUDE), + ExifRational{ static_cast(std::abs(coords[2])), 1 }); +} + +void Exif::setGPSMethod(const std::string &method) +{ + std::vector buf = libcamera::utils::string_to_c16(method, true); + /* Designate that this string is Unicode (UCS-2) */ + buf.insert(buf.begin(), { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00 }); + + /* 8 bytes for character code designation, plus 32 bytes from android */ + unsigned int nullTerm = 39; + for (int i = 8; i < buf.size(); i++) { + if (!buf[i]) { + nullTerm = i; + break; + } + } + buf.resize(nullTerm); + + setArray(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_PROCESSING_METHOD), + EXIF_FORMAT_UNDEFINED, buf.data(), 1, buf.size()); +} + void Exif::setOrientation(int orientation) { int value; @@ -288,6 +422,59 @@ void Exif::setThumbnail(Span thumbnail, data_->size = thumbnail.size(); setShort(EXIF_IFD_0, EXIF_TAG_COMPRESSION, compression); + setLong(EXIF_IFD_0, EXIF_TAG_JPEG_INTERCHANGE_FORMAT, 0); + setLong(EXIF_IFD_0, EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, data_->size); +} + +void Exif::setFocalLength(float length) +{ + ExifRational rational = { static_cast(length), 1 }; + setRational(EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, rational); +} + +void Exif::setExposureTime(int64_t sec) +{ + ExifRational rational = { static_cast(sec), 1 }; + setRational(EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_TIME, rational); +} + +void Exif::setAperture(float size) +{ + ExifRational rational = { static_cast(size * 10000), 10000 }; + setRational(EXIF_IFD_EXIF, EXIF_TAG_FNUMBER, rational); +} + +void Exif::setISO(int16_t iso) +{ + setShort(EXIF_IFD_EXIF, EXIF_TAG_ISO_SPEED_RATINGS, iso); +} + +void Exif::setFlash(Flash flash) +{ + setShort(EXIF_IFD_EXIF, EXIF_TAG_FLASH, static_cast(flash)); +} + +void Exif::setWhiteBalance(WhiteBalance wb) +{ + setShort(EXIF_IFD_EXIF, EXIF_TAG_WHITE_BALANCE, static_cast(wb)); +} + +void Exif::setSubsecTime(uint64_t subsec) +{ + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME, + EXIF_FORMAT_ASCII, std::to_string(subsec)); +} + +void Exif::setSubsecTimeOriginal(uint64_t subsec) +{ + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_ORIGINAL, + EXIF_FORMAT_ASCII, std::to_string(subsec)); +} + +void Exif::setSubsecTimeDigitized(uint64_t subsec) +{ + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_DIGITIZED, + EXIF_FORMAT_ASCII, std::to_string(subsec)); } [[nodiscard]] int Exif::generate() diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h index 5cab4559..cd3b78cd 100644 --- a/src/android/jpeg/exif.h +++ b/src/android/jpeg/exif.h @@ -26,6 +26,28 @@ public: JPEG = 6, }; + enum Flash { + /* bit 0 */ + Fired = 0x01, + /* bits 1 and 2 */ + StrobeReserved = 0x02, + StrobeDetected = 0x04, + StrobeNotDetected = 0x06, + /* bits 3 and 4 */ + ModeCompulsoryFiring = 0x08, + ModeCompulsorySuppression = 0x10, + ModeAuto = 0x18, + /* bit 5 */ + FlashNotPresent = 0x20, + /* bit 6 */ + RedEye = 0x40, + }; + + enum WhiteBalance { + Auto = 0, + Manual = 1, + }; + void setMake(const std::string &make); void setModel(const std::string &model); @@ -35,6 +57,19 @@ public: Compression compression); void setTimestamp(time_t timestamp); + void setGPSDateTimestamp(time_t timestamp); + void setGPSLocation(const double *coords); + void setGPSMethod(const std::string &method); + void setFocalLength(float length); + void setExposureTime(int64_t sec); + void setAperture(float size); + void setISO(int16_t iso); + void setFlash(Flash flash); + void setWhiteBalance(WhiteBalance wb); + void setSubsecTime(uint64_t subsec); + void setSubsecTimeOriginal(uint64_t subsec); + void setSubsecTimeDigitized(uint64_t subsec); + libcamera::Span data() const { return { exifData_, size_ }; } [[nodiscard]] int generate(); @@ -43,11 +78,17 @@ private: ExifEntry *createEntry(ExifIfd ifd, ExifTag tag, ExifFormat format, unsigned long components, unsigned int size); + void setArray(ExifIfd ifd, ExifTag tag, ExifFormat format, + const void *data, size_t size, size_t count); + void setByte(ExifIfd ifd, ExifTag tag, uint8_t item); void setShort(ExifIfd ifd, ExifTag tag, uint16_t item); void setLong(ExifIfd ifd, ExifTag tag, uint32_t item); void setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::string &item); void setRational(ExifIfd ifd, ExifTag tag, ExifRational item); + void setGPSTimestamp(ExifIfd ifd, ExifTag tag, const struct tm &tm); + std::tuple degreesToDMS(double decimalDegrees); + void setGPSDMS(ExifIfd ifd, ExifTag tag, int deg, int min, int sec); bool valid_; From patchwork Thu Jan 14 10:40:34 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10861 X-Patchwork-Delegate: paul.elder@ideasonboard.com 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 149BEBD808 for ; Thu, 14 Jan 2021 10:40:58 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D2791680EA; Thu, 14 Jan 2021 11:40:57 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fbR2vh9Z"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A249A680E7 for ; Thu, 14 Jan 2021 11:40:56 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2FDED2B3; Thu, 14 Jan 2021 11:40:54 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1610620856; bh=lV3ArDdVUZJz/wSGuOjzNwhSGGlVSwh548W5Wg+E6jk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fbR2vh9Z0tekcvUtCR63uAGPdrsbJZefONVs+Bqkb+neAoG77jB/z1NrIEDJVmo2v eZwQuLAKR3qM11E19nuV4UcLbUxMNse24MUusfOKcK6KfZCvhoOafTmg7C88yq01hz Z5FP7TAxyKGMq5U5B236qnN9mfJJ6sFY5hbWMAYA= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 14 Jan 2021 19:40:34 +0900 Message-Id: <20210114104035.302968-6-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210114104035.302968-1-paul.elder@ideasonboard.com> References: <20210114104035.302968-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 5/6] android: camera_device: Load make and model from platform settings 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" In ChromeOS the camera make and model is saved in /var/cache/camera/camera.prop. Load and save these values at construction time, if available. Signed-off-by: Paul Elder --- src/android/camera_device.cpp | 30 ++++++++++++++++++++++++++++++ src/android/camera_device.h | 5 +++++ 2 files changed, 35 insertions(+) diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp index d2a8e876..ed47c7cd 100644 --- a/src/android/camera_device.cpp +++ b/src/android/camera_device.cpp @@ -18,6 +18,7 @@ #include #include +#include "libcamera/internal/file.h" #include "libcamera/internal/formats.h" #include "libcamera/internal/log.h" #include "libcamera/internal/utils.h" @@ -344,6 +345,35 @@ CameraDevice::CameraDevice(unsigned int id, const std::shared_ptr &camer * streamConfiguration. */ maxJpegBufferSize_ = 13 << 20; /* 13631488 from USB HAL */ + + cameraMake_ = "libcamera"; + cameraModel_ = "cameraModel"; + + File prop("/var/cache/camera/camera.prop"); + if (!prop.open(File::ReadOnly)) + return; + + size_t fileSize = prop.size(); + if (fileSize <= 0) + return; + + std::vector buf(fileSize); + if (prop.read(buf) < 0) + return; + + std::string fileContents(buf.begin(), buf.end()); + for (const auto &line : utils::split(fileContents, "\n")) { + off_t delimPos = line.find("="); + if (delimPos == std::string::npos) + continue; + std::string key = line.substr(0, delimPos); + std::string val = line.substr(delimPos + 1, line.size()); + + if (!key.compare("ro.product.model")) + cameraModel_ = val; + if (!key.compare("ro.product.manufacturer")) + cameraMake_ = val; + } } CameraDevice::~CameraDevice() diff --git a/src/android/camera_device.h b/src/android/camera_device.h index 0874c80f..a285d0a1 100644 --- a/src/android/camera_device.h +++ b/src/android/camera_device.h @@ -56,6 +56,8 @@ public: return config_.get(); } + const std::string &cameraMake() const { return cameraMake_; } + const std::string &cameraModel() const { return cameraModel_; } int facing() const { return facing_; } int orientation() const { return orientation_; } unsigned int maxJpegBufferSize() const { return maxJpegBufferSize_; } @@ -127,6 +129,9 @@ private: std::map formatsMap_; std::vector streams_; + std::string cameraMake_; + std::string cameraModel_; + int facing_; int orientation_; From patchwork Thu Jan 14 10:40:35 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10862 X-Patchwork-Delegate: paul.elder@ideasonboard.com 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 78131BD808 for ; Thu, 14 Jan 2021 10:40:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4261E680F4; Thu, 14 Jan 2021 11:40:59 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cgEr7MPP"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6BC41680E6 for ; Thu, 14 Jan 2021 11:40:58 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 098452B3; Thu, 14 Jan 2021 11:40:56 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1610620858; bh=vS2pGjGLDSCCr5zG6kLgXXEkp62yX9U+4rSq50WDYs8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cgEr7MPPI984Ga6REmL4n2P691LOOlZKBwxIH3Yoz1mP/x1un7Gf6Vixey/8kJCDL rRR4hg1jT0FtYnkUIg3u/IUEF8gi36zhf9jqpE/uYaIAbmVpWxCUHueswaZbrzj4Xr jekEdVPz19I6X0BN0okB/W+6hVIHHNYg1vHASAJ0= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 14 Jan 2021 19:40:35 +0900 Message-Id: <20210114104035.302968-7-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210114104035.302968-1-paul.elder@ideasonboard.com> References: <20210114104035.302968-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 6/6] android: Set result metadata and EXIF fields based on request metadata 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" Set the following android result metadata: - ANDROID_LENS_FOCAL_LENGTH - ANDROID_LENS_APERTURE - ANDROID_JPEG_GPS_TIMESTAMP - ANDROID_JPEG_THUMBNAIL_SIZE - ANDROID_JPEG_THUMBNAIL_QUALITY - ANDROID_JPEG_GPS_COORDINATES - ANDROID_JPEG_GPS_PROCESSING_METHOD - ANDROID_JPEG_SIZE - ANDROID_JPEG_QUALITY - ANDROID_JPEG_ORIENTATION And the following EXIF fields: - GPSDatestamp - GPSTimestamp - GPSLocation - GPSLatitudeRef - GPSLatitude - GPSLongitudeRef - GPSLongitude - GPSAltitudeRef - GPSAltitude - GPSProcessingMethod - FocalLength - ExposureTime - FNumber - ISO - Flash - WhiteBalance - SubsecTime - SubsecTimeOriginal - SubsecTimeDigitized Based on android request metadata. This allows the following CTS tests to pass: - android.hardware.camera2.cts.StillCaptureTest#testFocalLengths - android.hardware.camera2.cts.StillCaptureTest#testJpegExif Signed-off-by: Paul Elder --- src/android/camera_device.cpp | 27 +++++- src/android/camera_device.h | 5 +- src/android/camera_stream.cpp | 7 +- src/android/camera_stream.h | 4 +- src/android/jpeg/post_processor_jpeg.cpp | 116 +++++++++++++++++++---- src/android/jpeg/post_processor_jpeg.h | 5 +- src/android/jpeg/thumbnailer.h | 1 + src/android/post_processor.h | 3 +- 8 files changed, 136 insertions(+), 32 deletions(-) diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp index ed47c7cd..8d697080 100644 --- a/src/android/camera_device.cpp +++ b/src/android/camera_device.cpp @@ -296,8 +296,9 @@ MappedCamera3Buffer::MappedCamera3Buffer(const buffer_handle_t camera3buffer, */ CameraDevice::Camera3RequestDescriptor::Camera3RequestDescriptor( - Camera *camera, unsigned int frameNumber, unsigned int numBuffers) - : frameNumber_(frameNumber), numBuffers_(numBuffers) + Camera *camera, unsigned int frameNumber, unsigned int numBuffers, + CameraMetadata &settings) + : frameNumber_(frameNumber), numBuffers_(numBuffers), settings_(settings) { buffers_ = new camera3_stream_buffer_t[numBuffers]; @@ -1700,9 +1701,12 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques * The descriptor and the associated memory reserved here are freed * at request complete time. */ + /* \todo Handle null request settings */ + CameraMetadata requestMetadata(camera3Request->settings); Camera3RequestDescriptor *descriptor = new Camera3RequestDescriptor(camera_.get(), camera3Request->frame_number, - camera3Request->num_output_buffers); + camera3Request->num_output_buffers, + requestMetadata); LOG(HAL, Debug) << "Queueing Request to libcamera with " << descriptor->numBuffers_ << " HAL streams"; @@ -1815,6 +1819,9 @@ void CameraDevice::requestComplete(Request *request) CameraStream *cameraStream = static_cast(descriptor->buffers_[i].stream->priv); + float focal_length = 1.0; + resultMetadata->addEntry(ANDROID_LENS_FOCAL_LENGTH, &focal_length, 1); + if (cameraStream->camera3Stream().format != HAL_PIXEL_FORMAT_BLOB) continue; @@ -1837,6 +1844,7 @@ void CameraDevice::requestComplete(Request *request) } int ret = cameraStream->process(*buffer, &mapped, + descriptor->settings_, resultMetadata.get()); if (ret) { status = CAMERA3_BUFFER_STATUS_ERROR; @@ -1933,14 +1941,23 @@ CameraDevice::getResultMetadata(Camera3RequestDescriptor *descriptor, /* * \todo Keep this in sync with the actual number of entries. - * Currently: 33 entries, 75 bytes + * Currently: 41 entries, 187 bytes * * Reserve more space for the JPEG metadata set by the post-processor. * Currently: ANDROID_JPEG_SIZE (int32_t), ANDROID_JPEG_QUALITY (byte), * ANDROID_JPEG_ORIENTATION (int32_t) = 3 entries, 9 bytes. + * ANDROID_JPEG_THUMBNAIL_SIZE (int32 x 2) = 8 bytes + * ANDROID_JPEG_THUMBNAIL_QUALITY (int32 x 2) = 8 bytes + * ANDROID_LENS_FOCAL_LENGTH (float) = 4 bytes + * ANDROID_JPEG_GPS_COORDINATES (double x 3) = 24 bytes + * ANDROID_JPEG_GPS_PROCESSING_METHOD (byte x 32) = 32 bytes + * ANDROID_JPEG_GPS_TIMESTAMP (int64) = 8 bytes + * ANDROID_LENS_APERTURE (float) = 4 bytes + * + * add coordinates again? */ std::unique_ptr resultMetadata = - std::make_unique(33, 75); + std::make_unique(41, 187); if (!resultMetadata->isValid()) { LOG(HAL, Error) << "Failed to allocate static metadata"; return nullptr; diff --git a/src/android/camera_device.h b/src/android/camera_device.h index a285d0a1..111a7d8f 100644 --- a/src/android/camera_device.h +++ b/src/android/camera_device.h @@ -24,6 +24,7 @@ #include "libcamera/internal/log.h" #include "libcamera/internal/message.h" +#include "camera_metadata.h" #include "camera_stream.h" #include "camera_worker.h" #include "jpeg/encoder.h" @@ -78,7 +79,8 @@ private: struct Camera3RequestDescriptor { Camera3RequestDescriptor(libcamera::Camera *camera, unsigned int frameNumber, - unsigned int numBuffers); + unsigned int numBuffers, + CameraMetadata &settings); ~Camera3RequestDescriptor(); uint32_t frameNumber_; @@ -86,6 +88,7 @@ private: camera3_stream_buffer_t *buffers_; std::vector> frameBuffers_; std::unique_ptr request_; + CameraMetadata settings_; }; struct Camera3StreamConfiguration { diff --git a/src/android/camera_stream.cpp b/src/android/camera_stream.cpp index dba351a4..4c8020e5 100644 --- a/src/android/camera_stream.cpp +++ b/src/android/camera_stream.cpp @@ -96,12 +96,15 @@ int CameraStream::configure() } int CameraStream::process(const libcamera::FrameBuffer &source, - MappedCamera3Buffer *dest, CameraMetadata *metadata) + MappedCamera3Buffer *dest, + const CameraMetadata &requestMetadata, + CameraMetadata *resultMetadata) { if (!postProcessor_) return 0; - return postProcessor_->process(source, dest->maps()[0], metadata); + return postProcessor_->process(source, dest->maps()[0], + requestMetadata, resultMetadata); } FrameBuffer *CameraStream::getBuffer() diff --git a/src/android/camera_stream.h b/src/android/camera_stream.h index cc9d5470..298ffbf6 100644 --- a/src/android/camera_stream.h +++ b/src/android/camera_stream.h @@ -119,7 +119,9 @@ public: int configure(); int process(const libcamera::FrameBuffer &source, - MappedCamera3Buffer *dest, CameraMetadata *metadata); + MappedCamera3Buffer *dest, + const CameraMetadata &requestMetadata, + CameraMetadata *resultMetadata); libcamera::FrameBuffer *getBuffer(); void putBuffer(libcamera::FrameBuffer *buffer); diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp index 436a50f8..13ad3777 100644 --- a/src/android/jpeg/post_processor_jpeg.cpp +++ b/src/android/jpeg/post_processor_jpeg.cpp @@ -25,6 +25,21 @@ PostProcessorJpeg::PostProcessorJpeg(CameraDevice *const device) { } +int PostProcessorJpeg::configureThumbnailer(const Size &size, + const PixelFormat &pixelFormat) +{ + thumbnailer_.configure(size, pixelFormat); + StreamConfiguration thCfg; + thCfg.size = thumbnailer_.size(); + thCfg.pixelFormat = pixelFormat; + if (thumbnailEncoder_.configure(thCfg) != 0) { + LOG(JPEG, Error) << "Failed to configure thumbnail encoder"; + return -EINVAL; + } + + return 0; +} + int PostProcessorJpeg::configure(const StreamConfiguration &inCfg, const StreamConfiguration &outCfg) { @@ -40,16 +55,11 @@ int PostProcessorJpeg::configure(const StreamConfiguration &inCfg, streamSize_ = outCfg.size; - thumbnailer_.configure(inCfg.size, inCfg.pixelFormat); - StreamConfiguration thCfg = inCfg; - thCfg.size = thumbnailer_.size(); - if (thumbnailEncoder_.configure(thCfg) != 0) { - LOG(JPEG, Error) << "Failed to configure thumbnail encoder"; - return -EINVAL; - } + int ret = configureThumbnailer(inCfg.size, inCfg.pixelFormat); + if (ret) + return ret; encoder_ = std::make_unique(); - return encoder_->configure(inCfg); } @@ -80,17 +90,22 @@ void PostProcessorJpeg::generateThumbnail(const FrameBuffer &source, int PostProcessorJpeg::process(const FrameBuffer &source, Span destination, - CameraMetadata *metadata) + const CameraMetadata &requestMetadata, + CameraMetadata *resultMetadata) { if (!encoder_) return 0; + camera_metadata_ro_entry_t entry; + int ret; + /* Set EXIF metadata for various tags. */ Exif exif; - /* \todo Set Make and Model from external vendor tags. */ - exif.setMake("libcamera"); - exif.setModel("cameraModel"); - exif.setOrientation(cameraDevice_->orientation()); + exif.setMake(cameraDevice_->cameraMake()); + exif.setModel(cameraDevice_->cameraModel()); + + ret = requestMetadata.getEntry(ANDROID_JPEG_ORIENTATION, &entry); + exif.setOrientation(ret ? cameraDevice_->orientation() : *entry.data.i32); exif.setSize(streamSize_); /* * We set the frame's EXIF timestamp as the time of encode. @@ -99,11 +114,68 @@ int PostProcessorJpeg::process(const FrameBuffer &source, */ exif.setTimestamp(std::time(nullptr)); - std::vector thumbnail; - generateThumbnail(source, &thumbnail); - if (!thumbnail.empty()) + exif.setExposureTime(0); + ret = requestMetadata.getEntry(ANDROID_LENS_APERTURE, &entry); + if (!ret) { + exif.setAperture(*entry.data.f); + resultMetadata->addEntry(ANDROID_LENS_APERTURE, entry.data.f, 1); + } + exif.setISO(100); + exif.setFlash(Exif::Flash::FlashNotPresent); + exif.setWhiteBalance(Exif::WhiteBalance::Auto); + + exif.setSubsecTime(0); + exif.setSubsecTimeOriginal(0); + exif.setSubsecTimeDigitized(0); + + float focal_length = 1.0; + exif.setFocalLength(focal_length); + + ret = requestMetadata.getEntry(ANDROID_JPEG_GPS_TIMESTAMP, &entry); + if (!ret) { + exif.setGPSDateTimestamp(*entry.data.i64); + resultMetadata->addEntry(ANDROID_JPEG_GPS_TIMESTAMP, + entry.data.i64, 1); + } + + ret = requestMetadata.getEntry(ANDROID_JPEG_THUMBNAIL_SIZE, &entry); + if (!ret) { + /* \todo Handle non-matching aspect ratios */ + /* \todo Handle non-zero orientations */ + const int32_t *data = entry.data.i32; + Size thumbnailSize = { static_cast(data[0]), + static_cast(data[1]) }; + + std::vector thumbnail; + if (thumbnailSize != Size(0, 0)) { + configureThumbnailer(thumbnailSize, thumbnailer_.pixelFormat()); + generateThumbnail(source, &thumbnail); + } exif.setThumbnail(thumbnail, Exif::Compression::JPEG); + resultMetadata->addEntry(ANDROID_JPEG_THUMBNAIL_SIZE, data, 2); + + /* \todo Use this quality as a parameter to the encoder */ + ret = requestMetadata.getEntry(ANDROID_JPEG_THUMBNAIL_QUALITY, &entry); + if (!ret) + resultMetadata->addEntry(ANDROID_JPEG_THUMBNAIL_QUALITY, entry.data.u8, 1); + } + + ret = requestMetadata.getEntry(ANDROID_JPEG_GPS_COORDINATES, &entry); + if (!ret) { + exif.setGPSLocation(entry.data.d); + resultMetadata->addEntry(ANDROID_JPEG_GPS_COORDINATES, + entry.data.d, 3); + } + + ret = requestMetadata.getEntry(ANDROID_JPEG_GPS_PROCESSING_METHOD, &entry); + if (!ret) { + std::string method(entry.data.u8, entry.data.u8 + 32); + exif.setGPSMethod(method); + resultMetadata->addEntry(ANDROID_JPEG_GPS_PROCESSING_METHOD, + entry.data.u8, 32); + } + if (exif.generate() != 0) LOG(JPEG, Error) << "Failed to generate valid EXIF data"; @@ -133,13 +205,15 @@ int PostProcessorJpeg::process(const FrameBuffer &source, blob->jpeg_size = jpeg_size; /* Update the JPEG result Metadata. */ - metadata->addEntry(ANDROID_JPEG_SIZE, &jpeg_size, 1); + resultMetadata->addEntry(ANDROID_JPEG_SIZE, &jpeg_size, 1); - const uint32_t jpeg_quality = 95; - metadata->addEntry(ANDROID_JPEG_QUALITY, &jpeg_quality, 1); + ret = requestMetadata.getEntry(ANDROID_JPEG_QUALITY, &entry); + const uint32_t jpeg_quality = ret ? 95 : *entry.data.u8; + resultMetadata->addEntry(ANDROID_JPEG_QUALITY, &jpeg_quality, 1); - const uint32_t jpeg_orientation = 0; - metadata->addEntry(ANDROID_JPEG_ORIENTATION, &jpeg_orientation, 1); + ret = requestMetadata.getEntry(ANDROID_JPEG_ORIENTATION, &entry); + const uint32_t jpeg_orientation = ret ? 0 : *entry.data.i32; + resultMetadata->addEntry(ANDROID_JPEG_ORIENTATION, &jpeg_orientation, 1); return 0; } diff --git a/src/android/jpeg/post_processor_jpeg.h b/src/android/jpeg/post_processor_jpeg.h index 5afa831c..d07c9fe5 100644 --- a/src/android/jpeg/post_processor_jpeg.h +++ b/src/android/jpeg/post_processor_jpeg.h @@ -26,11 +26,14 @@ public: const libcamera::StreamConfiguration &outcfg) override; int process(const libcamera::FrameBuffer &source, libcamera::Span destination, - CameraMetadata *metadata) override; + const CameraMetadata &requestMetadata, + CameraMetadata *resultMetadata) override; private: void generateThumbnail(const libcamera::FrameBuffer &source, std::vector *thumbnail); + int configureThumbnailer(const libcamera::Size &size, + const libcamera::PixelFormat &pixelFormat); CameraDevice *const cameraDevice_; std::unique_ptr encoder_; diff --git a/src/android/jpeg/thumbnailer.h b/src/android/jpeg/thumbnailer.h index 98f11833..f393db47 100644 --- a/src/android/jpeg/thumbnailer.h +++ b/src/android/jpeg/thumbnailer.h @@ -22,6 +22,7 @@ public: void createThumbnail(const libcamera::FrameBuffer &source, std::vector *dest); const libcamera::Size &size() const { return targetSize_; } + const libcamera::PixelFormat &pixelFormat() const { return pixelFormat_; } private: libcamera::Size computeThumbnailSize() const; diff --git a/src/android/post_processor.h b/src/android/post_processor.h index e0f91880..bda93bb4 100644 --- a/src/android/post_processor.h +++ b/src/android/post_processor.h @@ -22,7 +22,8 @@ public: const libcamera::StreamConfiguration &outCfg) = 0; virtual int process(const libcamera::FrameBuffer &source, libcamera::Span destination, - CameraMetadata *metadata) = 0; + const CameraMetadata &requestMetadata, + CameraMetadata *resultMetadata) = 0; }; #endif /* __ANDROID_POST_PROCESSOR_H__ */