[libcamera-devel,v3,3/4] Rework JPEG encoder API and update PostProcessorJpeg and EncoderLibJpeg
diff mbox series

Message ID 20220426091409.1352047-4-chenghaoyang@chromium.org
State Superseded
Headers show
Series
  • Add CrOS JEA implementation
Related show

Commit Message

Cheng-Hao Yang April 26, 2022, 9:14 a.m. UTC
To add JEA in the next CL, this CL reworks JPEG encoder API and move
thumbnail encoding code into EncoderLibJpeg's implementation, as JEA
supports that as well.

Signed-off-by: Harvey Yang <chenghaoyang at chromium.org>
---
 src/android/jpeg/encoder.h                    |  9 ++-
 src/android/jpeg/encoder_libjpeg.cpp          | 70 +++++++++++++++++++
 src/android/jpeg/encoder_libjpeg.h            | 21 +++++-
 .../jpeg/generic_post_processor_jpeg.cpp      | 14 ++++
 src/android/jpeg/meson.build                  |  9 +++
 src/android/jpeg/post_processor_jpeg.cpp      | 60 ++--------------
 src/android/jpeg/post_processor_jpeg.h        | 11 +--
 src/android/meson.build                       |  5 +-
 8 files changed, 128 insertions(+), 71 deletions(-)
 create mode 100644 src/android/jpeg/generic_post_processor_jpeg.cpp
 create mode 100644 src/android/jpeg/meson.build

Comments

Laurent Pinchart April 26, 2022, 11:44 p.m. UTC | #1
Hi Harvey,

Thank you for the patch.

On Tue, Apr 26, 2022 at 09:14:08AM +0000, Harvey Yang via libcamera-devel wrote:
> To add JEA in the next CL, this CL reworks JPEG encoder API and move

Same comment as in 1/4 for "CL" (I won't repeat it for the other patches
in this series, please update them as applicable).

> thumbnail encoding code into EncoderLibJpeg's implementation, as JEA
> supports that as well.
> 
> Signed-off-by: Harvey Yang <chenghaoyang at chromium.org>

The e-mail address should use "@" instead of " at ".

> ---
>  src/android/jpeg/encoder.h                    |  9 ++-
>  src/android/jpeg/encoder_libjpeg.cpp          | 70 +++++++++++++++++++
>  src/android/jpeg/encoder_libjpeg.h            | 21 +++++-
>  .../jpeg/generic_post_processor_jpeg.cpp      | 14 ++++
>  src/android/jpeg/meson.build                  |  9 +++
>  src/android/jpeg/post_processor_jpeg.cpp      | 60 ++--------------
>  src/android/jpeg/post_processor_jpeg.h        | 11 +--
>  src/android/meson.build                       |  5 +-

This is more reviewable than in the previous version, but there are
still quite a few changes bundled in the same patch. As a v4 will be
needed anyway, could you split this further as follows ? Feel free to
change the order as appropriate.

- Add meson.build file in jpeg/ directory
- Move thumbnail generate to Encoder class
- Pass streamBuffer to encode() to prepare for JPEG encoders that need
  access to the HAL buffer handle
- Add PostProcessorJpeg::SetEncoder()

>  8 files changed, 128 insertions(+), 71 deletions(-)
>  create mode 100644 src/android/jpeg/generic_post_processor_jpeg.cpp
>  create mode 100644 src/android/jpeg/meson.build
> 
> diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h
> index b974d367..6d527d91 100644
> --- a/src/android/jpeg/encoder.h
> +++ b/src/android/jpeg/encoder.h
> @@ -12,14 +12,19 @@
>  #include <libcamera/framebuffer.h>
>  #include <libcamera/stream.h>
>  
> +#include "../camera_request.h"
> +
>  class Encoder
>  {
>  public:
>  	virtual ~Encoder() = default;
>  
>  	virtual int configure(const libcamera::StreamConfiguration &cfg) = 0;
> -	virtual int encode(const libcamera::FrameBuffer &source,
> -			   libcamera::Span<uint8_t> destination,
> +	virtual int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
>  			   libcamera::Span<const uint8_t> exifData,
>  			   unsigned int quality) = 0;
> +	virtual int generateThumbnail(const libcamera::FrameBuffer &source,
> +				      const libcamera::Size &targetSize,
> +				      unsigned int quality,
> +				      std::vector<unsigned char> *thumbnail) = 0;
>  };
> diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp
> index 21a3b33d..b5591e33 100644
> --- a/src/android/jpeg/encoder_libjpeg.cpp
> +++ b/src/android/jpeg/encoder_libjpeg.cpp
> @@ -24,6 +24,8 @@
>  #include "libcamera/internal/formats.h"
>  #include "libcamera/internal/mapped_framebuffer.h"
>  
> +#include "../camera_buffer.h"
> +
>  using namespace libcamera;
>  
>  LOG_DECLARE_CATEGORY(JPEG)
> @@ -82,8 +84,17 @@ EncoderLibJpeg::~EncoderLibJpeg()
>  }
>  
>  int EncoderLibJpeg::configure(const StreamConfiguration &cfg)
> +{
> +	thumbnailer_.configure(cfg.size, cfg.pixelFormat);
> +	cfg_ = cfg;

I'd store the size and pixel format only, not the full
StreamConfiguration, as they are all you need. internalConfigure()
should then take a size and pixel format, which will make it easier to
use in generateThumbnail().

> +
> +	return internalConfigure(cfg);
> +}
> +
> +int EncoderLibJpeg::internalConfigure(const StreamConfiguration &cfg)

Let's name the function configureEncoder().

>  {
>  	const struct JPEGPixelFormatInfo info = findPixelInfo(cfg.pixelFormat);
> +
>  	if (info.colorSpace == JCS_UNKNOWN)
>  		return -ENOTSUP;
>  
> @@ -178,6 +189,65 @@ void EncoderLibJpeg::compressNV(const std::vector<Span<uint8_t>> &planes)
>  	}
>  }
>  
> +int EncoderLibJpeg::encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
> +			   libcamera::Span<const uint8_t> exifData,
> +			   unsigned int quality)
> +{
> +	internalConfigure(cfg_);

cfg_ is only used here, I suppose to reconfigure the libjpeg encoder for
the

> +	return encode(*streamBuffer->srcBuffer, streamBuffer->dstBuffer.get()->plane(0), exifData, quality);

Let's shorten the line a bit (we aim for 80 columns as a soft target)

	return encode(*streamBuffer->srcBuffer, streamBuffer->dstBuffer.get()->plane(0),
		      exifData, quality);

> +}
> +
> +int EncoderLibJpeg::generateThumbnail(
> +	const libcamera::FrameBuffer &source,
> +	const libcamera::Size &targetSize,
> +	unsigned int quality,
> +	std::vector<unsigned char> *thumbnail)

int EncoderLibJpeg::generateThumbnail(const libcamera::FrameBuffer &source,
				      const libcamera::Size &targetSize,
				      unsigned int quality,
				      std::vector<unsigned char> *thumbnail)

to match the coding style.

> +{
> +	/* 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();
> +	int ret = internalConfigure(thCfg);

You now use the same libjpeg encoder instance for both the main image
and the thumbnail. This leads to EncoderLibJpeg::internalConfigure()
being called twice for every frame. The function looks fairly cheap so
it should be fine, but could you confirm that jpeg_set_defaults()
doesn't cause a large overhead ? Please mention this change in the
commit message of the appropriate patch, it's an important one.

> +
> +	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 = encode(thumbnailPlanes, *thumbnail, {}, quality);
> +		thumbnail->resize(jpeg_size);
> +
> +		LOG(JPEG, Debug)
> +			<< "Thumbnail compress returned "
> +			<< jpeg_size << " bytes";
> +
> +		return jpeg_size;
> +	}
> +
> +	return -1;
> +}
> +
>  int EncoderLibJpeg::encode(const FrameBuffer &source, Span<uint8_t> dest,
>  			   Span<const uint8_t> exifData, unsigned int quality)
>  {
> diff --git a/src/android/jpeg/encoder_libjpeg.h b/src/android/jpeg/encoder_libjpeg.h
> index 1b3ac067..56b27bae 100644
> --- a/src/android/jpeg/encoder_libjpeg.h
> +++ b/src/android/jpeg/encoder_libjpeg.h
> @@ -15,6 +15,8 @@
>  
>  #include <jpeglib.h>
>  
> +#include "thumbnailer.h"
> +
>  class EncoderLibJpeg : public Encoder
>  {
>  public:
> @@ -22,19 +24,32 @@ public:
>  	~EncoderLibJpeg();
>  
>  	int configure(const libcamera::StreamConfiguration &cfg) override;
> +	int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
> +		   libcamera::Span<const uint8_t> exifData,
> +		   unsigned int quality) override;
> +	int generateThumbnail(
> +		const libcamera::FrameBuffer &source,
> +		const libcamera::Size &targetSize,
> +		unsigned int quality,
> +		std::vector<unsigned char> *thumbnail) override;

Same here.

> +
> +private:
> +	int internalConfigure(const libcamera::StreamConfiguration &cfg);
> +
>  	int encode(const libcamera::FrameBuffer &source,
>  		   libcamera::Span<uint8_t> destination,
>  		   libcamera::Span<const uint8_t> exifData,
> -		   unsigned int quality) override;
> +		   unsigned int quality);
>  	int encode(const std::vector<libcamera::Span<uint8_t>> &planes,
>  		   libcamera::Span<uint8_t> destination,
>  		   libcamera::Span<const uint8_t> exifData,
>  		   unsigned int quality);
>  
> -private:
>  	void compressRGB(const std::vector<libcamera::Span<uint8_t>> &planes);
>  	void compressNV(const std::vector<libcamera::Span<uint8_t>> &planes);
>  
> +	libcamera::StreamConfiguration cfg_;
> +
>  	struct jpeg_compress_struct compress_;
>  	struct jpeg_error_mgr jerr_;
>  
> @@ -42,4 +57,6 @@ private:
>  
>  	bool nv_;
>  	bool nvSwap_;
> +
> +	Thumbnailer thumbnailer_;
>  };
> diff --git a/src/android/jpeg/generic_post_processor_jpeg.cpp b/src/android/jpeg/generic_post_processor_jpeg.cpp
> new file mode 100644
> index 00000000..890f6972
> --- /dev/null
> +++ b/src/android/jpeg/generic_post_processor_jpeg.cpp
> @@ -0,0 +1,14 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2022, Google Inc.
> + *
> + * generic_post_processor_jpeg.cpp - Generic JPEG Post Processor
> + */
> +
> +#include "encoder_libjpeg.h"
> +#include "post_processor_jpeg.h"
> +
> +void PostProcessorJpeg::SetEncoder()

We use camelCase for function names, not CamelCase. Let's name the
function createEncoder(), as it doesn't just set it but actually creates
it.

> +{
> +	encoder_ = std::make_unique<EncoderLibJpeg>();

I'm tempted to simplify this by using conditional compilation (we have a
OS_CHROMEOS macro), the function could then go to
post_processor_jpeg.cpp.

> +}
> diff --git a/src/android/jpeg/meson.build b/src/android/jpeg/meson.build
> new file mode 100644
> index 00000000..94522dc0
> --- /dev/null
> +++ b/src/android/jpeg/meson.build
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: CC0-1.0
> +
> +android_hal_sources += files([
> +    'exif.cpp',
> +    'post_processor_jpeg.cpp'])

The '])' should be on the next line.

> +
> +android_hal_sources += files(['encoder_libjpeg.cpp',
> +                              'generic_post_processor_jpeg.cpp',
> +                              'thumbnailer.cpp'])

Similar comment here,

android_hal_sources += files([
    'encoder_libjpeg.cpp',
    'generic_post_processor_jpeg.cpp',
    'thumbnailer.cpp',
])

> diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp
> index d72ebc3c..7ceba60e 100644
> --- a/src/android/jpeg/post_processor_jpeg.cpp
> +++ b/src/android/jpeg/post_processor_jpeg.cpp
> @@ -9,10 +9,10 @@
>  
>  #include <chrono>
>  
> +#include "../android_framebuffer.h"

Is this needed ?

>  #include "../camera_device.h"
>  #include "../camera_metadata.h"
>  #include "../camera_request.h"
> -#include "encoder_libjpeg.h"
>  #include "exif.h"
>  
>  #include <libcamera/base/log.h>
> @@ -44,60 +44,11 @@ int PostProcessorJpeg::configure(const StreamConfiguration &inCfg,
>  
>  	streamSize_ = outCfg.size;
>  
> -	thumbnailer_.configure(inCfg.size, inCfg.pixelFormat);
> -
> -	encoder_ = std::make_unique<EncoderLibJpeg>();
> +	SetEncoder();
>  
>  	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();
> -	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";
> -	}
> -}
> -
>  void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBuffer)
>  {
>  	ASSERT(encoder_);
> @@ -164,8 +115,8 @@ void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
>  
>  		if (thumbnailSize != Size(0, 0)) {
>  			std::vector<unsigned char> thumbnail;
> -			generateThumbnail(source, thumbnailSize, quality, &thumbnail);
> -			if (!thumbnail.empty())
> +			ret = encoder_->generateThumbnail(source, thumbnailSize, quality, &thumbnail);
> +			if (ret > 0 && !thumbnail.empty())
>  				exif.setThumbnail(thumbnail, Exif::Compression::JPEG);
>  		}
>  
> @@ -194,8 +145,7 @@ void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
>  	const uint8_t quality = ret ? *entry.data.u8 : 95;
>  	resultMetadata->addEntry(ANDROID_JPEG_QUALITY, quality);
>  
> -	int jpeg_size = encoder_->encode(source, destination->plane(0),
> -					 exif.data(), 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);
> diff --git a/src/android/jpeg/post_processor_jpeg.h b/src/android/jpeg/post_processor_jpeg.h
> index 98309b01..a09f8798 100644
> --- a/src/android/jpeg/post_processor_jpeg.h
> +++ b/src/android/jpeg/post_processor_jpeg.h
> @@ -8,11 +8,11 @@
>  #pragma once
>  
>  #include "../post_processor.h"
> -#include "encoder_libjpeg.h"
> -#include "thumbnailer.h"
>  
>  #include <libcamera/geometry.h>
>  
> +#include "encoder.h"
> +
>  class CameraDevice;
>  
>  class PostProcessorJpeg : public PostProcessor
> @@ -25,14 +25,9 @@ public:
>  	void process(Camera3RequestDescriptor::StreamBuffer *streamBuffer) override;
>  
>  private:
> -	void generateThumbnail(const libcamera::FrameBuffer &source,
> -			       const libcamera::Size &targetSize,
> -			       unsigned int quality,
> -			       std::vector<unsigned char> *thumbnail);
> +	void SetEncoder();
>  
>  	CameraDevice *const cameraDevice_;
>  	std::unique_ptr<Encoder> encoder_;
>  	libcamera::Size streamSize_;
> -	EncoderLibJpeg thumbnailEncoder_;
> -	Thumbnailer thumbnailer_;
>  };
> diff --git a/src/android/meson.build b/src/android/meson.build
> index 27be27bb..026b8b3c 100644
> --- a/src/android/meson.build
> +++ b/src/android/meson.build
> @@ -48,10 +48,6 @@ android_hal_sources = files([
>      'camera_ops.cpp',
>      'camera_request.cpp',
>      'camera_stream.cpp',
> -    'jpeg/encoder_libjpeg.cpp',
> -    'jpeg/exif.cpp',
> -    'jpeg/post_processor_jpeg.cpp',
> -    'jpeg/thumbnailer.cpp',
>      'yuv/post_processor_yuv.cpp'
>  ])
>  
> @@ -59,6 +55,7 @@ android_cpp_args = []
>  
>  subdir('cros')
>  subdir('mm')
> +subdir('jpeg')

