/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Google Inc. * * JPEG Post Processor */ #include "post_processor_jpeg.h" #include #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 #include using namespace libcamera; using namespace std::chrono_literals; LOG_DEFINE_CATEGORY(JPEG) 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.size, inCfg.pixelFormat); #if defined(OS_CHROMEOS) encoder_ = std::make_unique(); #else /* !defined(OS_CHROMEOS) */ encoder_ = std::make_unique(); #endif return encoder_->configure(inCfg); } void PostProcessorJpeg::generateThumbnail(const FrameBuffer &source, const Size &targetSize, unsigned int quality, std::vector *thumbnail) { /* Stores the raw scaled-down thumbnail bytes. */ std::vector rawThumbnail; thumbnailer_.createThumbnail(source, targetSize, &rawThumbnail); 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. */ 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> 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"; } } void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBuffer) { ASSERT(encoder_); const FrameBuffer &source = *streamBuffer->srcBuffer; CameraBuffer *destination = streamBuffer->dstBuffer.get(); ASSERT(destination->numPlanes() == 1); const CameraMetadata &requestMetadata = streamBuffer->request->settings_; CameraMetadata *resultMetadata = streamBuffer->request->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); ret = resultMetadata->getEntry(ANDROID_SENSOR_EXPOSURE_TIME, &entry); exif.setExposureTime(ret ? *entry.data.i64 : 0); ret = requestMetadata.getEntry(ANDROID_LENS_APERTURE, &entry); if (ret) exif.setAperture(*entry.data.f); ret = resultMetadata->getEntry(ANDROID_SENSOR_SENSITIVITY, &entry); exif.setISO(ret ? *entry.data.i32 : 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); } 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]) }; 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 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); } 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; } /* 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(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); } -04-26ipa: rkisp1: Move the IPA to the ipa::rkisp1 namespaceJean-Michel Hautbois 2021-04-22libcamera: pipeline: ipu3: Cancel unused buffersKieran Bingham 2021-04-22libcamera: pipeline: rkisp1: Fail RkISP1FrameInfo can't be foundKieran Bingham 2021-04-22libcamera: pipeline: ipu3: frames: Fail if the FrameInfo can't be foundKieran Bingham 2021-04-22libcamera: camera: Assert pipelines complete all requestsKieran Bingham 2021-04-22ipa: ipu3: fix coverity issues in AWBJean-Michel Hautbois 2021-04-22ipa: ipu3: Add support for IPU3 AEC/AGC algorithmJean-Michel Hautbois 2021-04-22ipa: ipu3: Add support for IPU3 AWB algorithmJean-Michel Hautbois 2021-04-22ipa: ipu3: Add a histogram classJean-Michel Hautbois 2021-04-22ipa: Add a common interface for algorithm objectsJean-Michel Hautbois 2021-04-21lc-compliance: simple_capture: Handle unsupported rolesNiklas Söderlund 2021-04-21libcamera: class: Drop 'klass' argument from documentationJacopo Mondi 2021-04-21libcamera: Drop argument from LIBCAMERA_DECLARE_PRIVATEJacopo Mondi 2021-04-20android: CameraDevice: Fix Camera3RequestDescriptor leakageHirokazu Honda 2021-04-20android: CameraDevice: Add stop()Hirokazu Honda 2021-04-20pipeline: raspberrypi: Fix typo in a commentNaushir Patuck 2021-04-17ipa: ipu3: Move the IPA to the ipa::ipu3 namespaceKieran Bingham 2021-04-17libcamera: pipeline: ipu3: Check for failures when loading IPAKieran Bingham 2021-04-17libcamera: bound_method: Please the gcc undefined behaviour sanitizerLaurent Pinchart 2021-04-17libcamera: signal: Fix return value template type of BoundMethodMemberLaurent Pinchart 2021-04-15libcamera: span: Fix reverse iteratorsLaurent Pinchart 2021-04-15test: span: Add tests for begin() and rbegin()Laurent Pinchart 2021-04-15libcamera: camera_sensor: Demote error messageJacopo Mondi 2021-04-15ipa: raspberrypi: cam_helper: Remove duplicate wordsSebastian Fricke 2021-04-15libcamera: log: Use compiler builtins to retrieve file and line numberLaurent Pinchart 2021-04-15libcamera: log: De-duplicate _log() functions and LogMessage constructorLaurent Pinchart 2021-04-15libcamera: pipeline_handler: Fix typo in documentationLaurent Pinchart 2021-04-14libcamera: pipeline: rkisp1: Allow requests to be cancelledNícolas F. R. A. Prado 2021-04-14libcamera: pipeline: rkisp1: Assert empty queuedRequests before clearing fram...Nícolas F. R. A. Prado 2021-04-14libcamera: pipeline: rkisp1: Stop IPA before streamsNícolas F. R. A. Prado 2021-04-14lc-compliance: Drop return value from SimpleCapture::stop()Niklas Söderlund 2021-04-14lc-compliance: Initialize the event loop pointerNiklas Söderlund 2021-04-14pipeline: simple: Fix an issue in breadth-first searchPhi-Bang Nguyen 2021-04-13libcamera: pipeline_handler: Document requestSequence_Kieran Bingham 2021-04-13libcamera: ipa_proxy: Document ProxyStateKieran Bingham 2021-04-13libcamera: ipa_proxy: Scope ProxyState to IPAProxyKieran Bingham 2021-04-12test: threads: Fix memory leakLaurent Pinchart 2021-04-12libcamera: bound_method: Fix type of pack for void methodsLaurent Pinchart 2021-04-12lc-compliance: Add test stopping single stream with requests queuedNiklas Söderlund 2021-04-12lc-compliance: Add a libcamera compliance toolNiklas Söderlund 2021-04-07libcamera: camera_manager: Remove \todo on hotplug/unplug of camerasUmang Jain 2021-04-07libcamera: thread: Fix typo in commentSebastian Fricke