From patchwork Mon Jan 25 07:14:37 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10979 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 007D6C0F2B for ; Mon, 25 Jan 2021 07:15:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A57B3682AB; Mon, 25 Jan 2021 08:14: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="VAp+EKXI"; 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 E87CF682A3 for ; Mon, 25 Jan 2021 08:14:57 +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 446333E; Mon, 25 Jan 2021 08:14:55 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1611558897; bh=UlSbELcIEI8KiDrng+ZawPwZDvY49AToD6RcuA/MulI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VAp+EKXInF4iRHUOPqEZAtnvIm7NmfmV4/aVxMoqajlGRxUY+H5ums30y9Rf/DnH8 bwmeQPwEaFC7KbyzhNl19t3PvwafXXIwO3dswSgV0x7OexA2iY/uqXuUb3/VENstLS MhXtw5Mz6uaVNaMzArRCFmFD5JjglrAtGLCGOUEk= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Mon, 25 Jan 2021 16:14:37 +0900 Message-Id: <20210125071444.26252-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210125071444.26252-1-paul.elder@ideasonboard.com> References: <20210125071444.26252-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 1/8] android: jpeg: exif: Expand setString to support different encodings 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 UTF-16. Expand setString to take an encoding when the field type is undefined. Update callers accordingly. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- Changes in v4: - clean up switch Changes in v3: - use array to contain string encoding codes - drop JIS and Undefined - clean up setString a bit - use the endianness of the exif Changes in v2: - moved from utils into exif - support no-encoding --- src/android/jpeg/exif.cpp | 77 +++++++++++++++++++++++++++++++++++++-- src/android/jpeg/exif.h | 11 +++++- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp index 33b3fa7f..89343323 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" @@ -178,11 +181,18 @@ void Exif::setRational(ExifIfd ifd, ExifTag tag, ExifRational item) exif_entry_unref(entry); } -void Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::string &item) +static const std::map> stringEncodingCodes = { + { Exif::ASCII, { 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 } }, + { Exif::Unicode, { 0x55, 0x4e, 0x49, 0x43, 0x4f, 0x44, 0x45, 0x00 } }, +}; + +void Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, + const std::string &item, StringEncoding encoding) { std::string ascii; size_t length; const char *str; + std::vector buf; if (format == EXIF_FORMAT_ASCII) { ascii = utils::toAscii(item); @@ -191,13 +201,46 @@ void Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::str /* Pad 1 extra byte to null-terminate the ASCII string. */ length = ascii.length() + 1; } else { - str = item.c_str(); + std::u16string u16str; + + auto encodingString = stringEncodingCodes.find(encoding); + if (encodingString != stringEncodingCodes.end()) { + buf = { + encodingString->second.begin(), + encodingString->second.end() + }; + } + + switch (encoding) { + case Unicode: + u16str = utf8ToUtf16(item); + + buf.resize(8 + u16str.size() * 2); + for (size_t i = 0; i < u16str.size(); i++) { + if (order_ == EXIF_BYTE_ORDER_INTEL) { + buf[8 + 2 * i] = u16str[i] & 0xff; + buf[8 + 2 * i + 1] = (u16str[i] >> 8) & 0xff; + } else { + buf[8 + 2 * i] = (u16str[i] >> 8) & 0xff; + buf[8 + 2 * i + 1] = u16str[i] & 0xff; + } + } + + break; + + case ASCII: + case NoEncoding: + buf.insert(buf.end(), item.begin(), item.end()); + break; + } + + str = reinterpret_cast(buf.data()); /* * Strings stored in different formats (EXIF_FORMAT_UNDEFINED) * are not null-terminated. */ - length = item.length(); + length = buf.size(); } ExifEntry *entry = createEntry(ifd, tag, format, length, length); @@ -290,6 +333,34 @@ void Exif::setThumbnail(Span thumbnail, setShort(EXIF_IFD_0, EXIF_TAG_COMPRESSION, compression); } +/** + * \brief Convert UTF-8 string to UTF-16 string + * \param[in] str String to convert + * + * \return \a str in UTF-16 + */ +std::u16string Exif::utf8ToUtf16(const std::string &str) +{ + mbstate_t state{}; + char16_t c16; + const char *ptr = str.data(); + const char *end = ptr + str.size(); + + std::u16string 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(c16); + + if (rc > 0) + ptr += rc; + } + + return ret; +} + [[nodiscard]] int Exif::generate() { if (exifData_) { diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h index 5cab4559..8b84165b 100644 --- a/src/android/jpeg/exif.h +++ b/src/android/jpeg/exif.h @@ -26,6 +26,12 @@ public: JPEG = 6, }; + enum StringEncoding { + NoEncoding = 0, + ASCII = 1, + Unicode = 2, + }; + void setMake(const std::string &make); void setModel(const std::string &model); @@ -46,9 +52,12 @@ private: 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); + const std::string &item, + StringEncoding encoding = NoEncoding); void setRational(ExifIfd ifd, ExifTag tag, ExifRational item); + std::u16string utf8ToUtf16(const std::string &str); + bool valid_; ExifData *data_; From patchwork Mon Jan 25 07:14:38 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10980 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 2B667C0F2B for ; Mon, 25 Jan 2021 07:15:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F2286682B0; Mon, 25 Jan 2021 08:15:00 +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="WrdL5E0m"; 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 49A67682A0 for ; Mon, 25 Jan 2021 08:15:00 +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 504114FB; Mon, 25 Jan 2021 08:14:58 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1611558900; bh=IqNjk7Eq2zng9qAGz+NbI6WM99bZ12sQo3iNJ4VJRxw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WrdL5E0m9+fcFALoRM8HREPa0SAsuCFnPSFPyv8Po2veJ+lbf3NumyitKnOJJUJfn qmlr9mApfX5hbgAaeCk0KSsiOKtpZ2/I94Gr4jS5Jwx9JyVPmqx5wK1O+zC7d7I+0U Mjo49n3y5ozpsqg7UWUYuVdsg6pe0Y8htDw8giZA= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Mon, 25 Jan 2021 16:14:38 +0900 Message-Id: <20210125071444.26252-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210125071444.26252-1-paul.elder@ideasonboard.com> References: <20210125071444.26252-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 2/8] 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 the sensor orientation, 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. As Android defines the rotation as the clockwise angle by which the image needs to be rotated to appear in the correct orientation on the device screen, the previous values would be correct if the input angle was from the camera orientation. Since the correct input should be the requested JPEG orientation, these new values are the correct ones. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- No change in v3 Changes in v2 - expand commit message --- 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 89343323..204a237a 100644 --- a/src/android/jpeg/exif.cpp +++ b/src/android/jpeg/exif.cpp @@ -306,13 +306,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 Mon Jan 25 07:14:39 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10981 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 834B5C0F2B for ; Mon, 25 Jan 2021 07:15:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 511F3682AB; Mon, 25 Jan 2021 08:15:03 +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="QW/Gd+ps"; 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 555C2682A0 for ; Mon, 25 Jan 2021 08:15:02 +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 9A2FC331; Mon, 25 Jan 2021 08:15:00 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1611558902; bh=wFN6zVruReTsbCzqVSHxLJi0kXjFgyNJ8Ln8IG1opzI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QW/Gd+psclQ5excRYGalv4E5lHvBm5Fbb96h3W/iJ9hymK/nEjhFSVAokG9m/EcS0 JarVr2VRV65BhR8kFhEBN2872F71g6cT4rBQOSDEYEontjnGwB886UbcP3+DpQpYc1 OXkb0Czte97tBFrwB+POKcmqfaFXReHCVRCJH2W4= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Mon, 25 Jan 2021 16:14:39 +0900 Message-Id: <20210125071444.26252-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210125071444.26252-1-paul.elder@ideasonboard.com> References: <20210125071444.26252-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 3/8] 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 Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- Changes in v4: - change type of subsec to std::chrono::milliseconds Changes in v3: - merge setting subsec into setting the main timestamp Changes in v2: - some cosmetic and precision changes - use the new setString --- src/android/jpeg/exif.cpp | 178 +++++++++++++++++++++-- src/android/jpeg/exif.h | 39 ++++- src/android/jpeg/post_processor_jpeg.cpp | 4 +- 3 files changed, 205 insertions(+), 16 deletions(-) diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp index 204a237a..0f2c2d95 100644 --- a/src/android/jpeg/exif.cpp +++ b/src/android/jpeg/exif.cpp @@ -7,7 +7,10 @@ #include "exif.h" +#include +#include #include +#include #include #include "libcamera/internal/log.h" @@ -151,6 +154,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; + + entry->data[0] = item; + exif_entry_unref(entry); +} + void Exif::setShort(ExifIfd ifd, ExifTag tag, uint16_t item) { ExifEntry *entry = createEntry(ifd, tag); @@ -267,7 +280,7 @@ void Exif::setSize(const Size &size) setLong(EXIF_IFD_EXIF, EXIF_TAG_PIXEL_X_DIMENSION, size.width); } -void Exif::setTimestamp(time_t timestamp) +void Exif::setTimestamp(time_t timestamp, std::chrono::milliseconds msec) { struct tm tm; localtime_r(×tamp, &tm); @@ -282,19 +295,123 @@ void Exif::setTimestamp(time_t timestamp) /* Query and set timezone information if available. */ int r = strftime(str, sizeof(str), "%z", &tm); - if (r > 0) { - std::string tz(str); - tz.insert(3, 1, ':'); - setString(EXIF_IFD_EXIF, - static_cast(_ExifTag::OFFSET_TIME), - EXIF_FORMAT_ASCII, tz); - setString(EXIF_IFD_EXIF, - static_cast(_ExifTag::OFFSET_TIME_ORIGINAL), - EXIF_FORMAT_ASCII, tz); - setString(EXIF_IFD_EXIF, - static_cast(_ExifTag::OFFSET_TIME_DIGITIZED), - EXIF_FORMAT_ASCII, tz); - } + if (r <= 0) + return; + + std::string tz(str); + tz.insert(3, 1, ':'); + setString(EXIF_IFD_EXIF, + static_cast(_ExifTag::OFFSET_TIME), + EXIF_FORMAT_ASCII, tz); + setString(EXIF_IFD_EXIF, + static_cast(_ExifTag::OFFSET_TIME_ORIGINAL), + EXIF_FORMAT_ASCII, tz); + setString(EXIF_IFD_EXIF, + static_cast(_ExifTag::OFFSET_TIME_DIGITIZED), + EXIF_FORMAT_ASCII, tz); + + std::string subsec = std::to_string(msec.count()); + + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME, + EXIF_FORMAT_ASCII, subsec); + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_ORIGINAL, + EXIF_FORMAT_ASCII, subsec); + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_DIGITIZED, + EXIF_FORMAT_ASCII, subsec); +} + +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 tsStr(str); + + setString(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_DATE_STAMP), + EXIF_FORMAT_ASCII, tsStr); + + /* Set GPS_TIME_STAMP */ + ExifEntry *entry = + createEntry(EXIF_IFD_GPS, + static_cast(EXIF_TAG_GPS_TIME_STAMP), + EXIF_FORMAT_RATIONAL, 3, 3 * sizeof(ExifRational)); + if (!entry) + return; + + ExifRational ts[] = { + { static_cast(tm.tm_hour), 1 }, + { static_cast(tm.tm_min), 1 }, + { static_cast(tm.tm_sec), 1 }, + }; + + for (int i = 0; i < 3; i++) + exif_set_rational(entry->data + i * sizeof(ExifRational), + order_, ts[i]); + + exif_entry_unref(entry); +} + +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) +{ + ExifEntry *entry = createEntry(ifd, tag, EXIF_FORMAT_RATIONAL, 3, + 3 * sizeof(ExifRational)); + if (!entry) + return; + + ExifRational coords[] = { + { static_cast(deg), 1 }, + { static_cast(min), 1 }, + { static_cast(sec), 1 }, + }; + + for (int i = 0; i < 3; i++) + exif_set_rational(entry->data + i * sizeof(ExifRational), + order_, coords[i]); + exif_entry_unref(entry); +} + +/* + * \brief Set GPS location (lat, long, alt) + * \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 deg, min, sec; + + std::tie(deg, min, sec) = degreesToDMS(coords[0]); + setString(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LATITUDE_REF), + EXIF_FORMAT_ASCII, deg >= 0 ? "N" : "S"); + setGPSDMS(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LATITUDE), + std::abs(deg), min, sec); + + std::tie(deg, min, sec) = degreesToDMS(coords[1]); + setString(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LONGITUDE_REF), + EXIF_FORMAT_ASCII, deg >= 0 ? "E" : "W"); + setGPSDMS(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LATITUDE), + std::abs(deg), min, sec); + + 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) +{ + setString(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_PROCESSING_METHOD), + EXIF_FORMAT_UNDEFINED, method, Unicode); } void Exif::setOrientation(int orientation) @@ -333,6 +450,39 @@ void Exif::setThumbnail(Span thumbnail, setShort(EXIF_IFD_0, EXIF_TAG_COMPRESSION, compression); } +void Exif::setFocalLength(float length) +{ + ExifRational rational = { static_cast(length * 1000), 1000 }; + setRational(EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, rational); +} + +void Exif::setExposureTime(uint64_t nsec) +{ + ExifRational rational = { static_cast(nsec), 1000000000 }; + 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(uint16_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)); +} + /** * \brief Convert UTF-8 string to UTF-16 string * \param[in] str String to convert diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h index 8b84165b..b0d61402 100644 --- a/src/android/jpeg/exif.h +++ b/src/android/jpeg/exif.h @@ -7,6 +7,7 @@ #ifndef __ANDROID_JPEG_EXIF_H__ #define __ANDROID_JPEG_EXIF_H__ +#include #include #include @@ -26,6 +27,27 @@ public: JPEG = 6, }; + enum Flash { + /* bit 0 */ + Fired = 0x01, + /* bits 1 and 2 */ + 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, + }; + enum StringEncoding { NoEncoding = 0, ASCII = 1, @@ -39,7 +61,18 @@ public: void setSize(const libcamera::Size &size); void setThumbnail(libcamera::Span thumbnail, Compression compression); - void setTimestamp(time_t timestamp); + void setTimestamp(time_t timestamp, std::chrono::milliseconds msec); + + void setGPSDateTimestamp(time_t timestamp); + void setGPSLocation(const double *coords); + void setGPSMethod(const std::string &method); + + void setFocalLength(float length); + void setExposureTime(uint64_t nsec); + void setAperture(float size); + void setISO(uint16_t iso); + void setFlash(Flash flash); + void setWhiteBalance(WhiteBalance wb); libcamera::Span data() const { return { exifData_, size_ }; } [[nodiscard]] int generate(); @@ -49,6 +82,7 @@ private: ExifEntry *createEntry(ExifIfd ifd, ExifTag tag, ExifFormat format, unsigned long components, unsigned int size); + 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, @@ -56,6 +90,9 @@ private: StringEncoding encoding = NoEncoding); void setRational(ExifIfd ifd, ExifTag tag, ExifRational item); + std::tuple degreesToDMS(double decimalDegrees); + void setGPSDMS(ExifIfd ifd, ExifTag tag, int deg, int min, int sec); + std::u16string utf8ToUtf16(const std::string &str); bool valid_; diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp index 436a50f8..c29cb352 100644 --- a/src/android/jpeg/post_processor_jpeg.cpp +++ b/src/android/jpeg/post_processor_jpeg.cpp @@ -7,6 +7,8 @@ #include "post_processor_jpeg.h" +#include + #include "../camera_device.h" #include "../camera_metadata.h" #include "encoder_libjpeg.h" @@ -97,7 +99,7 @@ int PostProcessorJpeg::process(const FrameBuffer &source, * Since the precision we need for EXIF timestamp is only one * second, it is good enough. */ - exif.setTimestamp(std::time(nullptr)); + exif.setTimestamp(std::time(nullptr), std::chrono::milliseconds(0)); std::vector thumbnail; generateThumbnail(source, &thumbnail); From patchwork Mon Jan 25 07:14:40 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10982 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 E6D3EC0F2B for ; Mon, 25 Jan 2021 07:15:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B6EB8682AB; Mon, 25 Jan 2021 08:15:06 +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="f4Ejvb6k"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7D1C5682A0 for ; Mon, 25 Jan 2021 08:15:04 +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 B2B4A3E; Mon, 25 Jan 2021 08:15:02 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1611558904; bh=cGMm4a8BNd/BSZwtFQp+QkkR5Coh5AJ2kWJs8uISCKY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=f4Ejvb6kxiHLm4Wp4hv5Fnadt/YHkzv4A4ANnKDFSCJk3yJ5/RD8oZbXy8x2miuub cpIFOJUErrzxHCJFlps/H21eGYdKmAZfQDaj60AOSswfqKtSc70JeE2ao2lIh4yD/S 8jzlECcDmvf31fsjk4+nL2Z0uJagfStsc4DKGeb4= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Mon, 25 Jan 2021 16:14:40 +0900 Message-Id: <20210125071444.26252-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210125071444.26252-1-paul.elder@ideasonboard.com> References: <20210125071444.26252-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 4/8] 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 Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- Changes in v3: - rename cameraMake_ and cameraModel_ to maker_ and model_ Changes in v2: - use fstream instead of File and split --- src/android/camera_device.cpp | 23 +++++++++++++++++++++++ src/android/camera_device.h | 5 +++++ 2 files changed, 28 insertions(+) diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp index 3983c6dc..592e2d43 100644 --- a/src/android/camera_device.cpp +++ b/src/android/camera_device.cpp @@ -9,6 +9,7 @@ #include "camera_ops.h" #include "post_processor.h" +#include #include #include #include @@ -351,6 +352,28 @@ CameraDevice::CameraDevice(unsigned int id, const std::shared_ptr &camer * streamConfiguration. */ maxJpegBufferSize_ = 13 << 20; /* 13631488 from USB HAL */ + + maker_ = "libcamera"; + model_ = "cameraModel"; + + /* \todo Support getting properties on Android */ + std::ifstream fstream("/var/cache/camera/camera.prop"); + if (!fstream.is_open()) + return; + + std::string line; + while (std::getline(fstream, line)) { + std::string::size_type delimPos = line.find("="); + if (delimPos == std::string::npos) + continue; + std::string key = line.substr(0, delimPos); + std::string val = line.substr(delimPos + 1); + + if (!key.compare("ro.product.model")) + model_ = val; + else if (!key.compare("ro.product.manufacturer")) + maker_ = val; + } } CameraDevice::~CameraDevice() diff --git a/src/android/camera_device.h b/src/android/camera_device.h index 597d11fc..058a3f9a 100644 --- a/src/android/camera_device.h +++ b/src/android/camera_device.h @@ -55,6 +55,8 @@ public: return config_.get(); } + const std::string &cameraMake() const { return maker_; } + const std::string &cameraModel() const { return model_; } int facing() const { return facing_; } int orientation() const { return orientation_; } unsigned int maxJpegBufferSize() const { return maxJpegBufferSize_; } @@ -125,6 +127,9 @@ private: std::map formatsMap_; std::vector streams_; + std::string maker_; + std::string model_; + int facing_; int orientation_; From patchwork Mon Jan 25 07:14:41 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10983 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 53ADBC0F2B for ; Mon, 25 Jan 2021 07:15:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1F606682B7; Mon, 25 Jan 2021 08:15:08 +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="W+i66qEv"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AFE47682A9 for ; Mon, 25 Jan 2021 08:15:06 +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 D757C3E; Mon, 25 Jan 2021 08:15:04 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1611558906; bh=CpbkywU40X2M6xVNpW7WC6gOfumj5/v3gZZSYTTF8No=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=W+i66qEvNtyGBP/k2mdJo0MsMgx1E1zNWsKnD6TGahTh9ha4ZBMSGaRCi6gtIetsg HpjKKb/9SQHFS1Vmj7fpmD7xN2UlcEln+JcVoos+xkTUbblhCThmuOht9gxtoWzcod T8jbxRmuRN/3wRjy/xupqVLW9QJHpybWatSmupMA= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Mon, 25 Jan 2021 16:14:41 +0900 Message-Id: <20210125071444.26252-6-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210125071444.26252-1-paul.elder@ideasonboard.com> References: <20210125071444.26252-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 5/8] 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_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 Reviewed-by: Laurent Pinchart --- Changes in v4: - remove unused variable - move android JPEG thumbnail tags allocation to the thumbnail patch - move setting the timestamp with subsec to the patch that adds set subsec to timestamp ("android: jpeg: exif: Add functions for setting various values") - group setting GPS-related tags Changes in v3: - fix metadata entries and byte count - fix gps processing method string truncation - move thumbnail handling to a different patch Changes in v2: - break out processControls and thumbnailer configuration into different patches - fix android metadata entry number and size - other small changes --- src/android/camera_device.cpp | 19 +++++++- src/android/camera_stream.cpp | 7 ++- src/android/camera_stream.h | 4 +- src/android/jpeg/post_processor_jpeg.cpp | 62 +++++++++++++++++++----- src/android/jpeg/post_processor_jpeg.h | 3 +- src/android/post_processor.h | 3 +- 6 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp index 592e2d43..2cd3c8a1 100644 --- a/src/android/camera_device.cpp +++ b/src/android/camera_device.cpp @@ -1688,6 +1688,7 @@ 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 */ Camera3RequestDescriptor *descriptor = new Camera3RequestDescriptor(camera_.get(), camera3Request); @@ -1817,6 +1818,7 @@ void CameraDevice::requestComplete(Request *request) } int ret = cameraStream->process(*buffer, &mapped, + descriptor->settings_, resultMetadata.get()); if (ret) { status = CAMERA3_BUFFER_STATUS_ERROR; @@ -1913,14 +1915,19 @@ CameraDevice::getResultMetadata(Camera3RequestDescriptor *descriptor, /* * \todo Keep this in sync with the actual number of entries. - * Currently: 33 entries, 75 bytes + * Currently: 38 entries, 147 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_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_JPEG_ORIENTATION (int32_t) = 3 entries, 9 bytes. + * ANDROID_LENS_APERTURE (float) = 4 bytes + * ANDROID_LENS_FOCAL_LENGTH (float) = 4 bytes */ std::unique_ptr resultMetadata = - std::make_unique(33, 75); + std::make_unique(38, 147); if (!resultMetadata->isValid()) { LOG(HAL, Error) << "Failed to allocate static metadata"; return nullptr; @@ -1985,6 +1992,14 @@ CameraDevice::getResultMetadata(Camera3RequestDescriptor *descriptor, value = ANDROID_FLASH_STATE_UNAVAILABLE; resultMetadata->addEntry(ANDROID_FLASH_STATE, &value, 1); + camera_metadata_ro_entry_t entry; + int ret = descriptor->settings_.getEntry(ANDROID_LENS_APERTURE, &entry); + if (ret) + resultMetadata->addEntry(ANDROID_LENS_APERTURE, entry.data.f, 1); + + float focal_length = 1.0; + resultMetadata->addEntry(ANDROID_LENS_FOCAL_LENGTH, &focal_length, 1); + value = ANDROID_LENS_STATE_STATIONARY; resultMetadata->addEntry(ANDROID_LENS_STATE, &value, 1); 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 c29cb352..10c71a47 100644 --- a/src/android/jpeg/post_processor_jpeg.cpp +++ b/src/android/jpeg/post_processor_jpeg.cpp @@ -82,17 +82,26 @@ 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); + + const uint32_t jpegOrientation = ret ? *entry.data.i32 : 0; + resultMetadata->addEntry(ANDROID_JPEG_ORIENTATION, &jpegOrientation, 1); + exif.setOrientation(jpegOrientation); + exif.setSize(streamSize_); /* * We set the frame's EXIF timestamp as the time of encode. @@ -101,6 +110,38 @@ int PostProcessorJpeg::process(const FrameBuffer &source, */ exif.setTimestamp(std::time(nullptr), std::chrono::milliseconds(0)); + exif.setExposureTime(0); + ret = requestMetadata.getEntry(ANDROID_LENS_APERTURE, &entry); + if (ret) + exif.setAperture(*entry.data.f); + exif.setISO(100); + exif.setFlash(Exif::Flash::FlashNotPresent); + exif.setWhiteBalance(Exif::WhiteBalance::Auto); + + exif.setFocalLength(1.0); + + 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_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 + entry.count); + exif.setGPSMethod(method); + resultMetadata->addEntry(ANDROID_JPEG_GPS_PROCESSING_METHOD, + entry.data.u8, entry.count); + } + std::vector thumbnail; generateThumbnail(source, &thumbnail); if (!thumbnail.empty()) @@ -135,13 +176,12 @@ int PostProcessorJpeg::process(const FrameBuffer &source, blob->jpeg_size = jpeg_size; /* Update the JPEG result Metadata. */ - metadata->addEntry(ANDROID_JPEG_SIZE, &jpeg_size, 1); - - const uint32_t jpeg_quality = 95; - metadata->addEntry(ANDROID_JPEG_QUALITY, &jpeg_quality, 1); + resultMetadata->addEntry(ANDROID_JPEG_SIZE, &jpeg_size, 1); - const uint32_t jpeg_orientation = 0; - metadata->addEntry(ANDROID_JPEG_ORIENTATION, &jpeg_orientation, 1); + /* \todo Configure JPEG encoder with this */ + ret = requestMetadata.getEntry(ANDROID_JPEG_QUALITY, &entry); + const uint32_t jpegQuality = ret ? *entry.data.u8 : 95; + resultMetadata->addEntry(ANDROID_JPEG_QUALITY, &jpegQuality, 1); return 0; } diff --git a/src/android/jpeg/post_processor_jpeg.h b/src/android/jpeg/post_processor_jpeg.h index 5afa831c..d721d1b9 100644 --- a/src/android/jpeg/post_processor_jpeg.h +++ b/src/android/jpeg/post_processor_jpeg.h @@ -26,7 +26,8 @@ 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, 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__ */ From patchwork Mon Jan 25 07:14:42 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10984 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 E02C6C0F2B for ; Mon, 25 Jan 2021 07:15:10 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AEC7B682AD; Mon, 25 Jan 2021 08:15:10 +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="Yjj1BDv4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D59C2682A8 for ; Mon, 25 Jan 2021 08:15:08 +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 2DD63331; Mon, 25 Jan 2021 08:15:06 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1611558908; bh=EmyEaGJZaCkGrmvy62xLt/GNUdeZlpLPPagqqWda/ro=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Yjj1BDv4NWYm7H1eVzwN286b2/K+uInxomw/Gpsh/2WwLRD8+y8+1zjCOULGaKO5f ixST0XVXdyTx/BhWBpxaOl2s/lfQr1/9tzYPI1kUmDhZj9is939Je4CtnN/5F94eZ/ kC1/kHHL/W/8lk07OBuume0/e5kfCQnS5xy0aCBM= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Mon, 25 Jan 2021 16:14:42 +0900 Message-Id: <20210125071444.26252-7-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210125071444.26252-1-paul.elder@ideasonboard.com> References: <20210125071444.26252-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 6/8] android: jpeg: Configure thumbnailer 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" Configure the thumbnailer based on the thumbnail parameters given by the android request metadata. Only the thumbnail encoder needs to be configured, and since it is only used at post-processing time, move the configuration out of the post-processor constructor and into the processing step. Also set the following android result metadata tags: - ANDROID_JPEG_THUMBNAIL_SIZE - ANDROID_JPEG_THUMBNAIL_QUALITY Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v4: - cosmetic changes New in v3 - split from "android: Set result metadata and EXIF fields based on request" --- src/android/camera_device.cpp | 6 ++-- src/android/jpeg/post_processor_jpeg.cpp | 43 +++++++++++++++++------- src/android/jpeg/post_processor_jpeg.h | 1 + src/android/jpeg/thumbnailer.cpp | 25 ++------------ src/android/jpeg/thumbnailer.h | 6 ++-- 5 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp index 2cd3c8a1..3068f89f 100644 --- a/src/android/camera_device.cpp +++ b/src/android/camera_device.cpp @@ -1915,7 +1915,7 @@ CameraDevice::getResultMetadata(Camera3RequestDescriptor *descriptor, /* * \todo Keep this in sync with the actual number of entries. - * Currently: 38 entries, 147 bytes + * Currently: 40 entries, 156 bytes * * Reserve more space for the JPEG metadata set by the post-processor. * Currently: ANDROID_JPEG_SIZE (int32_t), ANDROID_JPEG_QUALITY (byte), @@ -1923,11 +1923,13 @@ CameraDevice::getResultMetadata(Camera3RequestDescriptor *descriptor, * ANDROID_JPEG_GPS_PROCESSING_METHOD (byte x 32) = 32 bytes * ANDROID_JPEG_GPS_TIMESTAMP (int64) = 8 bytes * ANDROID_JPEG_ORIENTATION (int32_t) = 3 entries, 9 bytes. + * ANDROID_JPEG_THUMBNAIL_QUALITY (byte) = 1 byte + * ANDROID_JPEG_THUMBNAIL_SIZE (int32 x 2) = 8 bytes * ANDROID_LENS_APERTURE (float) = 4 bytes * ANDROID_LENS_FOCAL_LENGTH (float) = 4 bytes */ std::unique_ptr resultMetadata = - std::make_unique(38, 147); + std::make_unique(40, 156); if (!resultMetadata->isValid()) { LOG(HAL, Error) << "Failed to allocate static metadata"; return nullptr; diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp index 10c71a47..d35fe361 100644 --- a/src/android/jpeg/post_processor_jpeg.cpp +++ b/src/android/jpeg/post_processor_jpeg.cpp @@ -43,12 +43,6 @@ 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; - } encoder_ = std::make_unique(); @@ -56,14 +50,20 @@ int PostProcessorJpeg::configure(const StreamConfiguration &inCfg, } void PostProcessorJpeg::generateThumbnail(const FrameBuffer &source, + const Size &targetSize, std::vector *thumbnail) { /* Stores the raw scaled-down thumbnail bytes. */ std::vector rawThumbnail; - thumbnailer_.createThumbnail(source, &rawThumbnail); + thumbnailer_.createThumbnail(source, targetSize, &rawThumbnail); - if (!rawThumbnail.empty()) { + StreamConfiguration thCfg; + thCfg.size = targetSize; + thCfg.pixelFormat = thumbnailer_.pixelFormat(); + int ret = thumbnailEncoder_.configure(thCfg); + + if (!rawThumbnail.empty() && !ret) { /* * \todo Avoid value-initialization of all elements of the * vector. @@ -127,6 +127,28 @@ int PostProcessorJpeg::process(const FrameBuffer &source, entry.data.i64, 1); } + ret = requestMetadata.getEntry(ANDROID_JPEG_THUMBNAIL_SIZE, &entry); + if (ret) { + const int32_t *data = entry.data.i32; + Size thumbnailSize = { static_cast(data[0]), + static_cast(data[1]) }; + + if (thumbnailSize != Size(0, 0)) { + std::vector thumbnail; + generateThumbnail(source, thumbnailSize, &thumbnail); + if (!thumbnail.empty()) + 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); @@ -142,11 +164,6 @@ int PostProcessorJpeg::process(const FrameBuffer &source, entry.data.u8, entry.count); } - std::vector thumbnail; - generateThumbnail(source, &thumbnail); - if (!thumbnail.empty()) - exif.setThumbnail(thumbnail, Exif::Compression::JPEG); - if (exif.generate() != 0) LOG(JPEG, Error) << "Failed to generate valid EXIF data"; diff --git a/src/android/jpeg/post_processor_jpeg.h b/src/android/jpeg/post_processor_jpeg.h index d721d1b9..660b79b4 100644 --- a/src/android/jpeg/post_processor_jpeg.h +++ b/src/android/jpeg/post_processor_jpeg.h @@ -31,6 +31,7 @@ public: private: void generateThumbnail(const libcamera::FrameBuffer &source, + const libcamera::Size &targetSize, std::vector *thumbnail); CameraDevice *const cameraDevice_; diff --git a/src/android/jpeg/thumbnailer.cpp b/src/android/jpeg/thumbnailer.cpp index 5374538a..f709d343 100644 --- a/src/android/jpeg/thumbnailer.cpp +++ b/src/android/jpeg/thumbnailer.cpp @@ -32,30 +32,11 @@ void Thumbnailer::configure(const Size &sourceSize, PixelFormat pixelFormat) return; } - targetSize_ = computeThumbnailSize(); - valid_ = true; } -/* - * The Exif specification recommends the width of the thumbnail to be a - * multiple of 16 (section 4.8.1). Hence, compute the corresponding height - * keeping the aspect ratio same as of the source. - */ -Size Thumbnailer::computeThumbnailSize() const -{ - unsigned int targetHeight; - constexpr unsigned int kTargetWidth = 160; - - targetHeight = kTargetWidth * sourceSize_.height / sourceSize_.width; - - if (targetHeight & 1) - targetHeight++; - - return Size(kTargetWidth, targetHeight); -} - void Thumbnailer::createThumbnail(const FrameBuffer &source, + const Size &targetSize, std::vector *destination) { MappedFrameBuffer frame(&source, PROT_READ); @@ -73,8 +54,8 @@ void Thumbnailer::createThumbnail(const FrameBuffer &source, const unsigned int sw = sourceSize_.width; const unsigned int sh = sourceSize_.height; - const unsigned int tw = targetSize_.width; - const unsigned int th = targetSize_.height; + const unsigned int tw = targetSize.width; + const unsigned int th = targetSize.height; ASSERT(tw % 2 == 0 && th % 2 == 0); diff --git a/src/android/jpeg/thumbnailer.h b/src/android/jpeg/thumbnailer.h index 98f11833..4e9226c3 100644 --- a/src/android/jpeg/thumbnailer.h +++ b/src/android/jpeg/thumbnailer.h @@ -20,15 +20,13 @@ public: void configure(const libcamera::Size &sourceSize, libcamera::PixelFormat pixelFormat); void createThumbnail(const libcamera::FrameBuffer &source, + const libcamera::Size &targetSize, std::vector *dest); - const libcamera::Size &size() const { return targetSize_; } + const libcamera::PixelFormat &pixelFormat() const { return pixelFormat_; } private: - libcamera::Size computeThumbnailSize() const; - libcamera::PixelFormat pixelFormat_; libcamera::Size sourceSize_; - libcamera::Size targetSize_; bool valid_; }; From patchwork Mon Jan 25 07:14:43 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10985 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 622AEC0F2B for ; Mon, 25 Jan 2021 07:15:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2E1D3682B5; Mon, 25 Jan 2021 08:15:12 +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="o0VFO2JW"; 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 40A4F682A0 for ; Mon, 25 Jan 2021 08:15:11 +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 5F1E83E; Mon, 25 Jan 2021 08:15:08 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1611558911; bh=fEHYRnVKDxIJ3DteNfKAKKIz9m8zVBI5LbT43da0sek=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=o0VFO2JWWHk4caT2SaCGWLVdqTewhT9/oiulaHuNfAyy4hXuO7oMSebUBs77z9Rus MLkB3qH9ZflfT8H2ZkU1Ge3ElQddNCL4ATkzDcUzUSmxhUOlW2WHrR0HoXysB8/IqB gfyJLLMOM8EvYzHlE2peb3BlSXDzSPxQ1MuvajN0= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Mon, 25 Jan 2021 16:14:43 +0900 Message-Id: <20210125071444.26252-8-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210125071444.26252-1-paul.elder@ideasonboard.com> References: <20210125071444.26252-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 7/8] android: camera_device: Cache 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" The settings in an android capture request may be null, in which case the settings from the most recently submitted capture request should be used. Cache the request settings to achieve this. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- Changes in v4: - use default CameraMetadata constructor for lastSettings_ (so no initialization in CameraDevice constructor) - add todo for incremental caching of android request settings Changes in v3: - rebase on "android: camera device and metatada improvements", so it's a bit cleaner New in v2 --- src/android/camera_device.cpp | 6 +++++- src/android/camera_device.h | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp index 3068f89f..9330db39 100644 --- a/src/android/camera_device.cpp +++ b/src/android/camera_device.cpp @@ -1688,9 +1688,13 @@ 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 */ Camera3RequestDescriptor *descriptor = new Camera3RequestDescriptor(camera_.get(), camera3Request); + /* \todo Set cache incrementally? */ + if (camera3Request->settings) + lastSettings_ = camera3Request->settings; + else + descriptor->settings_ = lastSettings_; LOG(HAL, Debug) << "Queueing Request to libcamera with " << descriptor->numBuffers_ << " HAL streams"; diff --git a/src/android/camera_device.h b/src/android/camera_device.h index 058a3f9a..fa4fb544 100644 --- a/src/android/camera_device.h +++ b/src/android/camera_device.h @@ -134,6 +134,8 @@ private: int orientation_; unsigned int maxJpegBufferSize_; + + CameraMetadata lastSettings_; }; #endif /* __ANDROID_CAMERA_DEVICE_H__ */ From patchwork Mon Jan 25 07:14:44 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10986 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 CCC00C0F2B for ; Mon, 25 Jan 2021 07:15:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 99336682A9; Mon, 25 Jan 2021 08:15:15 +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="lY/aCDV7"; 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 97E89682A0 for ; Mon, 25 Jan 2021 08:15:13 +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 B562E3E; Mon, 25 Jan 2021 08:15:11 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1611558913; bh=IgR5UJ+G7jjzMoUsI1li5ipV+GDLBayA4KDWz1rWgvA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lY/aCDV77nJu54TCIvR403vw1d6X1iwXYVO6e2LdGZPPGUjdhO3Uim1LuCqKn51vJ YW3WkOvujiazUcCkxd0AKhz06i5ARybOBGQwiNNGpcmYB1AXNek2l3JZa0PS8ATAuJ 7yhBvUqJCUwtL+YPNvU68ZsOqGqA7LHTxlpjsL48= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Mon, 25 Jan 2021 16:14:44 +0900 Message-Id: <20210125071444.26252-9-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210125071444.26252-1-paul.elder@ideasonboard.com> References: <20210125071444.26252-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 8/8] android: jpeg: Set thumbnail and JPEG quality based on request 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 thumbnail quality and the JPEG quality based on the android request metadata. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- Changes in v4: - set jpeg quality to 95 by default New in v3 --- src/android/jpeg/encoder.h | 3 ++- src/android/jpeg/encoder_libjpeg.cpp | 10 ++++----- src/android/jpeg/encoder_libjpeg.h | 8 ++++---- src/android/jpeg/post_processor_jpeg.cpp | 26 +++++++++++------------- src/android/jpeg/post_processor_jpeg.h | 1 + 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h index 027233dc..8d449369 100644 --- a/src/android/jpeg/encoder.h +++ b/src/android/jpeg/encoder.h @@ -19,7 +19,8 @@ public: virtual int configure(const libcamera::StreamConfiguration &cfg) = 0; virtual int encode(const libcamera::FrameBuffer &source, libcamera::Span destination, - libcamera::Span exifData) = 0; + libcamera::Span exifData, + unsigned int quality) = 0; }; #endif /* __ANDROID_JPEG_ENCODER_H__ */ diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp index aed919b9..f006e1d1 100644 --- a/src/android/jpeg/encoder_libjpeg.cpp +++ b/src/android/jpeg/encoder_libjpeg.cpp @@ -68,7 +68,6 @@ const struct JPEGPixelFormatInfo &findPixelInfo(const PixelFormat &format) } /* namespace */ EncoderLibJpeg::EncoderLibJpeg() - : quality_(95) { /* \todo Expand error handling coverage with a custom handler. */ compress_.err = jpeg_std_error(&jerr_); @@ -94,7 +93,6 @@ int EncoderLibJpeg::configure(const StreamConfiguration &cfg) compress_.input_components = info.colorSpace == JCS_GRAYSCALE ? 1 : 3; jpeg_set_defaults(&compress_); - jpeg_set_quality(&compress_, quality_, TRUE); pixelFormatInfo_ = &info.pixelFormatInfo; @@ -180,7 +178,7 @@ void EncoderLibJpeg::compressNV(Span frame) } int EncoderLibJpeg::encode(const FrameBuffer &source, Span dest, - Span exifData) + Span exifData, unsigned int quality) { MappedFrameBuffer frame(&source, PROT_READ); if (!frame.isValid()) { @@ -189,15 +187,17 @@ int EncoderLibJpeg::encode(const FrameBuffer &source, Span dest, return frame.error(); } - return encode(frame.maps()[0], dest, exifData); + return encode(frame.maps()[0], dest, exifData, quality); } int EncoderLibJpeg::encode(Span src, Span dest, - Span exifData) + Span exifData, unsigned int quality) { unsigned char *destination = dest.data(); unsigned long size = dest.size(); + jpeg_set_quality(&compress_, quality, TRUE); + /* * The jpeg_mem_dest will reallocate if the required size is not * sufficient. That means the output won't be written to the correct diff --git a/src/android/jpeg/encoder_libjpeg.h b/src/android/jpeg/encoder_libjpeg.h index 070f56f8..838da772 100644 --- a/src/android/jpeg/encoder_libjpeg.h +++ b/src/android/jpeg/encoder_libjpeg.h @@ -23,10 +23,12 @@ public: int configure(const libcamera::StreamConfiguration &cfg) override; int encode(const libcamera::FrameBuffer &source, libcamera::Span destination, - libcamera::Span exifData) override; + libcamera::Span exifData, + unsigned int quality) override; int encode(libcamera::Span source, libcamera::Span destination, - libcamera::Span exifData); + libcamera::Span exifData, + unsigned int quality); private: void compressRGB(libcamera::Span frame); @@ -35,8 +37,6 @@ private: struct jpeg_compress_struct compress_; struct jpeg_error_mgr jerr_; - unsigned int quality_; - const libcamera::PixelFormatInfo *pixelFormatInfo_; bool nv_; diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp index d35fe361..f52a791a 100644 --- a/src/android/jpeg/post_processor_jpeg.cpp +++ b/src/android/jpeg/post_processor_jpeg.cpp @@ -51,6 +51,7 @@ int PostProcessorJpeg::configure(const StreamConfiguration &inCfg, void PostProcessorJpeg::generateThumbnail(const FrameBuffer &source, const Size &targetSize, + unsigned int quality, std::vector *thumbnail) { /* Stores the raw scaled-down thumbnail bytes. */ @@ -71,7 +72,7 @@ void PostProcessorJpeg::generateThumbnail(const FrameBuffer &source, thumbnail->resize(rawThumbnail.size()); int jpeg_size = thumbnailEncoder_.encode(rawThumbnail, - *thumbnail, {}); + *thumbnail, {}, quality); thumbnail->resize(jpeg_size); LOG(JPEG, Debug) @@ -133,20 +134,18 @@ int PostProcessorJpeg::process(const FrameBuffer &source, Size thumbnailSize = { static_cast(data[0]), static_cast(data[1]) }; + ret = requestMetadata.getEntry(ANDROID_JPEG_THUMBNAIL_QUALITY, &entry); + uint8_t quality = ret ? *entry.data.u8 : 95; + resultMetadata->addEntry(ANDROID_JPEG_THUMBNAIL_QUALITY, &quality, 1); + if (thumbnailSize != Size(0, 0)) { std::vector thumbnail; - generateThumbnail(source, thumbnailSize, &thumbnail); + generateThumbnail(source, thumbnailSize, quality, &thumbnail); if (!thumbnail.empty()) 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); @@ -167,7 +166,11 @@ int PostProcessorJpeg::process(const FrameBuffer &source, if (exif.generate() != 0) LOG(JPEG, Error) << "Failed to generate valid EXIF data"; - int jpeg_size = encoder_->encode(source, destination, exif.data()); + ret = requestMetadata.getEntry(ANDROID_JPEG_QUALITY, &entry); + const uint8_t quality = ret ? *entry.data.u8 : 95; + resultMetadata->addEntry(ANDROID_JPEG_QUALITY, &quality, 1); + + int jpeg_size = encoder_->encode(source, destination, exif.data(), quality); if (jpeg_size < 0) { LOG(JPEG, Error) << "Failed to encode stream image"; return jpeg_size; @@ -195,10 +198,5 @@ int PostProcessorJpeg::process(const FrameBuffer &source, /* Update the JPEG result Metadata. */ resultMetadata->addEntry(ANDROID_JPEG_SIZE, &jpeg_size, 1); - /* \todo Configure JPEG encoder with this */ - ret = requestMetadata.getEntry(ANDROID_JPEG_QUALITY, &entry); - const uint32_t jpegQuality = ret ? *entry.data.u8 : 95; - resultMetadata->addEntry(ANDROID_JPEG_QUALITY, &jpegQuality, 1); - return 0; } diff --git a/src/android/jpeg/post_processor_jpeg.h b/src/android/jpeg/post_processor_jpeg.h index 660b79b4..d2dfa450 100644 --- a/src/android/jpeg/post_processor_jpeg.h +++ b/src/android/jpeg/post_processor_jpeg.h @@ -32,6 +32,7 @@ public: private: void generateThumbnail(const libcamera::FrameBuffer &source, const libcamera::Size &targetSize, + unsigned int quality, std::vector *thumbnail); CameraDevice *const cameraDevice_;