Please keep these alphabetically sorted.

>  
>  android_camera_metadata_sources = files([
>      'metadata/camera_metadata.c',
Cheng-Hao Yang May 3, 2022, 3:29 a.m. UTC | #2
Hi Laurent,

Sorry I forgot to send this reply before going on vacation...

> The e-mail address should use "@" instead of " at ".
Oh I see! I checked the libcamera archives for examples, and thought
that was intentional haha.
Updated.

> cfg_ is only used here, I suppose to reconfigure the libjpeg encoder for
the
Is this a deprecated comment?

> >       int configure(const libcamera::StreamConfiguration &cfg) override;
> > +     int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
> > +                libcamera::Span<const uint8_t> exifData,
> > +                unsigned int quality) override;
> > +     int generateThumbnail(
> > +             const libcamera::FrameBuffer &source,
> > +             const libcamera::Size &targetSize,
> > +             unsigned int quality,
> > +             std::vector<unsigned char> *thumbnail) override;

> Same here.
I think I need your help on the indentation format. Any doc or rule I can
study? Thanks!

> I'm tempted to simplify this by using conditional compilation (we have a
OS_CHROMEOS macro), the function could then go to post_processor_jpeg.cpp.
Oh I didn't notice there's such a macro to use. Thanks! I think we can get
rid of the PostProcessorJpeg::createEncoder() then.

> You now use the same libjpeg encoder instance for both the main image and
the thumbnail. This leads to EncoderLibJpeg::internalConfigure() being
called twice for every frame. The function looks fairly cheap so it should
be fine, but could you confirm that jpeg_set_defaults() doesn't cause a
large overhead ? Please mention this change in the commit message of the
appropriate patch, it's an important one.
Originally for each frame we need at least one call to jpeg_set_defaults()
to encode the thumbnail, and we need two now with patch v3, so I thought it
was fine in terms of the complexity, while you're right that I'm not sure
about the complexity of jpeg_set_defaults(), even looking into the
implementation [1]. I can also modify the code not to configure twice per
frame. WDYT?

[1]: https://github.com/kornelski/libjpeg/blob/master/jcparam.c#L268


On Wed, Apr 27, 2022 at 7:44 AM Laurent Pinchart <
laurent.pinchart@ideasonboard.com> wrote:

