blob: 49bf5e1c03849acaa399582ae06e8fa49c388331 [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* post_processor_jpeg.cpp - JPEG Post Processor
*/
#include "post_processor_jpeg.h"
#include <cstdint>
#include <cstring>
#include <ios>
#include <vector>
#include "../camera_device.h"
#include "../camera_metadata.h"
#include "../camera_request.h"
#if defined(OS_CHROMEOS)
#include "encoder_jea.h"
#else /* !defined(OS_CHROMEOS) */
#include "encoder_libjpeg.h"
#endif
#include "exif.h"
#include <libcamera/base/log.h>
#include <libcamera/control_ids.h>
#include <libcamera/formats.h>
using namespace libcamera;
using namespace std::chrono_literals;
LOG_DEFINE_CATEGORY(JPEG)
namespace {
const size_t kJpegMarkerSize = 2;
const size_t kJpegMetadataLengthSize = 2;
constexpr uint16_t kJpegSOF0 = 0xFFC0;
constexpr uint16_t kJpegSOF2 = 0xFFC2;
constexpr uint16_t kJpegDHT = 0xFFC4;
constexpr uint16_t kJpegRST0 = 0xFFD0;
constexpr uint16_t kJpegRST1 = 0xFFD1;
constexpr uint16_t kJpegRST2 = 0xFFD2;
constexpr uint16_t kJpegRST3 = 0xFFD3;
constexpr uint16_t kJpegRST4 = 0xFFD4;
constexpr uint16_t kJpegRST5 = 0xFFD5;
constexpr uint16_t kJpegRST6 = 0xFFD6;
constexpr uint16_t kJpegRST7 = 0xFFD7;
constexpr uint16_t kJpegSOF = 0xFFD8;
constexpr uint16_t kJpegEOI = 0xFFD9;
constexpr uint16_t kJpegSOS = 0xFFDA;
constexpr uint16_t kJpegDQT = 0xFFDB;
constexpr uint16_t kJpegDRI = 0xFFDD;
constexpr uint16_t kJpegAPP0 = 0xFFE0;
constexpr uint16_t kJpegAPP1 = 0xFFE1;
constexpr uint16_t kJpegAPP2 = 0xFFE2;
constexpr uint16_t kJpegAPP3 = 0xFFE3;
constexpr uint16_t kJpegAPP4 = 0xFFE4;
constexpr uint16_t kJpegAPP5 = 0xFFE5;
constexpr uint16_t kJpegAPP6 = 0xFFE6;
constexpr uint16_t kJpegAPP7 = 0xFFE7;
constexpr uint16_t kJpegAPP8 = 0xFFE8;
constexpr uint16_t kJpegAPP9 = 0xFFE9;
constexpr uint16_t kJpegAPP10 = 0xFFEA;
constexpr uint16_t kJpegAPP11 = 0xFFEB;
constexpr uint16_t kJpegAPP12 = 0xFFEC;
constexpr uint16_t kJpegAPP13 = 0xFFED;
constexpr uint16_t kJpegAPP14 = 0xFFEE;
constexpr uint16_t kJpegAPP15 = 0xFFEF;
constexpr uint16_t kJpegCOM = 0xFFFE;
} // namespace
PostProcessorJpeg::PostProcessorJpeg(CameraDevice *const device)
: cameraDevice_(device)
{
}
int PostProcessorJpeg::configure(const StreamConfiguration &inCfg,
const StreamConfiguration &outCfg)
{
if (inCfg.size != outCfg.size) {
LOG(JPEG, Error) << "Mismatch of input and output stream sizes";
return -EINVAL;
}
if (outCfg.pixelFormat != formats::MJPEG) {
LOG(JPEG, Error) << "Output stream pixel format is not JPEG";
return -EINVAL;
}
streamSize_ = outCfg.size;
thumbnailer_.configure(inCfg, inCfg.pixelFormat);
#if defined(OS_CHROMEOS)
encoder_ = std::make_unique<EncoderJea>();
#else /* !defined(OS_CHROMEOS) */
encoder_ = std::make_unique<EncoderLibJpeg>();
#endif
return encoder_->configure(inCfg);
}
void PostProcessorJpeg::generateThumbnail(const FrameBuffer &source,
const Size &targetSize,
unsigned int quality,
std::vector<unsigned char> *thumbnail)
{
/* Stores the raw scaled-down thumbnail bytes. */
std::vector<unsigned char> rawThumbnail;
thumbnailer_.createThumbnail(source, targetSize, &rawThumbnail);
StreamConfiguration thCfg;
thCfg.size = targetSize;
thCfg.pixelFormat = thumbnailer_.pixelFormat();
thCfg.stride = targetSize.width;
int ret = thumbnailEncoder_.configure(thCfg);
if (!rawThumbnail.empty() && !ret) {
/*
* \todo Avoid value-initialization of all elements of the
* vector.
*/
thumbnail->resize(rawThumbnail.size());
/*
* Split planes manually as the encoder expects a vector of
* planes.
*
* \todo Pass a vector of planes directly to
* Thumbnailer::createThumbnailer above and remove the manual
* planes split from here.
*/
std::vector<Span<uint8_t>> thumbnailPlanes;
const PixelFormatInfo &formatNV12 = PixelFormatInfo::info(formats::NV12);
size_t yPlaneSize = formatNV12.planeSize(targetSize, 0);
size_t uvPlaneSize = formatNV12.planeSize(targetSize, 1);
thumbnailPlanes.push_back({ rawThumbnail.data(), yPlaneSize });
thumbnailPlanes.push_back({ rawThumbnail.data() + yPlaneSize, uvPlaneSize });
int jpeg_size = thumbnailEncoder_.encode(thumbnailPlanes,
*thumbnail, {}, quality);
thumbnail->resize(jpeg_size);
LOG(JPEG, Debug)
<< "Thumbnail compress returned "
<< jpeg_size << " bytes";
}
}
inline uint16_t parseWord(uint8_t *addr)
{
return (*addr << 8) + *(addr + 1);
}
inline uint8_t *writeTwoBytes(uint8_t *dst, uint16_t value)
{
dst[0] = (value >> 8) & 0xFF;
dst[1] = value & 0xFF;
return dst + 2;
}
/**
* \brief Inserts JPEG app segments into existing JPEG blob.
* \param[in,out] jpegBlob The JPEG blob
* \param[in] jpegSize Current JPEG size (not buffer size)
* \param[in] metadata Metadata that may contains the JPEG app segments
*
* If buffer size is not enough, then not all app segments will be inserted.
*
* \return JPEG size after inserting APP segments.
*/
int PostProcessorJpeg::insertAppSegments(
libcamera::Span<uint8_t> jpegBlob, int jpegSize,
const libcamera::ControlList &metadata)
{
const auto &appSegmentLengthCtrl =
metadata.get(controls::JpegApplicationSegmentLength);
if (!appSegmentLengthCtrl.has_value()) {
return jpegSize;
}
const auto &appSegmentLength = *appSegmentLengthCtrl;
const auto &appSegmentArray =
metadata.get(controls::JpegApplicationSegmentContent);
if (!appSegmentArray.has_value()) {
LOG(JPEG, Error)
<< "JpegApplicationSegmentLength was not empty but "
<< "JpegApplicationSegmentContent is empty";
return jpegSize;
}
size_t totalLength = 0;
for (const auto &length : appSegmentLength) {
totalLength += length;
}
if (totalLength != appSegmentArray->size()) {
LOG(JPEG, Error) << "JPEG app segment ctrls have inconsistent "
<< "length. JpegApplicationSegmentLength: "
<< totalLength
<< ". JpegApplicationSegmentContent: "
<< appSegmentArray->size();
return jpegSize;
}
// Verify APP0 and APP1 are not there
if (appSegmentLength[0] != 0 || appSegmentLength[1] != 0) {
LOG(JPEG, Error) << "Libcamera reserves APP0 and APP1!";
return jpegSize;
}
int inputAppSegNum = 0;
// Start from APP2.
for (int i = 2; i < 16; i++) {
if (appSegmentLength[i] != 0) {
inputAppSegNum = i;
break;
}
}
uint8_t *inputAppSegPtr =
const_cast<uint8_t *>(appSegmentArray->data());
const uint8_t *inputAppSegEnd = appSegmentArray->end();
uint8_t *jpegRemainingAppSegPtr = nullptr;
uint8_t *jpegPtr = jpegBlob.begin();
uint8_t *jpegEnd = jpegBlob.begin() + jpegSize;
const size_t bufferSize = jpegBlob.size();
while (jpegPtr < jpegEnd) {
size_t nextAppSegmentSize = appSegmentLength[inputAppSegNum] +
kJpegMarkerSize +
kJpegMetadataLengthSize;
if (jpegSize + nextAppSegmentSize > bufferSize) {
LOG(JPEG, Warning) << "Not enough JPEG buffer for "
<< "remaining app segments.";
return jpegSize;
}
std::stringstream sstream;
sstream << " at " << std::hex << (jpegPtr - jpegBlob.begin());
const std::string logSuffix = sstream.str();
if (jpegPtr + kJpegMarkerSize > jpegEnd) {
LOG(JPEG, Error) << "Incomplete marker" << logSuffix;
return jpegSize;
}
uint16_t marker = parseWord(jpegPtr);
switch (marker) {
case kJpegSOF:
case kJpegRST0:
case kJpegRST1:
case kJpegRST2:
case kJpegRST3:
case kJpegRST4:
case kJpegRST5:
case kJpegRST6:
case kJpegRST7:
case kJpegEOI:
// Skip the marker as there's no payload.
jpegPtr += kJpegMarkerSize;
break;
case kJpegSOF0:
case kJpegSOF2:
case kJpegDHT:
case kJpegDQT:
case kJpegDRI:
case kJpegSOS:
if (jpegRemainingAppSegPtr == nullptr &&
inputAppSegPtr != inputAppSegEnd) {
// Maybe we'll have to insert all remaining
// segment here.
jpegRemainingAppSegPtr = jpegPtr;
}
if (jpegPtr + kJpegMarkerSize + kJpegMetadataLengthSize > jpegEnd) {
LOG(JPEG, Error) << "Invalid JPEG header"
<< logSuffix;
return jpegSize;
}
jpegPtr += (kJpegMarkerSize + parseWord(jpegPtr + kJpegMarkerSize));
break;
case kJpegAPP0:
case kJpegAPP1:
case kJpegAPP2:
case kJpegAPP3:
case kJpegAPP4:
case kJpegAPP5:
case kJpegAPP6:
case kJpegAPP7:
case kJpegAPP8:
case kJpegAPP9:
case kJpegAPP10:
case kJpegAPP11:
case kJpegAPP12:
case kJpegAPP13:
case kJpegAPP14:
case kJpegAPP15:
case kJpegCOM: {
if (jpegPtr + kJpegMarkerSize + kJpegMetadataLengthSize > jpegEnd) {
LOG(JPEG, Error) << "Invalid JPEG header"
<< logSuffix;
return jpegSize;
}
size_t segmentSize =
kJpegMarkerSize + parseWord(jpegPtr + kJpegMarkerSize);
if (jpegPtr + segmentSize > jpegEnd) {
LOG(JPEG, Error) << "Invalid JPEG header"
<< logSuffix;
return jpegSize;
}
int readAppSegmentIdx = marker - kJpegAPP0;
if (readAppSegmentIdx >= inputAppSegNum) {
if (readAppSegmentIdx == inputAppSegNum) {
LOG(JPEG, Warning) << "Skipping APP" << readAppSegmentIdx
<< " from metadata, prioritizing the one"
<< " from encoder.";
} else {
// Insert BEFORE the already
// existing app segment.
size_t insertSegmentSize =
appSegmentLength[inputAppSegNum] +
kJpegMarkerSize +
kJpegMetadataLengthSize;
uint8_t *insertBegin = jpegPtr;
jpegPtr += insertSegmentSize;
// Move
size_t moveSize = static_cast<size_t>(
jpegEnd - insertBegin + 1);
std::memmove(jpegPtr, insertBegin, moveSize);
jpegSize += insertSegmentSize;
jpegEnd += insertSegmentSize;
// Copy the app segment in
writeAppSegment(inputAppSegNum,
insertBegin,
inputAppSegPtr,
appSegmentLength[inputAppSegNum]);
inputAppSegPtr += appSegmentLength[inputAppSegNum];
inputAppSegNum++;
}
for (; inputAppSegNum < 16; inputAppSegNum++) {
if (appSegmentLength[inputAppSegNum] > 0) {
break;
}
}
}
jpegPtr += segmentSize;
break;
}
default:
LOG(JPEG, Error) << "Invalid JPEG marker: 0x"
<< std::hex << marker
<< logSuffix;
return jpegSize;
}
// Assuming that the APPn markers always appear before SOS.
if (marker == kJpegSOS || marker == kJpegEOI) {
break;
}
}
if (inputAppSegPtr != inputAppSegEnd) {
if (jpegRemainingAppSegPtr == nullptr) {
LOG(JPEG, Error) << "Invalid JPEG format";
return jpegSize;
}
size_t reservedAppSegSize = 0;
int maxSegmentIdx = 0;
for (int i = inputAppSegNum; i < 16; i++) {
if (appSegmentLength[i] != 0) {
reservedAppSegSize += appSegmentLength[i] +
kJpegMarkerSize +
kJpegMetadataLengthSize;
if (jpegSize + reservedAppSegSize > bufferSize) {
LOG(JPEG, Warning)
<< "Not enough JPEG buffer for "
<< "all app segments.";
break;
}
maxSegmentIdx = i;
}
}
size_t moveSize = static_cast<size_t>(jpegEnd - jpegRemainingAppSegPtr + 1);
std::memmove(jpegRemainingAppSegPtr + reservedAppSegSize,
jpegRemainingAppSegPtr, moveSize);
std::vector<uint8_t> headerBuffer(kJpegMarkerSize + kJpegMetadataLengthSize);
for (; inputAppSegNum <= maxSegmentIdx; inputAppSegNum++) {
if (appSegmentLength[inputAppSegNum] == 0) {
continue;
}
writeAppSegment(inputAppSegNum, jpegRemainingAppSegPtr,
inputAppSegPtr,
appSegmentLength[inputAppSegNum]);
inputAppSegPtr += appSegmentLength[inputAppSegNum];
jpegRemainingAppSegPtr +=
headerBuffer.size() +
appSegmentLength[inputAppSegNum];
}
jpegSize += reservedAppSegSize;
}
return jpegSize;
}
void PostProcessorJpeg::process(StreamBuffer *streamBuffer)
{
ASSERT(encoder_);
const FrameBuffer &source = *streamBuffer->srcBuffer;
CameraBuffer *destination = streamBuffer->dstBuffer.get();
const std::optional<StreamBuffer::JpegExifMetadata> &jpegExifMetadata =
streamBuffer->jpegExifMetadata;
ASSERT(destination->numPlanes() == 1);
ASSERT(jpegExifMetadata.has_value());
const CameraMetadata &requestMetadata = streamBuffer->request->settings_;
CameraMetadata *resultMetadata = streamBuffer->result->resultMetadata_.get();
camera_metadata_ro_entry_t entry;
int ret;
/* Set EXIF metadata for various tags. */
Exif exif;
exif.setMake(cameraDevice_->maker());
exif.setModel(cameraDevice_->model());
ret = requestMetadata.getEntry(ANDROID_JPEG_ORIENTATION, &entry);
const uint32_t jpegOrientation = ret ? *entry.data.i32 : 0;
resultMetadata->addEntry(ANDROID_JPEG_ORIENTATION, jpegOrientation);
exif.setOrientation(jpegOrientation);
exif.setSize(streamSize_);
/*
* We set the frame's EXIF timestamp as the time of encode.
* Since the precision we need for EXIF timestamp is only one
* second, it is good enough.
*/
exif.setTimestamp(std::time(nullptr), 0ms);
/* Exif requires nsec for exposure time */
exif.setExposureTime(jpegExifMetadata->sensorExposureTime * 1000);
exif.setISO(jpegExifMetadata->sensorSensitivityISO);
ret = requestMetadata.getEntry(ANDROID_LENS_APERTURE, &entry);
if (ret)
exif.setAperture(*entry.data.f);
exif.setFlash(Exif::Flash::FlashNotPresent);
exif.setWhiteBalance(Exif::WhiteBalance::Auto);
exif.setFocalLength(jpegExifMetadata->lensFocalLength);
ret = requestMetadata.getEntry(ANDROID_JPEG_GPS_TIMESTAMP, &entry);
if (ret) {
exif.setGPSDateTimestamp(*entry.data.i64);
resultMetadata->addEntry(ANDROID_JPEG_GPS_TIMESTAMP,
*entry.data.i64);
}
ret = requestMetadata.getEntry(ANDROID_JPEG_THUMBNAIL_SIZE, &entry);
if (ret) {
const int32_t *data = entry.data.i32;
Size thumbnailSize = { static_cast<uint32_t>(data[0]),
static_cast<uint32_t>(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);
if (thumbnailSize != Size(0, 0)) {
std::vector<unsigned char> thumbnail;
generateThumbnail(source, thumbnailSize, quality, &thumbnail);
if (!thumbnail.empty())
exif.setThumbnail(std::move(thumbnail), Exif::Compression::JPEG);
}
resultMetadata->addEntry(ANDROID_JPEG_THUMBNAIL_SIZE, data, 2);
}
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);
}
ret = requestMetadata.getEntry(ANDROID_CONTROL_AE_MODE, &entry);
if (ret) {
if (entry.data.u8[0] == ANDROID_CONTROL_AE_MODE_OFF) {
exif.setShort(EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_PROGRAM, 1);
exif.setShort(EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_MODE, 2);
} else {
exif.setShort(EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_PROGRAM, 2);
exif.setShort(EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_MODE, 1);
}
}
ret = requestMetadata.getEntry(ANDROID_SENSOR_EXPOSURE_TIME, &entry);
if (ret && entry.data.i64[0] > 0) {
// us -> second
double tv = static_cast<double>(entry.data.i64[0]) / 1000000;
// exposure time -> shutter speed
tv = -1.0 * (log(tv) / log(2.0));
ExifRational rational = { static_cast<ExifLong>(tv * 65536), 65536};
exif.setRational(EXIF_IFD_EXIF, EXIF_TAG_SHUTTER_SPEED_VALUE, rational);
} else {
ExifRational rational = { static_cast<ExifLong>(0), 1};
exif.setRational(EXIF_IFD_EXIF, EXIF_TAG_SHUTTER_SPEED_VALUE, rational);
}
if (exif.generate() != 0)
LOG(JPEG, Error) << "Failed to generate valid EXIF data";
ret = requestMetadata.getEntry(ANDROID_JPEG_QUALITY, &entry);
const uint8_t quality = ret ? *entry.data.u8 : 95;
resultMetadata->addEntry(ANDROID_JPEG_QUALITY, quality);
int jpeg_size = encoder_->encode(streamBuffer, exif.data(), quality);
if (jpeg_size < 0) {
LOG(JPEG, Error) << "Failed to encode stream image";
processComplete.emit(streamBuffer, PostProcessor::Status::Error);
return;
}
jpeg_size = insertAppSegments(destination->plane(0), jpeg_size,
streamBuffer->request->request_->metadata());
/* Fill in the JPEG blob header. */
uint8_t *resultPtr = destination->plane(0).data()
+ destination->jpegBufferSize(cameraDevice_->maxJpegBufferSize())
- sizeof(struct camera3_jpeg_blob);
auto *blob = reinterpret_cast<struct camera3_jpeg_blob *>(resultPtr);
blob->jpeg_blob_id = CAMERA3_JPEG_BLOB_ID;
blob->jpeg_size = jpeg_size;
/* Update the JPEG result Metadata. */
resultMetadata->addEntry(ANDROID_JPEG_SIZE, jpeg_size);
processComplete.emit(streamBuffer, PostProcessor::Status::Success);
}
void PostProcessorJpeg::writeAppSegment(int appSegmentIdx, uint8_t *dest,
uint8_t *src, size_t size)
{
LOG(JPEG, Info) << "Write APP" << appSegmentIdx;
std::vector<uint8_t> header(kJpegMarkerSize + kJpegMetadataLengthSize);
writeTwoBytes(header.data(), kJpegAPP0 + appSegmentIdx);
writeTwoBytes(header.data() + kJpegMarkerSize,
kJpegMetadataLengthSize + size);
std::memcpy(dest, header.data(), header.size());
std::memcpy(dest + header.size(), src, size);
}