[libcamera-devel] [PATCH 6/6] android: Set result metadata and EXIF fields based on request metadata

Jacopo Mondi jacopo at jmondi.org
Mon Jan 18 10:35:41 CET 2021


Hi Laurent,

On Fri, Jan 15, 2021 at 09:38:24PM +0200, Laurent Pinchart wrote:
> Hi Paul,
>
> Thank you for the patch.
>
> > >  		int ret = cameraStream->process(*buffer, &mapped,
> > > +						descriptor->settings_,
> >
> > I would move:
> > 1) Adding JPEG tags to EXIF
> > 2) Filling in the result metadata
> >
> > to 2 different patches
> >
> > However, I'm a bit skeptic about this change.
> > You pass to the JPEG encode the settings the application has
> > requested. The actual process should go through the pipeline handler,
> > so that you should be able to find all the required information in
> > libcamera::Request::metadata()
> >
> > This requires several parts:
> > 1) Define a libcamera (draft) control to be able to translate the
> > Android metadata to a control the libcamera pipeline handler can
> > consume
> > 2) Set the libcamera control in the Request's metadata
> > 3) Parse the control value in the completion handler here.
> >
> > You can see how (parts of) this process is handled for draft::PipelineDepth.
> >
> > However, it's not trivial to make all the pieces fit together. I have
> > a series of patches I still have to send out that implement exactly
> > this for SCALER_CROP_REGION (except for the fact no ad-hoc property
> > needs to be defined, as we already have
> > libcamera::controls::ScalerCrop)
> >
> > I'm not sure if we should either rush and take this big shortcut here
> > to provide the lens info to the post-processor, or rather hard-code it
> > there with a big \todo, or maybe wait until we close the loop.. I
> > would go for the second option, but I would like to know Laurent's
> > opinion here.
>
> There are Android metadata that are meant only for the HAL, such as all
> the JPEG-related information as we don't handle JPEG encoding in
> libcamera. Passing the request settings to the post-processor does make
> sense.
>
> Looking at the code below, the only metadata entry that needs to be
> handled by libcamera is the lens aperture. Given that we support a
> single aperture at the moment (we report a single entry in
> ANDROID_LENS_INFO_AVAILABLE_APERTURES), hardcoding ANDROID_LENS_APERTURE
> makes sense, we don't need to define libcamera controls yet. However,
> setting ANDROID_LENS_APERTURE doesn't belong to this patch (see below
> for more comments).
>
> Same for ANDROID_LENS_FOCAL_LENGTH, we can hardcode it for now as we
> have a fixed focus lens, but it should go to getResultMetadata() as
> you've mentioned.
>

Agreed with the fixed values.

> > >  						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.
> >
> > Can you move the additional JPEG sizes to the end ?
> >
> >         = 10 entries, 92 bytes
> >
> > However I count:
> >         41 - 33 = +8
> >         We have 10 JPEG metadata so I assume the additional one is for
> >         LENS_FOCAL_LENGTH you add here above
> >
> >         187 - 75 = 112
> >         I count 92 bytes for JPEG + 4 for LENS_FOCAL_LENGHT
> >
> >         Where do the additional 16 bytes come from ?
> >
> > > +	 * 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?
> >
> > Not sure I get this todo :)
> >
> > >  	 */
> > >  	std::unique_ptr<CameraMetadata> resultMetadata =
> > > -		std::make_unique<CameraMetadata>(33, 75);
> > > +		std::make_unique<CameraMetadata>(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<std::unique_ptr<libcamera::FrameBuffer>> frameBuffers_;
> > >  		std::unique_ptr<CaptureRequest> request_;
> > > +		CameraMetadata settings_;
> > >  	};
> > >
> > >  	struct Camera3StreamConfiguration {
> >
> > The part below should go to a separate patch imho
> >
> > > 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<EncoderLibJpeg>();
> > > -
> > >  	return encoder_->configure(inCfg);
> >
> > Unrelated changes ? Should they go to a third patch ?
>
> The introduction of PostProcessorJpeg::configureThumbnailer(), if
> desired, should be split to a patch of its own, yes.
>

I mean the empy line, but yeah, the newly introduce function even more

> > I'll review the rest once separated to a new patch
> >
> > >  }
> > >
> > > @@ -80,17 +90,22 @@ void PostProcessorJpeg::generateThumbnail(const FrameBuffer &source,
> > >
> > >  int PostProcessorJpeg::process(const FrameBuffer &source,
> > >  			       Span<uint8_t> 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);
> >
> > If you intend to handle the ANDROID_JPEG_ORIENTATION control (do you
> > need this?) I think you should combine it with the camera orientation.
> >
> > > +	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<unsigned char> 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);
>
> This doesn't belong to PostProcessorJpeg::process(), only
> exif.setAperture() belongs here. ANDROID_LENS_APERTURE should be set in
> CameraDevice::getResultMetadata().
>
> > > +	}
> > > +	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);
>
> 	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);
> > > +	}
> > > +
>
> As part as the rework of the thumbnail generation, you need to report
> additional sizes in ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES.
>