> Hi Harvey,
>
> Thank you for the patch.
>
> On Tue, Apr 26, 2022 at 09:14:08AM +0000, Harvey Yang via libcamera-devel
> wrote:
> > To add JEA in the next CL, this CL reworks JPEG encoder API and move
>
> Same comment as in 1/4 for "CL" (I won't repeat it for the other patches
> in this series, please update them as applicable).
>
> > thumbnail encoding code into EncoderLibJpeg's implementation, as JEA
> > supports that as well.
> >
> > Signed-off-by: Harvey Yang <chenghaoyang at chromium.org>
>
> The e-mail address should use "@" instead of " at ".
>
> > ---
> >  src/android/jpeg/encoder.h                    |  9 ++-
> >  src/android/jpeg/encoder_libjpeg.cpp          | 70 +++++++++++++++++++
> >  src/android/jpeg/encoder_libjpeg.h            | 21 +++++-
> >  .../jpeg/generic_post_processor_jpeg.cpp      | 14 ++++
> >  src/android/jpeg/meson.build                  |  9 +++
> >  src/android/jpeg/post_processor_jpeg.cpp      | 60 ++--------------
> >  src/android/jpeg/post_processor_jpeg.h        | 11 +--
> >  src/android/meson.build                       |  5 +-
>
> This is more reviewable than in the previous version, but there are
> still quite a few changes bundled in the same patch. As a v4 will be
> needed anyway, could you split this further as follows ? Feel free to
> change the order as appropriate.
>
> - Add meson.build file in jpeg/ directory
> - Move thumbnail generate to Encoder class
> - Pass streamBuffer to encode() to prepare for JPEG encoders that need
>   access to the HAL buffer handle
> - Add PostProcessorJpeg::SetEncoder()
>
> >  8 files changed, 128 insertions(+), 71 deletions(-)
> >  create mode 100644 src/android/jpeg/generic_post_processor_jpeg.cpp
> >  create mode 100644 src/android/jpeg/meson.build
> >
> > diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h
> > index b974d367..6d527d91 100644
> > --- a/src/android/jpeg/encoder.h
> > +++ b/src/android/jpeg/encoder.h
> > @@ -12,14 +12,19 @@
> >  #include <libcamera/framebuffer.h>
> >  #include <libcamera/stream.h>
> >
> > +#include "../camera_request.h"
> > +
> >  class Encoder
> >  {
> >  public:
> >       virtual ~Encoder() = default;
> >
> >       virtual int configure(const libcamera::StreamConfiguration &cfg) =
> 0;
> > -     virtual int encode(const libcamera::FrameBuffer &source,
> > -                        libcamera::Span<uint8_t> destination,
> > +     virtual int encode(Camera3RequestDescriptor::StreamBuffer
> *streamBuffer,
> >                          libcamera::Span<const uint8_t> exifData,
> >                          unsigned int quality) = 0;
> > +     virtual int generateThumbnail(const libcamera::FrameBuffer &source,
> > +                                   const libcamera::Size &targetSize,
> > +                                   unsigned int quality,
> > +                                   std::vector<unsigned char>
> *thumbnail) = 0;
> >  };
> > diff --git a/src/android/jpeg/encoder_libjpeg.cpp
> b/src/android/jpeg/encoder_libjpeg.cpp
> > index 21a3b33d..b5591e33 100644
> > --- a/src/android/jpeg/encoder_libjpeg.cpp
> > +++ b/src/android/jpeg/encoder_libjpeg.cpp
> > @@ -24,6 +24,8 @@
> >  #include "libcamera/internal/formats.h"
> >  #include "libcamera/internal/mapped_framebuffer.h"
> >
> > +#include "../camera_buffer.h"
> > +
> >  using namespace libcamera;
> >
> >  LOG_DECLARE_CATEGORY(JPEG)
> > @@ -82,8 +84,17 @@ EncoderLibJpeg::~EncoderLibJpeg()
> >  }
> >
> >  int EncoderLibJpeg::configure(const StreamConfiguration &cfg)
> > +{
> > +     thumbnailer_.configure(cfg.size, cfg.pixelFormat);
> > +     cfg_ = cfg;
>
> I'd store the size and pixel format only, not the full
> StreamConfiguration, as they are all you need. internalConfigure()
> should then take a size and pixel format, which will make it easier to
> use in generateThumbnail().
>
> > +
> > +     return internalConfigure(cfg);
> > +}
> > +
> > +int EncoderLibJpeg::internalConfigure(const StreamConfiguration &cfg)
>
> Let's name the function configureEncoder().
>
> >  {
> >       const struct JPEGPixelFormatInfo info =
> findPixelInfo(cfg.pixelFormat);
> > +
> >       if (info.colorSpace == JCS_UNKNOWN)
> >               return -ENOTSUP;
> >
> > @@ -178,6 +189,65 @@ void EncoderLibJpeg::compressNV(const
> std::vector<Span<uint8_t>> &planes)
> >       }
> >  }
> >
> > +int EncoderLibJpeg::encode(Camera3RequestDescriptor::StreamBuffer
> *streamBuffer,
> > +                        libcamera::Span<const uint8_t> exifData,
> > +                        unsigned int quality)
> > +{
> > +     internalConfigure(cfg_);
>
> cfg_ is only used here, I suppose to reconfigure the libjpeg encoder for
> the
>
> > +     return encode(*streamBuffer->srcBuffer,
> streamBuffer->dstBuffer.get()->plane(0), exifData, quality);
>
> Let's shorten the line a bit (we aim for 80 columns as a soft target)
>
>         return encode(*streamBuffer->srcBuffer,
> streamBuffer->dstBuffer.get()->plane(0),
>                       exifData, quality);
>
> > +}
> > +
> > +int EncoderLibJpeg::generateThumbnail(
> > +     const libcamera::FrameBuffer &source,
> > +     const libcamera::Size &targetSize,
> > +     unsigned int quality,
> > +     std::vector<unsigned char> *thumbnail)
>
> int EncoderLibJpeg::generateThumbnail(const libcamera::FrameBuffer &source,
>                                       const libcamera::Size &targetSize,
>                                       unsigned int quality,
>                                       std::vector<unsigned char>
> *thumbnail)
>
> to match the coding style.
>
> > +{
> > +     /* 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();
> > +     int ret = internalConfigure(thCfg);
>
> You now use the same libjpeg encoder instance for both the main image
> and the thumbnail. This leads to EncoderLibJpeg::internalConfigure()
> being called twice for every frame. The function looks fairly cheap so
> it should be fine, but could you confirm that jpeg_set_defaults()
> doesn't cause a large overhead ? Please mention this change in the
> commit message of the appropriate patch, it's an important one.
>
> > +
> > +     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 = encode(thumbnailPlanes, *thumbnail, {},
> quality);
> > +             thumbnail->resize(jpeg_size);
> > +
> > +             LOG(JPEG, Debug)
> > +                     << "Thumbnail compress returned "
> > +                     << jpeg_size << " bytes";
> > +
> > +             return jpeg_size;
> > +     }
> > +
> > +     return -1;
> > +}
> > +
> >  int EncoderLibJpeg::encode(const FrameBuffer &source, Span<uint8_t>
> dest,
> >                          Span<const uint8_t> exifData, unsigned int
> quality)
> >  {
> > diff --git a/src/android/jpeg/encoder_libjpeg.h
> b/src/android/jpeg/encoder_libjpeg.h
> > index 1b3ac067..56b27bae 100644
> > --- a/src/android/jpeg/encoder_libjpeg.h
> > +++ b/src/android/jpeg/encoder_libjpeg.h
> > @@ -15,6 +15,8 @@
> >
> >  #include <jpeglib.h>
> >
> > +#include "thumbnailer.h"
> > +
> >  class EncoderLibJpeg : public Encoder
> >  {
> >  public:
> > @@ -22,19 +24,32 @@ public:
> >       ~EncoderLibJpeg();
> >
> >       int configure(const libcamera::StreamConfiguration &cfg) override;
> > +     int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
> > +                libcamera::Span<const uint8_t> exifData,
> > +                unsigned int quality) override;
> > +     int generateThumbnail(
> > +             const libcamera::FrameBuffer &source,
> > +             const libcamera::Size &targetSize,
> > +             unsigned int quality,
> > +             std::vector<unsigned char> *thumbnail) override;
>
> Same here.
>
> > +
> > +private:
> > +     int internalConfigure(const libcamera::StreamConfiguration &cfg);
> > +
> >       int encode(const libcamera::FrameBuffer &source,
> >                  libcamera::Span<uint8_t> destination,
> >                  libcamera::Span<const uint8_t> exifData,
> > -                unsigned int quality) override;
> > +                unsigned int quality);
> >       int encode(const std::vector<libcamera::Span<uint8_t>> &planes,
> >                  libcamera::Span<uint8_t> destination,
> >                  libcamera::Span<const uint8_t> exifData,
> >                  unsigned int quality);
> >
> > -private:
> >       void compressRGB(const std::vector<libcamera::Span<uint8_t>>
> &planes);
> >       void compressNV(const std::vector<libcamera::Span<uint8_t>>
> &planes);
> >
> > +     libcamera::StreamConfiguration cfg_;
> > +
> >       struct jpeg_compress_struct compress_;
> >       struct jpeg_error_mgr jerr_;
> >
> > @@ -42,4 +57,6 @@ private:
> >
> >       bool nv_;
> >       bool nvSwap_;
> > +
> > +     Thumbnailer thumbnailer_;
> >  };
> > diff --git a/src/android/jpeg/generic_post_processor_jpeg.cpp
> b/src/android/jpeg/generic_post_processor_jpeg.cpp
> > new file mode 100644
> > index 00000000..890f6972
> > --- /dev/null
> > +++ b/src/android/jpeg/generic_post_processor_jpeg.cpp
> > @@ -0,0 +1,14 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2022, Google Inc.
> > + *
> > + * generic_post_processor_jpeg.cpp - Generic JPEG Post Processor
> > + */
> > +
> > +#include "encoder_libjpeg.h"
> > +#include "post_processor_jpeg.h"
> > +
> > +void PostProcessorJpeg::SetEncoder()
>
> We use camelCase for function names, not CamelCase. Let's name the
> function createEncoder(), as it doesn't just set it but actually creates
> it.
>
> > +{
> > +     encoder_ = std::make_unique<EncoderLibJpeg>();
>
> I'm tempted to simplify this by using conditional compilation (we have a
> OS_CHROMEOS macro), the function could then go to
> post_processor_jpeg.cpp.
>
> > +}
> > diff --git a/src/android/jpeg/meson.build b/src/android/jpeg/meson.build
> > new file mode 100644
> > index 00000000..94522dc0
> > --- /dev/null
> > +++ b/src/android/jpeg/meson.build
> > @@ -0,0 +1,9 @@
> > +# SPDX-License-Identifier: CC0-1.0
> > +
> > +android_hal_sources += files([
> > +    'exif.cpp',
> > +    'post_processor_jpeg.cpp'])
>
> The '])' should be on the next line.
>
> > +
> > +android_hal_sources += files(['encoder_libjpeg.cpp',
> > +                              'generic_post_processor_jpeg.cpp',
> > +                              'thumbnailer.cpp'])
>
> Similar comment here,
>
> android_hal_sources += files([
>     'encoder_libjpeg.cpp',
>     'generic_post_processor_jpeg.cpp',
>     'thumbnailer.cpp',
> ])
>
> > diff --git a/src/android/jpeg/post_processor_jpeg.cpp
> b/src/android/jpeg/post_processor_jpeg.cpp
> > index d72ebc3c..7ceba60e 100644
> > --- a/src/android/jpeg/post_processor_jpeg.cpp
> > +++ b/src/android/jpeg/post_processor_jpeg.cpp
> > @@ -9,10 +9,10 @@
> >
> >  #include <chrono>
> >
> > +#include "../android_framebuffer.h"
>
> Is this needed ?
>
> >  #include "../camera_device.h"
> >  #include "../camera_metadata.h"
> >  #include "../camera_request.h"
> > -#include "encoder_libjpeg.h"
> >  #include "exif.h"
> >
> >  #include <libcamera/base/log.h>
> > @@ -44,60 +44,11 @@ int PostProcessorJpeg::configure(const
> StreamConfiguration &inCfg,
> >
> >       streamSize_ = outCfg.size;
> >
> > -     thumbnailer_.configure(inCfg.size, inCfg.pixelFormat);
> > -
> > -     encoder_ = std::make_unique<EncoderLibJpeg>();
> > +     SetEncoder();
> >
> >       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();
> > -     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";
> > -     }
> > -}
> > -
> >  void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer
> *streamBuffer)
> >  {
> >       ASSERT(encoder_);
> > @@ -164,8 +115,8 @@ void
> PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
> >
> >               if (thumbnailSize != Size(0, 0)) {
> >                       std::vector<unsigned char> thumbnail;
> > -                     generateThumbnail(source, thumbnailSize, quality,
> &thumbnail);
> > -                     if (!thumbnail.empty())
> > +                     ret = encoder_->generateThumbnail(source,
> thumbnailSize, quality, &thumbnail);
> > +                     if (ret > 0 && !thumbnail.empty())
> >                               exif.setThumbnail(thumbnail,
> Exif::Compression::JPEG);
> >               }
> >
> > @@ -194,8 +145,7 @@ void
> PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
> >       const uint8_t quality = ret ? *entry.data.u8 : 95;
> >       resultMetadata->addEntry(ANDROID_JPEG_QUALITY, quality);
> >
> > -     int jpeg_size = encoder_->encode(source, destination->plane(0),
> > -                                      exif.data(), 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);
> > diff --git a/src/android/jpeg/post_processor_jpeg.h
> b/src/android/jpeg/post_processor_jpeg.h
> > index 98309b01..a09f8798 100644
> > --- a/src/android/jpeg/post_processor_jpeg.h
> > +++ b/src/android/jpeg/post_processor_jpeg.h
> > @@ -8,11 +8,11 @@
> >  #pragma once
> >
> >  #include "../post_processor.h"
> > -#include "encoder_libjpeg.h"
> > -#include "thumbnailer.h"
> >
> >  #include <libcamera/geometry.h>
> >
> > +#include "encoder.h"
> > +
> >  class CameraDevice;
> >
> >  class PostProcessorJpeg : public PostProcessor
> > @@ -25,14 +25,9 @@ public:
> >       void process(Camera3RequestDescriptor::StreamBuffer *streamBuffer)
> override;
> >
> >  private:
> > -     void generateThumbnail(const libcamera::FrameBuffer &source,
> > -                            const libcamera::Size &targetSize,
> > -                            unsigned int quality,
> > -                            std::vector<unsigned char> *thumbnail);
> > +     void SetEncoder();
> >
> >       CameraDevice *const cameraDevice_;
> >       std::unique_ptr<Encoder> encoder_;
> >       libcamera::Size streamSize_;
> > -     EncoderLibJpeg thumbnailEncoder_;
> > -     Thumbnailer thumbnailer_;
> >  };
> > diff --git a/src/android/meson.build b/src/android/meson.build
> > index 27be27bb..026b8b3c 100644
> > --- a/src/android/meson.build
> > +++ b/src/android/meson.build
> > @@ -48,10 +48,6 @@ android_hal_sources = files([
> >      'camera_ops.cpp',
> >      'camera_request.cpp',
> >      'camera_stream.cpp',
> > -    'jpeg/encoder_libjpeg.cpp',
> > -    'jpeg/exif.cpp',
> > -    'jpeg/post_processor_jpeg.cpp',
> > -    'jpeg/thumbnailer.cpp',
> >      'yuv/post_processor_yuv.cpp'
> >  ])
> >
> > @@ -59,6 +55,7 @@ android_cpp_args = []
> >
> >  subdir('cros')
> >  subdir('mm')
> > +subdir('jpeg')
>
> Please keep these alphabetically sorted.
>
> >
> >  android_camera_metadata_sources = files([
> >      'metadata/camera_metadata.c',
>
> --
> Regards,
>
> Laurent Pinchart
>
Cheng-Hao Yang June 22, 2022, 12:37 p.m. UTC | #3
Hi Laurent,

Uploaded PATCH v5 and fixed the following:

> > >       int configure(const libcamera::StreamConfiguration &cfg)
override;
> > > +     int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
> > > +                libcamera::Span<const uint8_t> exifData,
> > > +                unsigned int quality) override;
> > > +     int generateThumbnail(
> > > +             const libcamera::FrameBuffer &source,
> > > +             const libcamera::Size &targetSize,
> > > +             unsigned int quality,
> > > +             std::vector<unsigned char> *thumbnail) override;

> > Same here.
> I think I need your help on the indentation format. Any doc or rule I can
study? Thanks!

I was using gmail to view your comment. Now I use `
https://patchwork.libcamera.org/patch`, and the tabs are correctly aligned.
Done.


> > You now use the same libjpeg encoder instance for both the main image
and the thumbnail. This leads to EncoderLibJpeg::internalConfigure() being
called twice for every frame. The function looks fairly cheap so it should
be fine, but could you confirm that jpeg_set_defaults() doesn't cause a
large overhead ? Please mention this change in the commit message of the
appropriate patch, it's an important one.

> Originally for each frame we need at least one call to
jpeg_set_defaults() to encode the thumbnail, and we need two now with patch
v3, so I thought it was fine in terms of the complexity, while you're right
that I'm not sure about the complexity of jpeg_set_defaults(), even looking
into the implementation [1]. I can also modify the code not to configure
twice per frame. WDYT?

Done with only one jpeg_set_defaults() being called per frame. Please check.

---------

Thanks!

On Tue, May 3, 2022 at 11:29 AM Cheng-Hao Yang <chenghaoyang@chromium.org>
wrote:

> Hi Laurent,
>
> Sorry I forgot to send this reply before going on vacation...
>
> > The e-mail address should use "@" instead of " at ".
> Oh I see! I checked the libcamera archives for examples, and thought
> that was intentional haha.
> Updated.
>
> > cfg_ is only used here, I suppose to reconfigure the libjpeg encoder for
> the
> Is this a deprecated comment?
>
> > >       int configure(const libcamera::StreamConfiguration &cfg)
> override;
> > > +     int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
> > > +                libcamera::Span<const uint8_t> exifData,
> > > +                unsigned int quality) override;
> > > +     int generateThumbnail(
> > > +             const libcamera::FrameBuffer &source,
> > > +             const libcamera::Size &targetSize,
> > > +             unsigned int quality,
> > > +             std::vector<unsigned char> *thumbnail) override;
>
> > Same here.
> I think I need your help on the indentation format. Any doc or rule I can
> study? Thanks!
>
> > I'm tempted to simplify this by using conditional compilation (we have a
> OS_CHROMEOS macro), the function could then go to post_processor_jpeg.cpp.
> Oh I didn't notice there's such a macro to use. Thanks! I think we can get
> rid of the PostProcessorJpeg::createEncoder() then.
>
> > You now use the same libjpeg encoder instance for both the main image
> and the thumbnail. This leads to EncoderLibJpeg::internalConfigure() being
> called twice for every frame. The function looks fairly cheap so it should
> be fine, but could you confirm that jpeg_set_defaults() doesn't cause a
> large overhead ? Please mention this change in the commit message of the
> appropriate patch, it's an important one.
> Originally for each frame we need at least one call to jpeg_set_defaults()
> to encode the thumbnail, and we need two now with patch v3, so I thought it
> was fine in terms of the complexity, while you're right that I'm not sure
> about the complexity of jpeg_set_defaults(), even looking into the
> implementation [1]. I can also modify the code not to configure twice per
> frame. WDYT?
>
> [1]: https://github.com/kornelski/libjpeg/blob/master/jcparam.c#L268
>
>
> On Wed, Apr 27, 2022 at 7:44 AM Laurent Pinchart <
> laurent.pinchart@ideasonboard.com> wrote:
>
>> Hi Harvey,
>>
>> Thank you for the patch.
>>
>> On Tue, Apr 26, 2022 at 09:14:08AM +0000, Harvey Yang via libcamera-devel
>> wrote:
>> > To add JEA in the next CL, this CL reworks JPEG encoder API and move
>>
>> Same comment as in 1/4 for "CL" (I won't repeat it for the other patches
>> in this series, please update them as applicable).
>>
>> > thumbnail encoding code into EncoderLibJpeg's implementation, as JEA
>> > supports that as well.
>> >
>> > Signed-off-by: Harvey Yang <chenghaoyang at chromium.org>
>>
>> The e-mail address should use "@" instead of " at ".
>>
>> > ---
>> >  src/android/jpeg/encoder.h                    |  9 ++-
>> >  src/android/jpeg/encoder_libjpeg.cpp          | 70 +++++++++++++++++++
>> >  src/android/jpeg/encoder_libjpeg.h            | 21 +++++-
>> >  .../jpeg/generic_post_processor_jpeg.cpp      | 14 ++++
>> >  src/android/jpeg/meson.build                  |  9 +++
>> >  src/android/jpeg/post_processor_jpeg.cpp      | 60 ++--------------
>> >  src/android/jpeg/post_processor_jpeg.h        | 11 +--
>> >  src/android/meson.build                       |  5 +-
>>
>> This is more reviewable than in the previous version, but there are
>> still quite a few changes bundled in the same patch. As a v4 will be
>> needed anyway, could you split this further as follows ? Feel free to
>> change the order as appropriate.
>>
>> - Add meson.build file in jpeg/ directory
>> - Move thumbnail generate to Encoder class
>> - Pass streamBuffer to encode() to prepare for JPEG encoders that need
>>   access to the HAL buffer handle
>> - Add PostProcessorJpeg::SetEncoder()
>>
>> >  8 files changed, 128 insertions(+), 71 deletions(-)
>> >  create mode 100644 src/android/jpeg/generic_post_processor_jpeg.cpp
>> >  create mode 100644 src/android/jpeg/meson.build
>> >
>> > diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h
>> > index b974d367..6d527d91 100644
>> > --- a/src/android/jpeg/encoder.h
>> > +++ b/src/android/jpeg/encoder.h
>> > @@ -12,14 +12,19 @@
>> >  #include <libcamera/framebuffer.h>
>> >  #include <libcamera/stream.h>
>> >
>> > +#include "../camera_request.h"
>> > +
>> >  class Encoder
>> >  {
>> >  public:
>> >       virtual ~Encoder() = default;
>> >
>> >       virtual int configure(const libcamera::StreamConfiguration &cfg)
>> = 0;
>> > -     virtual int encode(const libcamera::FrameBuffer &source,
>> > -                        libcamera::Span<uint8_t> destination,
>> > +     virtual int encode(Camera3RequestDescriptor::StreamBuffer
>> *streamBuffer,
>> >                          libcamera::Span<const uint8_t> exifData,
>> >                          unsigned int quality) = 0;
>> > +     virtual int generateThumbnail(const libcamera::FrameBuffer
>> &source,
>> > +                                   const libcamera::Size &targetSize,
>> > +                                   unsigned int quality,
>> > +                                   std::vector<unsigned char>
>> *thumbnail) = 0;
>> >  };
>> > diff --git a/src/android/jpeg/encoder_libjpeg.cpp
>> b/src/android/jpeg/encoder_libjpeg.cpp
>> > index 21a3b33d..b5591e33 100644
>> > --- a/src/android/jpeg/encoder_libjpeg.cpp
>> > +++ b/src/android/jpeg/encoder_libjpeg.cpp
>> > @@ -24,6 +24,8 @@
>> >  #include "libcamera/internal/formats.h"
>> >  #include "libcamera/internal/mapped_framebuffer.h"
>> >
>> > +#include "../camera_buffer.h"
>> > +
>> >  using namespace libcamera;
>> >
>> >  LOG_DECLARE_CATEGORY(JPEG)
>> > @@ -82,8 +84,17 @@ EncoderLibJpeg::~EncoderLibJpeg()
>> >  }
>> >
>> >  int EncoderLibJpeg::configure(const StreamConfiguration &cfg)
>> > +{
>> > +     thumbnailer_.configure(cfg.size, cfg.pixelFormat);
>> > +     cfg_ = cfg;
>>
>> I'd store the size and pixel format only, not the full
>> StreamConfiguration, as they are all you need. internalConfigure()
>> should then take a size and pixel format, which will make it easier to
>> use in generateThumbnail().
>>
>> > +
>> > +     return internalConfigure(cfg);
>> > +}
>> > +
>> > +int EncoderLibJpeg::internalConfigure(const StreamConfiguration &cfg)
>>
>> Let's name the function configureEncoder().
>>
>> >  {
>> >       const struct JPEGPixelFormatInfo info =
>> findPixelInfo(cfg.pixelFormat);
>> > +
>> >       if (info.colorSpace == JCS_UNKNOWN)
>> >               return -ENOTSUP;
>> >
>> > @@ -178,6 +189,65 @@ void EncoderLibJpeg::compressNV(const
>> std::vector<Span<uint8_t>> &planes)
>> >       }
>> >  }
>> >
>> > +int EncoderLibJpeg::encode(Camera3RequestDescriptor::StreamBuffer
>> *streamBuffer,
>> > +                        libcamera::Span<const uint8_t> exifData,
>> > +                        unsigned int quality)
>> > +{
>> > +     internalConfigure(cfg_);
>>
>> cfg_ is only used here, I suppose to reconfigure the libjpeg encoder for
>> the
>>
>> > +     return encode(*streamBuffer->srcBuffer,
>> streamBuffer->dstBuffer.get()->plane(0), exifData, quality);
>>
>> Let's shorten the line a bit (we aim for 80 columns as a soft target)
>>
>>         return encode(*streamBuffer->srcBuffer,
>> streamBuffer->dstBuffer.get()->plane(0),
>>                       exifData, quality);
>>
>> > +}
>> > +
>> > +int EncoderLibJpeg::generateThumbnail(
>> > +     const libcamera::FrameBuffer &source,
>> > +     const libcamera::Size &targetSize,
>> > +     unsigned int quality,
>> > +     std::vector<unsigned char> *thumbnail)
>>
>> int EncoderLibJpeg::generateThumbnail(const libcamera::FrameBuffer
>> &source,
>>                                       const libcamera::Size &targetSize,
>>                                       unsigned int quality,
>>                                       std::vector<unsigned char>
>> *thumbnail)
>>
>> to match the coding style.
>>
>> > +{
>> > +     /* 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();
>> > +     int ret = internalConfigure(thCfg);
>>
>> You now use the same libjpeg encoder instance for both the main image
>> and the thumbnail. This leads to EncoderLibJpeg::internalConfigure()
>> being called twice for every frame. The function looks fairly cheap so
>> it should be fine, but could you confirm that jpeg_set_defaults()
>> doesn't cause a large overhead ? Please mention this change in the
>> commit message of the appropriate patch, it's an important one.
>>
>> > +
>> > +     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 = encode(thumbnailPlanes, *thumbnail, {},
>> quality);
>> > +             thumbnail->resize(jpeg_size);
>> > +
>> > +             LOG(JPEG, Debug)
>> > +                     << "Thumbnail compress returned "
>> > +                     << jpeg_size << " bytes";
>> > +
>> > +             return jpeg_size;
>> > +     }
>> > +
>> > +     return -1;
>> > +}
>> > +
>> >  int EncoderLibJpeg::encode(const FrameBuffer &source, Span<uint8_t>
>> dest,
>> >                          Span<const uint8_t> exifData, unsigned int
>> quality)
>> >  {
>> > diff --git a/src/android/jpeg/encoder_libjpeg.h
>> b/src/android/jpeg/encoder_libjpeg.h
>> > index 1b3ac067..56b27bae 100644
>> > --- a/src/android/jpeg/encoder_libjpeg.h
>> > +++ b/src/android/jpeg/encoder_libjpeg.h
>> > @@ -15,6 +15,8 @@
>> >
>> >  #include <jpeglib.h>
>> >
>> > +#include "thumbnailer.h"
>> > +
>> >  class EncoderLibJpeg : public Encoder
>> >  {
>> >  public:
>> > @@ -22,19 +24,32 @@ public:
>> >       ~EncoderLibJpeg();
>> >
>> >       int configure(const libcamera::StreamConfiguration &cfg) override;
>> > +     int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
>> > +                libcamera::Span<const uint8_t> exifData,
>> > +                unsigned int quality) override;
>> > +     int generateThumbnail(
>> > +             const libcamera::FrameBuffer &source,
>> > +             const libcamera::Size &targetSize,
>> > +             unsigned int quality,
>> > +             std::vector<unsigned char> *thumbnail) override;
>>
>> Same here.
>>
>> > +
>> > +private:
>> > +     int internalConfigure(const libcamera::StreamConfiguration &cfg);
>> > +
>> >       int encode(const libcamera::FrameBuffer &source,
>> >                  libcamera::Span<uint8_t> destination,
>> >                  libcamera::Span<const uint8_t> exifData,
>> > -                unsigned int quality) override;
>> > +                unsigned int quality);
>> >       int encode(const std::vector<libcamera::Span<uint8_t>> &planes,
>> >                  libcamera::Span<uint8_t> destination,
>> >                  libcamera::Span<const uint8_t> exifData,
>> >                  unsigned int quality);
>> >
>> > -private:
>> >       void compressRGB(const std::vector<libcamera::Span<uint8_t>>
>> &planes);
>> >       void compressNV(const std::vector<libcamera::Span<uint8_t>>
>> &planes);
>> >
>> > +     libcamera::StreamConfiguration cfg_;
>> > +
>> >       struct jpeg_compress_struct compress_;
>> >       struct jpeg_error_mgr jerr_;
>> >
>> > @@ -42,4 +57,6 @@ private:
>> >
>> >       bool nv_;
>> >       bool nvSwap_;
>> > +
>> > +     Thumbnailer thumbnailer_;
>> >  };
>> > diff --git a/src/android/jpeg/generic_post_processor_jpeg.cpp
>> b/src/android/jpeg/generic_post_processor_jpeg.cpp
>> > new file mode 100644
>> > index 00000000..890f6972
>> > --- /dev/null
>> > +++ b/src/android/jpeg/generic_post_processor_jpeg.cpp
>> > @@ -0,0 +1,14 @@
>> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>> > +/*
>> > + * Copyright (C) 2022, Google Inc.
>> > + *
>> > + * generic_post_processor_jpeg.cpp - Generic JPEG Post Processor
>> > + */
>> > +
>> > +#include "encoder_libjpeg.h"
>> > +#include "post_processor_jpeg.h"
>> > +
>> > +void PostProcessorJpeg::SetEncoder()
>>
>> We use camelCase for function names, not CamelCase. Let's name the
>> function createEncoder(), as it doesn't just set it but actually creates
>> it.
>>
>> > +{
>> > +     encoder_ = std::make_unique<EncoderLibJpeg>();
>>
>> I'm tempted to simplify this by using conditional compilation (we have a
>> OS_CHROMEOS macro), the function could then go to
>> post_processor_jpeg.cpp.
>>
>> > +}
>> > diff --git a/src/android/jpeg/meson.build b/src/android/jpeg/meson.build
>> > new file mode 100644
>> > index 00000000..94522dc0
>> > --- /dev/null
>> > +++ b/src/android/jpeg/meson.build
>> > @@ -0,0 +1,9 @@
>> > +# SPDX-License-Identifier: CC0-1.0
>> > +
>> > +android_hal_sources += files([
>> > +    'exif.cpp',
>> > +    'post_processor_jpeg.cpp'])
>>
>> The '])' should be on the next line.
>>
>> > +
>> > +android_hal_sources += files(['encoder_libjpeg.cpp',
>> > +                              'generic_post_processor_jpeg.cpp',
>> > +                              'thumbnailer.cpp'])
>>
>> Similar comment here,
>>
>> android_hal_sources += files([
>>     'encoder_libjpeg.cpp',
>>     'generic_post_processor_jpeg.cpp',
>>     'thumbnailer.cpp',
>> ])
>>
>> > diff --git a/src/android/jpeg/post_processor_jpeg.cpp
>> b/src/android/jpeg/post_processor_jpeg.cpp
>> > index d72ebc3c..7ceba60e 100644
>> > --- a/src/android/jpeg/post_processor_jpeg.cpp
>> > +++ b/src/android/jpeg/post_processor_jpeg.cpp
>> > @@ -9,10 +9,10 @@
>> >
>> >  #include <chrono>
>> >
>> > +#include "../android_framebuffer.h"
>>
>> Is this needed ?
>>
>> >  #include "../camera_device.h"
>> >  #include "../camera_metadata.h"
>> >  #include "../camera_request.h"
>> > -#include "encoder_libjpeg.h"
>> >  #include "exif.h"
>> >
>> >  #include <libcamera/base/log.h>
>> > @@ -44,60 +44,11 @@ int PostProcessorJpeg::configure(const
>> StreamConfiguration &inCfg,
>> >
>> >       streamSize_ = outCfg.size;
>> >
>> > -     thumbnailer_.configure(inCfg.size, inCfg.pixelFormat);
>> > -
>> > -     encoder_ = std::make_unique<EncoderLibJpeg>();
>> > +     SetEncoder();
>> >
>> >       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();
>> > -     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";
>> > -     }
>> > -}
>> > -
>> >  void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer
>> *streamBuffer)
>> >  {
>> >       ASSERT(encoder_);
>> > @@ -164,8 +115,8 @@ void
>> PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
>> >
>> >               if (thumbnailSize != Size(0, 0)) {
>> >                       std::vector<unsigned char> thumbnail;
>> > -                     generateThumbnail(source, thumbnailSize, quality,
>> &thumbnail);
>> > -                     if (!thumbnail.empty())
>> > +                     ret = encoder_->generateThumbnail(source,
>> thumbnailSize, quality, &thumbnail);
>> > +                     if (ret > 0 && !thumbnail.empty())
>> >                               exif.setThumbnail(thumbnail,
>> Exif::Compression::JPEG);
>> >               }
>> >
>> > @@ -194,8 +145,7 @@ void
>> PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
>> >       const uint8_t quality = ret ? *entry.data.u8 : 95;
>> >       resultMetadata->addEntry(ANDROID_JPEG_QUALITY, quality);
>> >
>> > -     int jpeg_size = encoder_->encode(source, destination->plane(0),
>> > -                                      exif.data(), 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);
>> > diff --git a/src/android/jpeg/post_processor_jpeg.h
>> b/src/android/jpeg/post_processor_jpeg.h
>> > index 98309b01..a09f8798 100644
>> > --- a/src/android/jpeg/post_processor_jpeg.h
>> > +++ b/src/android/jpeg/post_processor_jpeg.h
>> > @@ -8,11 +8,11 @@
>> >  #pragma once
>> >
>> >  #include "../post_processor.h"
>> > -#include "encoder_libjpeg.h"
>> > -#include "thumbnailer.h"
>> >
>> >  #include <libcamera/geometry.h>
>> >
>> > +#include "encoder.h"
>> > +
>> >  class CameraDevice;
>> >
>> >  class PostProcessorJpeg : public PostProcessor
>> > @@ -25,14 +25,9 @@ public:
>> >       void process(Camera3RequestDescriptor::StreamBuffer
>> *streamBuffer) override;
>> >
>> >  private:
>> > -     void generateThumbnail(const libcamera::FrameBuffer &source,
>> > -                            const libcamera::Size &targetSize,
>> > -                            unsigned int quality,
>> > -                            std::vector<unsigned char> *thumbnail);
>> > +     void SetEncoder();
>> >
>> >       CameraDevice *const cameraDevice_;
>> >       std::unique_ptr<Encoder> encoder_;
>> >       libcamera::Size streamSize_;
>> > -     EncoderLibJpeg thumbnailEncoder_;
>> > -     Thumbnailer thumbnailer_;
>> >  };
>> > diff --git a/src/android/meson.build b/src/android/meson.build
>> > index 27be27bb..026b8b3c 100644
>> > --- a/src/android/meson.build
>> > +++ b/src/android/meson.build
>> > @@ -48,10 +48,6 @@ android_hal_sources = files([
>> >      'camera_ops.cpp',
>> >      'camera_request.cpp',
>> >      'camera_stream.cpp',
>> > -    'jpeg/encoder_libjpeg.cpp',
>> > -    'jpeg/exif.cpp',
>> > -    'jpeg/post_processor_jpeg.cpp',
>> > -    'jpeg/thumbnailer.cpp',
>> >      'yuv/post_processor_yuv.cpp'
>> >  ])
>> >
>> > @@ -59,6 +55,7 @@ android_cpp_args = []
>> >
>> >  subdir('cros')
>> >  subdir('mm')
>> > +subdir('jpeg')
>>
>> Please keep these alphabetically sorted.
>>
>> >
>> >  android_camera_metadata_sources = files([
>> >      'metadata/camera_metadata.c',
>>
>> --
>> Regards,
>>
>> Laurent Pinchart
>>
>
Laurent Pinchart Sept. 1, 2022, 8:09 p.m. UTC | #4
Hi Harvey,

On Wed, Jun 22, 2022 at 08:37:58PM +0800, Cheng-Hao Yang wrote:
> Hi Laurent,
> 
> Uploaded PATCH v5 and fixed the following:
> 
> > > >       int configure(const libcamera::StreamConfiguration &cfg) override;
> > > > +     int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
> > > > +                libcamera::Span<const uint8_t> exifData,
> > > > +                unsigned int quality) override;
> > > > +     int generateThumbnail(
> > > > +             const libcamera::FrameBuffer &source,
> > > > +             const libcamera::Size &targetSize,
> > > > +             unsigned int quality,
> > > > +             std::vector<unsigned char> *thumbnail) override;
> > >
> > > Same here.
> > 
> > I think I need your help on the indentation format. Any doc or rule I can
> > study? Thanks!
> 
> I was using gmail to view your comment. Now I use `
> https://patchwork.libcamera.org/patch`, and the tabs are correctly aligned.
> Done.

I'm afraid I can't really help with getting gmail to display the e-mail
contents properly. Maybe you have more leverage than I do there ;-)
Jokes aside, I think most people avoid webmail for patch review.

While on the topic of e-mails, could you plese try to reply inline in
the patch instead of copying content and answers to the beginning ? It
gets difficult to get the whole context otherwise.

> > > You now use the same libjpeg encoder instance for both the main image
> > > and the thumbnail. This leads to EncoderLibJpeg::internalConfigure() being
> > > called twice for every frame. The function looks fairly cheap so it should
> > > be fine, but could you confirm that jpeg_set_defaults() doesn't cause a
> > > large overhead ? Please mention this change in the commit message of the
> > > appropriate patch, it's an important one.
> > 
> > Originally for each frame we need at least one call to
> > jpeg_set_defaults() to encode the thumbnail, and we need two now with patch
> > v3, so I thought it was fine in terms of the complexity, while you're right
> > that I'm not sure about the complexity of jpeg_set_defaults(), even looking
> > into the implementation [1]. I can also modify the code not to configure
> > twice per frame. WDYT?
> 
> Done with only one jpeg_set_defaults() being called per frame. Please check.

Thank you.

> On Tue, May 3, 2022 at 11:29 AM Cheng-Hao Yang wrote:
> 
> > Hi Laurent,
> >
> > Sorry I forgot to send this reply before going on vacation...
> >
> > > The e-mail address should use "@" instead of " at ".
> > Oh I see! I checked the libcamera archives for examples, and thought
> > that was intentional haha.
> > Updated.
> >
> > > cfg_ is only used here, I suppose to reconfigure the libjpeg encoder for
> > the
> > Is this a deprecated comment?
> >
> > > >       int configure(const libcamera::StreamConfiguration &cfg)
> > override;
> > > > +     int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
> > > > +                libcamera::Span<const uint8_t> exifData,
> > > > +                unsigned int quality) override;
> > > > +     int generateThumbnail(
> > > > +             const libcamera::FrameBuffer &source,
> > > > +             const libcamera::Size &targetSize,
> > > > +             unsigned int quality,
> > > > +             std::vector<unsigned char> *thumbnail) override;
> >
> > > Same here.
> > I think I need your help on the indentation format. Any doc or rule I can
> > study? Thanks!
> >
> > > I'm tempted to simplify this by using conditional compilation (we have a
> > OS_CHROMEOS macro), the function could then go to post_processor_jpeg.cpp.
> > Oh I didn't notice there's such a macro to use. Thanks! I think we can get
> > rid of the PostProcessorJpeg::createEncoder() then.
> >
> > > You now use the same libjpeg encoder instance for both the main image
> > and the thumbnail. This leads to EncoderLibJpeg::internalConfigure() being
> > called twice for every frame. The function looks fairly cheap so it should
> > be fine, but could you confirm that jpeg_set_defaults() doesn't cause a
> > large overhead ? Please mention this change in the commit message of the
> > appropriate patch, it's an important one.
> > Originally for each frame we need at least one call to jpeg_set_defaults()
> > to encode the thumbnail, and we need two now with patch v3, so I thought it
> > was fine in terms of the complexity, while you're right that I'm not sure
> > about the complexity of jpeg_set_defaults(), even looking into the
> > implementation [1]. I can also modify the code not to configure twice per
> > frame. WDYT?
> >
> > [1]: https://github.com/kornelski/libjpeg/blob/master/jcparam.c#L268
> >
> >
> > On Wed, Apr 27, 2022 at 7:44 AM Laurent Pinchart <
> > laurent.pinchart@ideasonboard.com> wrote:
> >
> >> Hi Harvey,
> >>
> >> Thank you for the patch.
> >>
> >> On Tue, Apr 26, 2022 at 09:14:08AM +0000, Harvey Yang via libcamera-devel
> >> wrote:
> >> > To add JEA in the next CL, this CL reworks JPEG encoder API and move
> >>
> >> Same comment as in 1/4 for "CL" (I won't repeat it for the other patches
> >> in this series, please update them as applicable).
> >>
> >> > thumbnail encoding code into EncoderLibJpeg's implementation, as JEA
> >> > supports that as well.
> >> >
> >> > Signed-off-by: Harvey Yang <chenghaoyang at chromium.org>
> >>
> >> The e-mail address should use "@" instead of " at ".
> >>
> >> > ---
> >> >  src/android/jpeg/encoder.h                    |  9 ++-
> >> >  src/android/jpeg/encoder_libjpeg.cpp          | 70 +++++++++++++++++++
> >> >  src/android/jpeg/encoder_libjpeg.h            | 21 +++++-
> >> >  .../jpeg/generic_post_processor_jpeg.cpp      | 14 ++++
> >> >  src/android/jpeg/meson.build                  |  9 +++
> >> >  src/android/jpeg/post_processor_jpeg.cpp      | 60 ++--------------
> >> >  src/android/jpeg/post_processor_jpeg.h        | 11 +--
> >> >  src/android/meson.build                       |  5 +-
> >>
> >> This is more reviewable than in the previous version, but there are
> >> still quite a few changes bundled in the same patch. As a v4 will be
> >> needed anyway, could you split this further as follows ? Feel free to
> >> change the order as appropriate.
> >>
> >> - Add meson.build file in jpeg/ directory
> >> - Move thumbnail generate to Encoder class
> >> - Pass streamBuffer to encode() to prepare for JPEG encoders that need
> >>   access to the HAL buffer handle
> >> - Add PostProcessorJpeg::SetEncoder()
> >>
> >> >  8 files changed, 128 insertions(+), 71 deletions(-)
> >> >  create mode 100644 src/android/jpeg/generic_post_processor_jpeg.cpp
> >> >  create mode 100644 src/android/jpeg/meson.build
> >> >
> >> > diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h
> >> > index b974d367..6d527d91 100644
> >> > --- a/src/android/jpeg/encoder.h
> >> > +++ b/src/android/jpeg/encoder.h
> >> > @@ -12,14 +12,19 @@
> >> >  #include <libcamera/framebuffer.h>
> >> >  #include <libcamera/stream.h>
> >> >
> >> > +#include "../camera_request.h"
> >> > +
> >> >  class Encoder
> >> >  {
> >> >  public:
> >> >       virtual ~Encoder() = default;
> >> >
> >> >       virtual int configure(const libcamera::StreamConfiguration &cfg)
> >> = 0;
> >> > -     virtual int encode(const libcamera::FrameBuffer &source,
> >> > -                        libcamera::Span<uint8_t> destination,
> >> > +     virtual int encode(Camera3RequestDescriptor::StreamBuffer
> >> *streamBuffer,
> >> >                          libcamera::Span<const uint8_t> exifData,
> >> >                          unsigned int quality) = 0;
> >> > +     virtual int generateThumbnail(const libcamera::FrameBuffer
> >> &source,
> >> > +                                   const libcamera::Size &targetSize,
> >> > +                                   unsigned int quality,
> >> > +                                   std::vector<unsigned char>
> >> *thumbnail) = 0;
> >> >  };
> >> > diff --git a/src/android/jpeg/encoder_libjpeg.cpp
> >> b/src/android/jpeg/encoder_libjpeg.cpp
> >> > index 21a3b33d..b5591e33 100644
> >> > --- a/src/android/jpeg/encoder_libjpeg.cpp
> >> > +++ b/src/android/jpeg/encoder_libjpeg.cpp
> >> > @@ -24,6 +24,8 @@
> >> >  #include "libcamera/internal/formats.h"
> >> >  #include "libcamera/internal/mapped_framebuffer.h"
> >> >
> >> > +#include "../camera_buffer.h"
> >> > +
> >> >  using namespace libcamera;
> >> >
> >> >  LOG_DECLARE_CATEGORY(JPEG)
> >> > @@ -82,8 +84,17 @@ EncoderLibJpeg::~EncoderLibJpeg()
> >> >  }
> >> >
> >> >  int EncoderLibJpeg::configure(const StreamConfiguration &cfg)
> >> > +{
> >> > +     thumbnailer_.configure(cfg.size, cfg.pixelFormat);
> >> > +     cfg_ = cfg;
> >>
> >> I'd store the size and pixel format only, not the full
> >> StreamConfiguration, as they are all you need. internalConfigure()
> >> should then take a size and pixel format, which will make it easier to
> >> use in generateThumbnail().
> >>
> >> > +
> >> > +     return internalConfigure(cfg);
> >> > +}
> >> > +
> >> > +int EncoderLibJpeg::internalConfigure(const StreamConfiguration &cfg)
> >>
> >> Let's name the function configureEncoder().
> >>
> >> >  {
> >> >       const struct JPEGPixelFormatInfo info =
> >> findPixelInfo(cfg.pixelFormat);
> >> > +
> >> >       if (info.colorSpace == JCS_UNKNOWN)
> >> >               return -ENOTSUP;
> >> >
> >> > @@ -178,6 +189,65 @@ void EncoderLibJpeg::compressNV(const
> >> std::vector<Span<uint8_t>> &planes)
> >> >       }
> >> >  }
> >> >
> >> > +int EncoderLibJpeg::encode(Camera3RequestDescriptor::StreamBuffer
> >> *streamBuffer,
> >> > +                        libcamera::Span<const uint8_t> exifData,
> >> > +                        unsigned int quality)
> >> > +{
> >> > +     internalConfigure(cfg_);
> >>
> >> cfg_ is only used here, I suppose to reconfigure the libjpeg encoder for
> >> the
> >>
> >> > +     return encode(*streamBuffer->srcBuffer,
> >> streamBuffer->dstBuffer.get()->plane(0), exifData, quality);
> >>
> >> Let's shorten the line a bit (we aim for 80 columns as a soft target)
> >>
> >>         return encode(*streamBuffer->srcBuffer,
> >> streamBuffer->dstBuffer.get()->plane(0),
> >>                       exifData, quality);
> >>
> >> > +}
> >> > +
> >> > +int EncoderLibJpeg::generateThumbnail(
> >> > +     const libcamera::FrameBuffer &source,
> >> > +     const libcamera::Size &targetSize,
> >> > +     unsigned int quality,
> >> > +     std::vector<unsigned char> *thumbnail)
> >>
> >> int EncoderLibJpeg::generateThumbnail(const libcamera::FrameBuffer
> >> &source,
> >>                                       const libcamera::Size &targetSize,
> >>                                       unsigned int quality,
> >>                                       std::vector<unsigned char>
> >> *thumbnail)
> >>
> >> to match the coding style.
> >>
> >> > +{
> >> > +     /* 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();
> >> > +     int ret = internalConfigure(thCfg);
> >>
> >> You now use the same libjpeg encoder instance for both the main image
> >> and the thumbnail. This leads to EncoderLibJpeg::internalConfigure()
> >> being called twice for every frame. The function looks fairly cheap so
> >> it should be fine, but could you confirm that jpeg_set_defaults()
> >> doesn't cause a large overhead ? Please mention this change in the
> >> commit message of the appropriate patch, it's an important one.
> >>
> >> > +
> >> > +     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 = encode(thumbnailPlanes, *thumbnail, {},
> >> quality);
> >> > +             thumbnail->resize(jpeg_size);
> >> > +
> >> > +             LOG(JPEG, Debug)
> >> > +                     << "Thumbnail compress returned "
> >> > +                     << jpeg_size << " bytes";
> >> > +
> >> > +             return jpeg_size;
> >> > +     }
> >> > +
> >> > +     return -1;
> >> > +}
> >> > +
> >> >  int EncoderLibJpeg::encode(const FrameBuffer &source, Span<uint8_t>
> >> dest,
> >> >                          Span<const uint8_t> exifData, unsigned int
> >> quality)
> >> >  {
> >> > diff --git a/src/android/jpeg/encoder_libjpeg.h
> >> b/src/android/jpeg/encoder_libjpeg.h
> >> > index 1b3ac067..56b27bae 100644
> >> > --- a/src/android/jpeg/encoder_libjpeg.h
> >> > +++ b/src/android/jpeg/encoder_libjpeg.h
> >> > @@ -15,6 +15,8 @@
> >> >
> >> >  #include <jpeglib.h>
> >> >
> >> > +#include "thumbnailer.h"
> >> > +
> >> >  class EncoderLibJpeg : public Encoder
> >> >  {
> >> >  public:
> >> > @@ -22,19 +24,32 @@ public:
> >> >       ~EncoderLibJpeg();
> >> >
> >> >       int configure(const libcamera::StreamConfiguration &cfg) override;
> >> > +     int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
> >> > +                libcamera::Span<const uint8_t> exifData,
> >> > +                unsigned int quality) override;
> >> > +     int generateThumbnail(
> >> > +             const libcamera::FrameBuffer &source,
> >> > +             const libcamera::Size &targetSize,
> >> > +             unsigned int quality,
> >> > +             std::vector<unsigned char> *thumbnail) override;
> >>
> >> Same here.
> >>
> >> > +
> >> > +private:
> >> > +     int internalConfigure(const libcamera::StreamConfiguration &cfg);
> >> > +
> >> >       int encode(const libcamera::FrameBuffer &source,
> >> >                  libcamera::Span<uint8_t> destination,
> >> >                  libcamera::Span<const uint8_t> exifData,
> >> > -                unsigned int quality) override;
> >> > +                unsigned int quality);
> >> >       int encode(const std::vector<libcamera::Span<uint8_t>> &planes,
> >> >                  libcamera::Span<uint8_t> destination,
> >> >                  libcamera::Span<const uint8_t> exifData,
> >> >                  unsigned int quality);
> >> >
> >> > -private:
> >> >       void compressRGB(const std::vector<libcamera::Span<uint8_t>>
> >> &planes);
> >> >       void compressNV(const std::vector<libcamera::Span<uint8_t>>
> >> &planes);
> >> >
> >> > +     libcamera::StreamConfiguration cfg_;
> >> > +
> >> >       struct jpeg_compress_struct compress_;
> >> >       struct jpeg_error_mgr jerr_;
> >> >
> >> > @@ -42,4 +57,6 @@ private:
> >> >
> >> >       bool nv_;
> >> >       bool nvSwap_;
> >> > +
> >> > +     Thumbnailer thumbnailer_;
> >> >  };
> >> > diff --git a/src/android/jpeg/generic_post_processor_jpeg.cpp
> >> b/src/android/jpeg/generic_post_processor_jpeg.cpp
> >> > new file mode 100644
> >> > index 00000000..890f6972
> >> > --- /dev/null
> >> > +++ b/src/android/jpeg/generic_post_processor_jpeg.cpp
> >> > @@ -0,0 +1,14 @@
> >> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> >> > +/*
> >> > + * Copyright (C) 2022, Google Inc.
> >> > + *
> >> > + * generic_post_processor_jpeg.cpp - Generic JPEG Post Processor
> >> > + */
> >> > +
> >> > +#include "encoder_libjpeg.h"
> >> > +#include "post_processor_jpeg.h"
> >> > +
> >> > +void PostProcessorJpeg::SetEncoder()
> >>
> >> We use camelCase for function names, not CamelCase. Let's name the
> >> function createEncoder(), as it doesn't just set it but actually creates
> >> it.
> >>
> >> > +{
> >> > +     encoder_ = std::make_unique<EncoderLibJpeg>();
> >>
> >> I'm tempted to simplify this by using conditional compilation (we have a
> >> OS_CHROMEOS macro), the function could then go to
> >> post_processor_jpeg.cpp.
> >>
> >> > +}
> >> > diff --git a/src/android/jpeg/meson.build b/src/android/jpeg/meson.build
> >> > new file mode 100644
> >> > index 00000000..94522dc0
> >> > --- /dev/null
> >> > +++ b/src/android/jpeg/meson.build
> >> > @@ -0,0 +1,9 @@
> >> > +# SPDX-License-Identifier: CC0-1.0
> >> > +
> >> > +android_hal_sources += files([
> >> > +    'exif.cpp',
> >> > +    'post_processor_jpeg.cpp'])
> >>
> >> The '])' should be on the next line.
> >>
> >> > +
> >> > +android_hal_sources += files(['encoder_libjpeg.cpp',
> >> > +                              'generic_post_processor_jpeg.cpp',
> >> > +                              'thumbnailer.cpp'])
> >>
> >> Similar comment here,
> >>
> >> android_hal_sources += files([
> >>     'encoder_libjpeg.cpp',
> >>     'generic_post_processor_jpeg.cpp',
> >>     'thumbnailer.cpp',
> >> ])
> >>
> >> > diff --git a/src/android/jpeg/post_processor_jpeg.cpp
> >> b/src/android/jpeg/post_processor_jpeg.cpp
> >> > index d72ebc3c..7ceba60e 100644
> >> > --- a/src/android/jpeg/post_processor_jpeg.cpp
> >> > +++ b/src/android/jpeg/post_processor_jpeg.cpp
> >> > @@ -9,10 +9,10 @@
> >> >
> >> >  #include <chrono>
> >> >
> >> > +#include "../android_framebuffer.h"
> >>
> >> Is this needed ?
> >>
> >> >  #include "../camera_device.h"
> >> >  #include "../camera_metadata.h"
> >> >  #include "../camera_request.h"
> >> > -#include "encoder_libjpeg.h"
> >> >  #include "exif.h"
> >> >
> >> >  #include <libcamera/base/log.h>
> >> > @@ -44,60 +44,11 @@ int PostProcessorJpeg::configure(const
> >> StreamConfiguration &inCfg,
> >> >
> >> >       streamSize_ = outCfg.size;
> >> >
> >> > -     thumbnailer_.configure(inCfg.size, inCfg.pixelFormat);
> >> > -
> >> > -     encoder_ = std::make_unique<EncoderLibJpeg>();
> >> > +     SetEncoder();
> >> >
> >> >       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();
> >> > -     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";
> >> > -     }
> >> > -}
> >> > -
> >> >  void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer
> >> *streamBuffer)
> >> >  {
> >> >       ASSERT(encoder_);
> >> > @@ -164,8 +115,8 @@ void
> >> PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
> >> >
> >> >               if (thumbnailSize != Size(0, 0)) {
> >> >                       std::vector<unsigned char> thumbnail;
> >> > -                     generateThumbnail(source, thumbnailSize, quality,
> >> &thumbnail);
> >> > -                     if (!thumbnail.empty())
> >> > +                     ret = encoder_->generateThumbnail(source,
> >> thumbnailSize, quality, &thumbnail);
> >> > +                     if (ret > 0 && !thumbnail.empty())
> >> >                               exif.setThumbnail(thumbnail,
> >> Exif::Compression::JPEG);
> >> >               }
> >> >
> >> > @@ -194,8 +145,7 @@ void
> >> PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
> >> >       const uint8_t quality = ret ? *entry.data.u8 : 95;
> >> >       resultMetadata->addEntry(ANDROID_JPEG_QUALITY, quality);
> >> >
> >> > -     int jpeg_size = encoder_->encode(source, destination->plane(0),
> >> > -                                      exif.data(), 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);
> >> > diff --git a/src/android/jpeg/post_processor_jpeg.h
> >> b/src/android/jpeg/post_processor_jpeg.h
> >> > index 98309b01..a09f8798 100644
> >> > --- a/src/android/jpeg/post_processor_jpeg.h
> >> > +++ b/src/android/jpeg/post_processor_jpeg.h
> >> > @@ -8,11 +8,11 @@
> >> >  #pragma once
> >> >
> >> >  #include "../post_processor.h"
> >> > -#include "encoder_libjpeg.h"
> >> > -#include "thumbnailer.h"
> >> >
> >> >  #include <libcamera/geometry.h>
> >> >
> >> > +#include "encoder.h"
> >> > +
> >> >  class CameraDevice;
> >> >
> >> >  class PostProcessorJpeg : public PostProcessor
> >> > @@ -25,14 +25,9 @@ public:
> >> >       void process(Camera3RequestDescriptor::StreamBuffer
> >> *streamBuffer) override;
> >> >
> >> >  private:
> >> > -     void generateThumbnail(const libcamera::FrameBuffer &source,
> >> > -                            const libcamera::Size &targetSize,
> >> > -                            unsigned int quality,
> >> > -                            std::vector<unsigned char> *thumbnail);
> >> > +     void SetEncoder();
> >> >
> >> >       CameraDevice *const cameraDevice_;
> >> >       std::unique_ptr<Encoder> encoder_;
> >> >       libcamera::Size streamSize_;
> >> > -     EncoderLibJpeg thumbnailEncoder_;
> >> > -     Thumbnailer thumbnailer_;
> >> >  };
> >> > diff --git a/src/android/meson.build b/src/android/meson.build
> >> > index 27be27bb..026b8b3c 100644
> >> > --- a/src/android/meson.build
> >> > +++ b/src/android/meson.build
> >> > @@ -48,10 +48,6 @@ android_hal_sources = files([
> >> >      'camera_ops.cpp',
> >> >      'camera_request.cpp',
> >> >      'camera_stream.cpp',
> >> > -    'jpeg/encoder_libjpeg.cpp',
> >> > -    'jpeg/exif.cpp',
> >> > -    'jpeg/post_processor_jpeg.cpp',
> >> > -    'jpeg/thumbnailer.cpp',
> >> >      'yuv/post_processor_yuv.cpp'
> >> >  ])
> >> >
> >> > @@ -59,6 +55,7 @@ android_cpp_args = []
> >> >
> >> >  subdir('cros')
> >> >  subdir('mm')
> >> > +subdir('jpeg')
> >>
> >> Please keep these alphabetically sorted.
> >>
> >> >
> >> >  android_camera_metadata_sources = files([
> >> >      'metadata/camera_metadata.c',
> >>
> >> --
> >> Regards,
> >>
> >> Laurent Pinchart
> >>
> >
Cheng-Hao Yang Sept. 1, 2022, 9:07 p.m. UTC | #5
Hi Laurent,

On Fri, Sep 2, 2022 at 4:09 AM Laurent Pinchart <
laurent.pinchart@ideasonboard.com> wrote:

> Hi Harvey,
>
> On Wed, Jun 22, 2022 at 08:37:58PM +0800, Cheng-Hao Yang wrote:
> > Hi Laurent,
> >
> > Uploaded PATCH v5 and fixed the following:
> >
> > > > >       int configure(const libcamera::StreamConfiguration &cfg)
> override;
> > > > > +     int encode(Camera3RequestDescriptor::StreamBuffer
> *streamBuffer,
> > > > > +                libcamera::Span<const uint8_t> exifData,
> > > > > +                unsigned int quality) override;
> > > > > +     int generateThumbnail(
> > > > > +             const libcamera::FrameBuffer &source,
> > > > > +             const libcamera::Size &targetSize,
> > > > > +             unsigned int quality,
> > > > > +             std::vector<unsigned char> *thumbnail) override;
> > > >
> > > > Same here.
> > >
> > > I think I need your help on the indentation format. Any doc or rule I
> can
> > > study? Thanks!
> >
> > I was using gmail to view your comment. Now I use `
> > https://patchwork.libcamera.org/patch`
> <https://patchwork.libcamera.org/patch>, and the tabs are correctly
> aligned.
> > Done.
>
> I'm afraid I can't really help with getting gmail to display the e-mail
> contents properly. Maybe you have more leverage than I do there ;-)
> Jokes aside, I think most people avoid webmail for patch review.
>
> While on the topic of e-mails, could you plese try to reply inline in
> the patch instead of copying content and answers to the beginning ? It
> gets difficult to get the whole context otherwise.
>
>
Right, I'll start to do that from now on. Thanks for letting me know :)


> > > > You now use the same libjpeg encoder instance for both the main image
> > > > and the thumbnail. This leads to EncoderLibJpeg::internalConfigure()
> being
> > > > called twice for every frame. The function looks fairly cheap so it
> should
> > > > be fine, but could you confirm that jpeg_set_defaults() doesn't
> cause a
> > > > large overhead ? Please mention this change in the commit message of
> the
> > > > appropriate patch, it's an important one.
> > >
> > > Originally for each frame we need at least one call to
> > > jpeg_set_defaults() to encode the thumbnail, and we need two now with
> patch
> > > v3, so I thought it was fine in terms of the complexity, while you're
> right
> > > that I'm not sure about the complexity of jpeg_set_defaults(), even
> looking
> > > into the implementation [1]. I can also modify the code not to
> configure
> > > twice per frame. WDYT?
> >
> > Done with only one jpeg_set_defaults() being called per frame. Please
> check.
>
> Thank you.
>
> > On Tue, May 3, 2022 at 11:29 AM Cheng-Hao Yang wrote:
> >
> > > Hi Laurent,
> > >
> > > Sorry I forgot to send this reply before going on vacation...
> > >
> > > > The e-mail address should use "@" instead of " at ".
> > > Oh I see! I checked the libcamera archives for examples, and thought
> > > that was intentional haha.
> > > Updated.
> > >
> > > > cfg_ is only used here, I suppose to reconfigure the libjpeg encoder
> for
> > > the
> > > Is this a deprecated comment?
> > >
> > > > >       int configure(const libcamera::StreamConfiguration &cfg)
> > > override;
> > > > > +     int encode(Camera3RequestDescriptor::StreamBuffer
> *streamBuffer,
> > > > > +                libcamera::Span<const uint8_t> exifData,
> > > > > +                unsigned int quality) override;
> > > > > +     int generateThumbnail(
> > > > > +             const libcamera::FrameBuffer &source,
> > > > > +             const libcamera::Size &targetSize,
> > > > > +             unsigned int quality,
> > > > > +             std::vector<unsigned char> *thumbnail) override;
> > >
> > > > Same here.
> > > I think I need your help on the indentation format. Any doc or rule I
> can
> > > study? Thanks!
> > >
> > > > I'm tempted to simplify this by using conditional compilation (we
> have a
> > > OS_CHROMEOS macro), the function could then go to
> post_processor_jpeg.cpp.
> > > Oh I didn't notice there's such a macro to use. Thanks! I think we can
> get
> > > rid of the PostProcessorJpeg::createEncoder() then.
> > >
> > > > You now use the same libjpeg encoder instance for both the main image
> > > and the thumbnail. This leads to EncoderLibJpeg::internalConfigure()
> being
> > > called twice for every frame. The function looks fairly cheap so it
> should
> > > be fine, but could you confirm that jpeg_set_defaults() doesn't cause a
> > > large overhead ? Please mention this change in the commit message of
> the
> > > appropriate patch, it's an important one.
> > > Originally for each frame we need at least one call to
> jpeg_set_defaults()
> > > to encode the thumbnail, and we need two now with patch v3, so I
> thought it
> > > was fine in terms of the complexity, while you're right that I'm not
> sure
> > > about the complexity of jpeg_set_defaults(), even looking into the
> > > implementation [1]. I can also modify the code not to configure twice
> per
> > > frame. WDYT?
> > >
> > > [1]: https://github.com/kornelski/libjpeg/blob/master/jcparam.c#L268
> > >
> > >
> > > On Wed, Apr 27, 2022 at 7:44 AM Laurent Pinchart <
> > > laurent.pinchart@ideasonboard.com> wrote:
> > >
> > >> Hi Harvey,
> > >>
> > >> Thank you for the patch.
> > >>
> > >> On Tue, Apr 26, 2022 at 09:14:08AM +0000, Harvey Yang via
> libcamera-devel
> > >> wrote:
> > >> > To add JEA in the next CL, this CL reworks JPEG encoder API and move
> > >>
> > >> Same comment as in 1/4 for "CL" (I won't repeat it for the other
> patches
> > >> in this series, please update them as applicable).
> > >>
> > >> > thumbnail encoding code into EncoderLibJpeg's implementation, as JEA
> > >> > supports that as well.
> > >> >
> > >> > Signed-off-by: Harvey Yang <chenghaoyang at chromium.org>
> > >>
> > >> The e-mail address should use "@" instead of " at ".
> > >>
> > >> > ---
> > >> >  src/android/jpeg/encoder.h                    |  9 ++-
> > >> >  src/android/jpeg/encoder_libjpeg.cpp          | 70
> +++++++++++++++++++
> > >> >  src/android/jpeg/encoder_libjpeg.h            | 21 +++++-
> > >> >  .../jpeg/generic_post_processor_jpeg.cpp      | 14 ++++
> > >> >  src/android/jpeg/meson.build                  |  9 +++
> > >> >  src/android/jpeg/post_processor_jpeg.cpp      | 60 ++--------------
> > >> >  src/android/jpeg/post_processor_jpeg.h        | 11 +--
> > >> >  src/android/meson.build                       |  5 +-
> > >>
> > >> This is more reviewable than in the previous version, but there are
> > >> still quite a few changes bundled in the same patch. As a v4 will be
> > >> needed anyway, could you split this further as follows ? Feel free to
> > >> change the order as appropriate.
> > >>
> > >> - Add meson.build file in jpeg/ directory
> > >> - Move thumbnail generate to Encoder class
> > >> - Pass streamBuffer to encode() to prepare for JPEG encoders that need
> > >>   access to the HAL buffer handle
> > >> - Add PostProcessorJpeg::SetEncoder()
> > >>
> > >> >  8 files changed, 128 insertions(+), 71 deletions(-)
> > >> >  create mode 100644 src/android/jpeg/generic_post_processor_jpeg.cpp
> > >> >  create mode 100644 src/android/jpeg/meson.build
> > >> >
> > >> > diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h
> > >> > index b974d367..6d527d91 100644
> > >> > --- a/src/android/jpeg/encoder.h
> > >> > +++ b/src/android/jpeg/encoder.h
> > >> > @@ -12,14 +12,19 @@
> > >> >  #include <libcamera/framebuffer.h>
> > >> >  #include <libcamera/stream.h>
> > >> >
> > >> > +#include "../camera_request.h"
> > >> > +
> > >> >  class Encoder
> > >> >  {
> > >> >  public:
> > >> >       virtual ~Encoder() = default;
> > >> >
> > >> >       virtual int configure(const libcamera::StreamConfiguration
> &cfg)
> > >> = 0;
> > >> > -     virtual int encode(const libcamera::FrameBuffer &source,
> > >> > -                        libcamera::Span<uint8_t> destination,
> > >> > +     virtual int encode(Camera3RequestDescriptor::StreamBuffer
> > >> *streamBuffer,
> > >> >                          libcamera::Span<const uint8_t> exifData,
> > >> >                          unsigned int quality) = 0;
> > >> > +     virtual int generateThumbnail(const libcamera::FrameBuffer
> > >> &source,
> > >> > +                                   const libcamera::Size
> &targetSize,
> > >> > +                                   unsigned int quality,
> > >> > +                                   std::vector<unsigned char>
> > >> *thumbnail) = 0;
> > >> >  };
> > >> > diff --git a/src/android/jpeg/encoder_libjpeg.cpp
> > >> b/src/android/jpeg/encoder_libjpeg.cpp
> > >> > index 21a3b33d..b5591e33 100644
> > >> > --- a/src/android/jpeg/encoder_libjpeg.cpp
> > >> > +++ b/src/android/jpeg/encoder_libjpeg.cpp
> > >> > @@ -24,6 +24,8 @@
> > >> >  #include "libcamera/internal/formats.h"
> > >> >  #include "libcamera/internal/mapped_framebuffer.h"
> > >> >
> > >> > +#include "../camera_buffer.h"
> > >> > +
> > >> >  using namespace libcamera;
> > >> >
> > >> >  LOG_DECLARE_CATEGORY(JPEG)
> > >> > @@ -82,8 +84,17 @@ EncoderLibJpeg::~EncoderLibJpeg()
> > >> >  }
> > >> >
> > >> >  int EncoderLibJpeg::configure(const StreamConfiguration &cfg)
> > >> > +{
> > >> > +     thumbnailer_.configure(cfg.size, cfg.pixelFormat);
> > >> > +     cfg_ = cfg;
> > >>
> > >> I'd store the size and pixel format only, not the full
> > >> StreamConfiguration, as they are all you need. internalConfigure()
> > >> should then take a size and pixel format, which will make it easier to
> > >> use in generateThumbnail().
> > >>
> > >> > +
> > >> > +     return internalConfigure(cfg);
> > >> > +}
> > >> > +
> > >> > +int EncoderLibJpeg::internalConfigure(const StreamConfiguration
> &cfg)
> > >>
> > >> Let's name the function configureEncoder().
> > >>
> > >> >  {
> > >> >       const struct JPEGPixelFormatInfo info =
> > >> findPixelInfo(cfg.pixelFormat);
> > >> > +
> > >> >       if (info.colorSpace == JCS_UNKNOWN)
> > >> >               return -ENOTSUP;
> > >> >
> > >> > @@ -178,6 +189,65 @@ void EncoderLibJpeg::compressNV(const
> > >> std::vector<Span<uint8_t>> &planes)
> > >> >       }
> > >> >  }
> > >> >
> > >> > +int EncoderLibJpeg::encode(Camera3RequestDescriptor::StreamBuffer
> > >> *streamBuffer,
> > >> > +                        libcamera::Span<const uint8_t> exifData,
> > >> > +                        unsigned int quality)
> > >> > +{
> > >> > +     internalConfigure(cfg_);
> > >>
> > >> cfg_ is only used here, I suppose to reconfigure the libjpeg encoder
> for
> > >> the
> > >>
> > >> > +     return encode(*streamBuffer->srcBuffer,
> > >> streamBuffer->dstBuffer.get()->plane(0), exifData, quality);
> > >>
> > >> Let's shorten the line a bit (we aim for 80 columns as a soft target)
> > >>
> > >>         return encode(*streamBuffer->srcBuffer,
> > >> streamBuffer->dstBuffer.get()->plane(0),
> > >>                       exifData, quality);
> > >>
> > >> > +}
> > >> > +
> > >> > +int EncoderLibJpeg::generateThumbnail(
> > >> > +     const libcamera::FrameBuffer &source,
> > >> > +     const libcamera::Size &targetSize,
> > >> > +     unsigned int quality,
> > >> > +     std::vector<unsigned char> *thumbnail)
> > >>
> > >> int EncoderLibJpeg::generateThumbnail(const libcamera::FrameBuffer
> > >> &source,
> > >>                                       const libcamera::Size
> &targetSize,
> > >>                                       unsigned int quality,
> > >>                                       std::vector<unsigned char>
> > >> *thumbnail)
> > >>
> > >> to match the coding style.
> > >>
> > >> > +{
> > >> > +     /* 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();
> > >> > +     int ret = internalConfigure(thCfg);
> > >>
> > >> You now use the same libjpeg encoder instance for both the main image
> > >> and the thumbnail. This leads to EncoderLibJpeg::internalConfigure()
> > >> being called twice for every frame. The function looks fairly cheap so
> > >> it should be fine, but could you confirm that jpeg_set_defaults()
> > >> doesn't cause a large overhead ? Please mention this change in the
> > >> commit message of the appropriate patch, it's an important one.
> > >>
> > >> > +
> > >> > +     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 = encode(thumbnailPlanes, *thumbnail,
> {},
> > >> quality);
> > >> > +             thumbnail->resize(jpeg_size);
> > >> > +
> > >> > +             LOG(JPEG, Debug)
> > >> > +                     << "Thumbnail compress returned "
> > >> > +                     << jpeg_size << " bytes";
> > >> > +
> > >> > +             return jpeg_size;
> > >> > +     }
> > >> > +
> > >> > +     return -1;
> > >> > +}
> > >> > +
> > >> >  int EncoderLibJpeg::encode(const FrameBuffer &source, Span<uint8_t>
> > >> dest,
> > >> >                          Span<const uint8_t> exifData, unsigned int
> > >> quality)
> > >> >  {
> > >> > diff --git a/src/android/jpeg/encoder_libjpeg.h
> > >> b/src/android/jpeg/encoder_libjpeg.h
> > >> > index 1b3ac067..56b27bae 100644
> > >> > --- a/src/android/jpeg/encoder_libjpeg.h
> > >> > +++ b/src/android/jpeg/encoder_libjpeg.h
> > >> > @@ -15,6 +15,8 @@
> > >> >
> > >> >  #include <jpeglib.h>
> > >> >
> > >> > +#include "thumbnailer.h"
> > >> > +
> > >> >  class EncoderLibJpeg : public Encoder
> > >> >  {
> > >> >  public:
> > >> > @@ -22,19 +24,32 @@ public:
> > >> >       ~EncoderLibJpeg();
> > >> >
> > >> >       int configure(const libcamera::StreamConfiguration &cfg)
> override;
> > >> > +     int encode(Camera3RequestDescriptor::StreamBuffer
> *streamBuffer,
> > >> > +                libcamera::Span<const uint8_t> exifData,
> > >> > +                unsigned int quality) override;
> > >> > +     int generateThumbnail(
> > >> > +             const libcamera::FrameBuffer &source,
> > >> > +             const libcamera::Size &targetSize,
> > >> > +             unsigned int quality,
> > >> > +             std::vector<unsigned char> *thumbnail) override;
> > >>
> > >> Same here.
> > >>
> > >> > +
> > >> > +private:
> > >> > +     int internalConfigure(const libcamera::StreamConfiguration
> &cfg);
> > >> > +
> > >> >       int encode(const libcamera::FrameBuffer &source,
> > >> >                  libcamera::Span<uint8_t> destination,
> > >> >                  libcamera::Span<const uint8_t> exifData,
> > >> > -                unsigned int quality) override;
> > >> > +                unsigned int quality);
> > >> >       int encode(const std::vector<libcamera::Span<uint8_t>>
> &planes,
> > >> >                  libcamera::Span<uint8_t> destination,
> > >> >                  libcamera::Span<const uint8_t> exifData,
> > >> >                  unsigned int quality);
> > >> >
> > >> > -private:
> > >> >       void compressRGB(const std::vector<libcamera::Span<uint8_t>>
> > >> &planes);
> > >> >       void compressNV(const std::vector<libcamera::Span<uint8_t>>
> > >> &planes);
> > >> >
> > >> > +     libcamera::StreamConfiguration cfg_;
> > >> > +
> > >> >       struct jpeg_compress_struct compress_;
> > >> >       struct jpeg_error_mgr jerr_;
> > >> >
> > >> > @@ -42,4 +57,6 @@ private:
> > >> >
> > >> >       bool nv_;
> > >> >       bool nvSwap_;
> > >> > +
> > >> > +     Thumbnailer thumbnailer_;
> > >> >  };
> > >> > diff --git a/src/android/jpeg/generic_post_processor_jpeg.cpp
> > >> b/src/android/jpeg/generic_post_processor_jpeg.cpp
> > >> > new file mode 100644
> > >> > index 00000000..890f6972
> > >> > --- /dev/null
> > >> > +++ b/src/android/jpeg/generic_post_processor_jpeg.cpp
> > >> > @@ -0,0 +1,14 @@
> > >> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > >> > +/*
> > >> > + * Copyright (C) 2022, Google Inc.
> > >> > + *
> > >> > + * generic_post_processor_jpeg.cpp - Generic JPEG Post Processor
> > >> > + */
> > >> > +
> > >> > +#include "encoder_libjpeg.h"
> > >> > +#include "post_processor_jpeg.h"
> > >> > +
> > >> > +void PostProcessorJpeg::SetEncoder()
> > >>
> > >> We use camelCase for function names, not CamelCase. Let's name the
> > >> function createEncoder(), as it doesn't just set it but actually
> creates
> > >> it.
> > >>
> > >> > +{
> > >> > +     encoder_ = std::make_unique<EncoderLibJpeg>();
> > >>
> > >> I'm tempted to simplify this by using conditional compilation (we
> have a
> > >> OS_CHROMEOS macro), the function could then go to
> > >> post_processor_jpeg.cpp.
> > >>
> > >> > +}
> > >> > diff --git a/src/android/jpeg/meson.build
> b/src/android/jpeg/meson.build
> > >> > new file mode 100644
> > >> > index 00000000..94522dc0
> > >> > --- /dev/null
> > >> > +++ b/src/android/jpeg/meson.build
> > >> > @@ -0,0 +1,9 @@
> > >> > +# SPDX-License-Identifier: CC0-1.0
> > >> > +
> > >> > +android_hal_sources += files([
> > >> > +    'exif.cpp',
> > >> > +    'post_processor_jpeg.cpp'])
> > >>
> > >> The '])' should be on the next line.
> > >>
> > >> > +
> > >> > +android_hal_sources += files(['encoder_libjpeg.cpp',
> > >> > +                              'generic_post_processor_jpeg.cpp',
> > >> > +                              'thumbnailer.cpp'])
> > >>
> > >> Similar comment here,
> > >>
> > >> android_hal_sources += files([
> > >>     'encoder_libjpeg.cpp',
> > >>     'generic_post_processor_jpeg.cpp',
> > >>     'thumbnailer.cpp',
> > >> ])
> > >>
> > >> > diff --git a/src/android/jpeg/post_processor_jpeg.cpp
> > >> b/src/android/jpeg/post_processor_jpeg.cpp
> > >> > index d72ebc3c..7ceba60e 100644
> > >> > --- a/src/android/jpeg/post_processor_jpeg.cpp
> > >> > +++ b/src/android/jpeg/post_processor_jpeg.cpp
> > >> > @@ -9,10 +9,10 @@
> > >> >
> > >> >  #include <chrono>
> > >> >
> > >> > +#include "../android_framebuffer.h"
> > >>
> > >> Is this needed ?
> > >>
> > >> >  #include "../camera_device.h"
> > >> >  #include "../camera_metadata.h"
> > >> >  #include "../camera_request.h"
> > >> > -#include "encoder_libjpeg.h"
> > >> >  #include "exif.h"
> > >> >
> > >> >  #include <libcamera/base/log.h>
> > >> > @@ -44,60 +44,11 @@ int PostProcessorJpeg::configure(const
> > >> StreamConfiguration &inCfg,
> > >> >
> > >> >       streamSize_ = outCfg.size;
> > >> >
> > >> > -     thumbnailer_.configure(inCfg.size, inCfg.pixelFormat);
> > >> > -
> > >> > -     encoder_ = std::make_unique<EncoderLibJpeg>();
> > >> > +     SetEncoder();
> > >> >
> > >> >       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();
> > >> > -     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";
> > >> > -     }
> > >> > -}
> > >> > -
> > >> >  void
> PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer
> > >> *streamBuffer)
> > >> >  {
> > >> >       ASSERT(encoder_);
> > >> > @@ -164,8 +115,8 @@ void
> > >> PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer
> *streamBu
> > >> >
> > >> >               if (thumbnailSize != Size(0, 0)) {
> > >> >                       std::vector<unsigned char> thumbnail;
> > >> > -                     generateThumbnail(source, thumbnailSize,
> quality,
> > >> &thumbnail);
> > >> > -                     if (!thumbnail.empty())
> > >> > +                     ret = encoder_->generateThumbnail(source,
> > >> thumbnailSize, quality, &thumbnail);
> > >> > +                     if (ret > 0 && !thumbnail.empty())
> > >> >                               exif.setThumbnail(thumbnail,
> > >> Exif::Compression::JPEG);
> > >> >               }
> > >> >
> > >> > @@ -194,8 +145,7 @@ void
> > >> PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer
> *streamBu
> > >> >       const uint8_t quality = ret ? *entry.data.u8 : 95;
> > >> >       resultMetadata->addEntry(ANDROID_JPEG_QUALITY, quality);
> > >> >
> > >> > -     int jpeg_size = encoder_->encode(source,
> destination->plane(0),
> > >> > -                                      exif.data(), 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);
> > >> > diff --git a/src/android/jpeg/post_processor_jpeg.h
> > >> b/src/android/jpeg/post_processor_jpeg.h
> > >> > index 98309b01..a09f8798 100644
> > >> > --- a/src/android/jpeg/post_processor_jpeg.h
> > >> > +++ b/src/android/jpeg/post_processor_jpeg.h
> > >> > @@ -8,11 +8,11 @@
> > >> >  #pragma once
> > >> >
> > >> >  #include "../post_processor.h"
> > >> > -#include "encoder_libjpeg.h"
> > >> > -#include "thumbnailer.h"
> > >> >
> > >> >  #include <libcamera/geometry.h>
> > >> >
> > >> > +#include "encoder.h"
> > >> > +
> > >> >  class CameraDevice;
> > >> >
> > >> >  class PostProcessorJpeg : public PostProcessor
> > >> > @@ -25,14 +25,9 @@ public:
> > >> >       void process(Camera3RequestDescriptor::StreamBuffer
> > >> *streamBuffer) override;
> > >> >
> > >> >  private:
> > >> > -     void generateThumbnail(const libcamera::FrameBuffer &source,
> > >> > -                            const libcamera::Size &targetSize,
> > >> > -                            unsigned int quality,
> > >> > -                            std::vector<unsigned char> *thumbnail);
> > >> > +     void SetEncoder();
> > >> >
> > >> >       CameraDevice *const cameraDevice_;
> > >> >       std::unique_ptr<Encoder> encoder_;
> > >> >       libcamera::Size streamSize_;
> > >> > -     EncoderLibJpeg thumbnailEncoder_;
> > >> > -     Thumbnailer thumbnailer_;
> > >> >  };
> > >> > diff --git a/src/android/meson.build b/src/android/meson.build
> > >> > index 27be27bb..026b8b3c 100644
> > >> > --- a/src/android/meson.build
> > >> > +++ b/src/android/meson.build
> > >> > @@ -48,10 +48,6 @@ android_hal_sources = files([
> > >> >      'camera_ops.cpp',
> > >> >      'camera_request.cpp',
> > >> >      'camera_stream.cpp',
> > >> > -    'jpeg/encoder_libjpeg.cpp',
> > >> > -    'jpeg/exif.cpp',
> > >> > -    'jpeg/post_processor_jpeg.cpp',
> > >> > -    'jpeg/thumbnailer.cpp',
> > >> >      'yuv/post_processor_yuv.cpp'
> > >> >  ])
> > >> >
> > >> > @@ -59,6 +55,7 @@ android_cpp_args = []
> > >> >
> > >> >  subdir('cros')
> > >> >  subdir('mm')
> > >> > +subdir('jpeg')
> > >>
> > >> Please keep these alphabetically sorted.
> > >>
> > >> >
> > >> >  android_camera_metadata_sources = files([
> > >> >      'metadata/camera_metadata.c',
> > >>
> > >> --
> > >> Regards,
> > >>
> > >> Laurent Pinchart
> > >>
> > >
>
>
> --
> Regards,
>
> Laurent Pinchart
>

Patch
diff mbox series

diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h
index b974d367..6d527d91 100644
--- a/src/android/jpeg/encoder.h
+++ b/src/android/jpeg/encoder.h
@@ -12,14 +12,19 @@ 
 #include <libcamera/framebuffer.h>
 #include <libcamera/stream.h>
 
+#include "../camera_request.h"
+
 class Encoder
 {
 public:
 	virtual ~Encoder() = default;
 
 	virtual int configure(const libcamera::StreamConfiguration &cfg) = 0;
-	virtual int encode(const libcamera::FrameBuffer &source,
-			   libcamera::Span<uint8_t> destination,
+	virtual int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
 			   libcamera::Span<const uint8_t> exifData,
 			   unsigned int quality) = 0;
+	virtual int generateThumbnail(const libcamera::FrameBuffer &source,
+				      const libcamera::Size &targetSize,
+				      unsigned int quality,
+				      std::vector<unsigned char> *thumbnail) = 0;
 };
diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp
index 21a3b33d..b5591e33 100644
--- a/src/android/jpeg/encoder_libjpeg.cpp
+++ b/src/android/jpeg/encoder_libjpeg.cpp
@@ -24,6 +24,8 @@ 
 #include "libcamera/internal/formats.h"
 #include "libcamera/internal/mapped_framebuffer.h"
 
+#include "../camera_buffer.h"
+
 using namespace libcamera;
 
 LOG_DECLARE_CATEGORY(JPEG)
@@ -82,8 +84,17 @@  EncoderLibJpeg::~EncoderLibJpeg()
 }
 
 int EncoderLibJpeg::configure(const StreamConfiguration &cfg)
+{
+	thumbnailer_.configure(cfg.size, cfg.pixelFormat);
+	cfg_ = cfg;
+
+	return internalConfigure(cfg);
+}
+
+int EncoderLibJpeg::internalConfigure(const StreamConfiguration &cfg)
 {
 	const struct JPEGPixelFormatInfo info = findPixelInfo(cfg.pixelFormat);
+
 	if (info.colorSpace == JCS_UNKNOWN)
 		return -ENOTSUP;
 
@@ -178,6 +189,65 @@  void EncoderLibJpeg::compressNV(const std::vector<Span<uint8_t>> &planes)
 	}
 }
 