That's not that linear, you know.. I mean, are the newly sizes fixed ?
in that case it's trivial, but going forward we need to design an
interface to query the post-processor derived class to query it
capabilities at static metadata intialization time.. maybe a few static
parameters would do.

> > > +	ret = requestMetadata.getEntry(ANDROID_JPEG_THUMBNAIL_SIZE, &entry);
> > > +	if (!ret) {
> > > +		/* \todo Handle non-matching aspect ratios */
> > > +		/* \todo Handle non-zero orientations */
>
> Is this required ? As we handle the orientation by setting it in the
> Exif tags, we don't need to rotate the thumbnail, is there anything else
> to handle ?
>
> > > +		const int32_t *data = entry.data.i32;
> > > +		Size thumbnailSize = { static_cast<uint32_t>(data[0]),
> > > +				       static_cast<uint32_t>(data[1]) };
> > > +
> > > +		std::vector<unsigned char> thumbnail;
> > > +		if (thumbnailSize != Size(0, 0)) {
> > > +			configureThumbnailer(thumbnailSize, thumbnailer_.pixelFormat());
> > > +			generateThumbnail(source, &thumbnail);
> > > +		}
> > >  		exif.setThumbnail(thumbnail, Exif::Compression::JPEG);
>
> You shouldn't set a thumbnail at all if the size is { 0, 0 }.
>
> > >
> > > +		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);
>
> If we start acting on ANDROID_JPEG_QUALITY, we should really take it
> into account and configure the JPEG encoder accordingly. This can be
> done on top.
>
> > >
> > > -	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<uint8_t> destination,
> > > -		    CameraMetadata *metadata) override;
> > > +		    const CameraMetadata &requestMetadata,
> > > +		    CameraMetadata *resultMetadata) override;
>
> I'd split this change and the similar one below to a separate patch, it
> will make this patch easier to review.
>
> > >
> > >  private:
> > >  	void generateThumbnail(const libcamera::FrameBuffer &source,
> > >  			       std::vector<unsigned char> *thumbnail);
> > > +	int configureThumbnailer(const libcamera::Size &size,
> > > +				 const libcamera::PixelFormat &pixelFormat);
> > >
> > >  	CameraDevice *const cameraDevice_;
> > >  	std::unique_ptr<Encoder> 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<unsigned char> *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<uint8_t> destination,
> > > -			    CameraMetadata *metadata) = 0;
> > > +			    const CameraMetadata &requestMetadata,
> > > +			    CameraMetadata *resultMetadata) = 0;
> > >  };
> > >
> > >  #endif /* __ANDROID_POST_PROCESSOR_H__ */
>
> --
> Regards,
>
> Laurent Pinchart


More information about the libcamera-devel mailing list