+int EncoderLibJpeg::encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
+			   libcamera::Span<const uint8_t> exifData,
+			   unsigned int quality)
+{
+	internalConfigure(cfg_);
+	return encode(*streamBuffer->srcBuffer, streamBuffer->dstBuffer.get()->plane(0), exifData, quality);
+}
+
+int EncoderLibJpeg::generateThumbnail(
+	const libcamera::FrameBuffer &source,
+	const libcamera::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();
+	int ret = internalConfigure(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 = encode(thumbnailPlanes, *thumbnail, {}, quality);
+		thumbnail->resize(jpeg_size);
+
+		LOG(JPEG, Debug)
+			<< "Thumbnail compress returned "
+			<< jpeg_size << " bytes";
+
+		return jpeg_size;
+	}
+
+	return -1;
+}
+
 int EncoderLibJpeg::encode(const FrameBuffer &source, Span<uint8_t> dest,
 			   Span<const uint8_t> exifData, unsigned int quality)
 {
diff --git a/src/android/jpeg/encoder_libjpeg.h b/src/android/jpeg/encoder_libjpeg.h
index 1b3ac067..56b27bae 100644
--- a/src/android/jpeg/encoder_libjpeg.h
+++ b/src/android/jpeg/encoder_libjpeg.h
@@ -15,6 +15,8 @@ 
 
 #include <jpeglib.h>
 
+#include "thumbnailer.h"
+
 class EncoderLibJpeg : public Encoder
 {
 public:
@@ -22,19 +24,32 @@  public:
 	~EncoderLibJpeg();
 
 	int configure(const libcamera::StreamConfiguration &cfg) override;
+	int encode(Camera3RequestDescriptor::StreamBuffer *streamBuffer,
+		   libcamera::Span<const uint8_t> exifData,
+		   unsigned int quality) override;
+	int generateThumbnail(
+		const libcamera::FrameBuffer &source,
+		const libcamera::Size &targetSize,
+		unsigned int quality,
+		std::vector<unsigned char> *thumbnail) override;
+
+private:
+	int internalConfigure(const libcamera::StreamConfiguration &cfg);
+
 	int encode(const libcamera::FrameBuffer &source,
 		   libcamera::Span<uint8_t> destination,
 		   libcamera::Span<const uint8_t> exifData,
-		   unsigned int quality) override;
+		   unsigned int quality);
 	int encode(const std::vector<libcamera::Span<uint8_t>> &planes,
 		   libcamera::Span<uint8_t> destination,
 		   libcamera::Span<const uint8_t> exifData,
 		   unsigned int quality);
 
-private:
 	void compressRGB(const std::vector<libcamera::Span<uint8_t>> &planes);
 	void compressNV(const std::vector<libcamera::Span<uint8_t>> &planes);
 
+	libcamera::StreamConfiguration cfg_;
+
 	struct jpeg_compress_struct compress_;
 	struct jpeg_error_mgr jerr_;
 
@@ -42,4 +57,6 @@  private:
 
 	bool nv_;
 	bool nvSwap_;
+
+	Thumbnailer thumbnailer_;
 };
diff --git a/src/android/jpeg/generic_post_processor_jpeg.cpp b/src/android/jpeg/generic_post_processor_jpeg.cpp
new file mode 100644
index 00000000..890f6972
--- /dev/null
+++ b/src/android/jpeg/generic_post_processor_jpeg.cpp
@@ -0,0 +1,14 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ *
+ * generic_post_processor_jpeg.cpp - Generic JPEG Post Processor
+ */
+
+#include "encoder_libjpeg.h"
+#include "post_processor_jpeg.h"
+
+void PostProcessorJpeg::SetEncoder()
+{
+	encoder_ = std::make_unique<EncoderLibJpeg>();
+}
diff --git a/src/android/jpeg/meson.build b/src/android/jpeg/meson.build
new file mode 100644
index 00000000..94522dc0
--- /dev/null
+++ b/src/android/jpeg/meson.build
@@ -0,0 +1,9 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+android_hal_sources += files([
+    'exif.cpp',
+    'post_processor_jpeg.cpp'])
+
+android_hal_sources += files(['encoder_libjpeg.cpp',
+                              'generic_post_processor_jpeg.cpp',
+                              'thumbnailer.cpp'])
diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp
index d72ebc3c..7ceba60e 100644
--- a/src/android/jpeg/post_processor_jpeg.cpp
+++ b/src/android/jpeg/post_processor_jpeg.cpp
@@ -9,10 +9,10 @@ 
 
 #include <chrono>
 
+#include "../android_framebuffer.h"
 #include "../camera_device.h"
 #include "../camera_metadata.h"
 #include "../camera_request.h"
-#include "encoder_libjpeg.h"
 #include "exif.h"
 
 #include <libcamera/base/log.h>
@@ -44,60 +44,11 @@  int PostProcessorJpeg::configure(const StreamConfiguration &inCfg,
 
 	streamSize_ = outCfg.size;
 
-	thumbnailer_.configure(inCfg.size, inCfg.pixelFormat);
-
-	encoder_ = std::make_unique<EncoderLibJpeg>();
+	SetEncoder();
 
 	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();
-	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";
-	}
-}
-
 void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBuffer)
 {
 	ASSERT(encoder_);
@@ -164,8 +115,8 @@  void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
 
 		if (thumbnailSize != Size(0, 0)) {
 			std::vector<unsigned char> thumbnail;
-			generateThumbnail(source, thumbnailSize, quality, &thumbnail);
-			if (!thumbnail.empty())
+			ret = encoder_->generateThumbnail(source, thumbnailSize, quality, &thumbnail);
+			if (ret > 0 && !thumbnail.empty())
 				exif.setThumbnail(thumbnail, Exif::Compression::JPEG);
 		}
 
@@ -194,8 +145,7 @@  void PostProcessorJpeg::process(Camera3RequestDescriptor::StreamBuffer *streamBu
 	const uint8_t quality = ret ? *entry.data.u8 : 95;
 	resultMetadata->addEntry(ANDROID_JPEG_QUALITY, quality);
 
-	int jpeg_size = encoder_->encode(source, destination->plane(0),
-					 exif.data(), 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);
diff --git a/src/android/jpeg/post_processor_jpeg.h b/src/android/jpeg/post_processor_jpeg.h
index 98309b01..a09f8798 100644
--- a/src/android/jpeg/post_processor_jpeg.h
+++ b/src/android/jpeg/post_processor_jpeg.h
@@ -8,11 +8,11 @@ 
 #pragma once
 
 #include "../post_processor.h"
-#include "encoder_libjpeg.h"
-#include "thumbnailer.h"
 
 #include <libcamera/geometry.h>
 
+#include "encoder.h"
+
 class CameraDevice;
 
 class PostProcessorJpeg : public PostProcessor
@@ -25,14 +25,9 @@  public:
 	void process(Camera3RequestDescriptor::StreamBuffer *streamBuffer) override;
 
 private:
-	void generateThumbnail(const libcamera::FrameBuffer &source,
-			       const libcamera::Size &targetSize,
-			       unsigned int quality,
-			       std::vector<unsigned char> *thumbnail);
+	void SetEncoder();
 
 	CameraDevice *const cameraDevice_;
 	std::unique_ptr<Encoder> encoder_;
 	libcamera::Size streamSize_;
-	EncoderLibJpeg thumbnailEncoder_;
-	Thumbnailer thumbnailer_;
 };
diff --git a/src/android/meson.build b/src/android/meson.build
index 27be27bb..026b8b3c 100644
--- a/src/android/meson.build
+++ b/src/android/meson.build
@@ -48,10 +48,6 @@  android_hal_sources = files([
     'camera_ops.cpp',
     'camera_request.cpp',
     'camera_stream.cpp',
-    'jpeg/encoder_libjpeg.cpp',
-    'jpeg/exif.cpp',
-    'jpeg/post_processor_jpeg.cpp',
-    'jpeg/thumbnailer.cpp',
     'yuv/post_processor_yuv.cpp'
 ])
 
@@ -59,6 +55,7 @@  android_cpp_args = []
 
 subdir('cros')
 subdir('mm')
+subdir('jpeg')
 
 android_camera_metadata_sources = files([
     'metadata/camera_metadata.c',