[libcamera-devel] [PATCH v1 1/3] android: jpeg: Add a basic NV12 image thumbnailer

Laurent Pinchart laurent.pinchart at ideasonboard.com
Sun Oct 4 22:46:27 CEST 2020


Hi Umang,

One more thing.

On Sun, Oct 04, 2020 at 10:16:57PM +0300, Laurent Pinchart wrote:
> On Fri, Oct 02, 2020 at 04:04:14PM +0530, Umang Jain wrote:
> > Add a basic image thumbnailer for NV12 frames being captured.
> > It shall generate a thumbnail image to be embedded as a part of
> > EXIF metadata of the frame. The output of the thumbnail will still
> > be NV12.
> > 
> > Signed-off-by: Umang Jain <email at uajain.com>
> > ---
> >  src/android/jpeg/encoder_libjpeg.cpp |  12 +++
> >  src/android/jpeg/thumbnailer.cpp     | 106 +++++++++++++++++++++++++++
> >  src/android/jpeg/thumbnailer.h       |  34 +++++++++
> >  src/android/meson.build              |   1 +
> >  4 files changed, 153 insertions(+)
> >  create mode 100644 src/android/jpeg/thumbnailer.cpp
> >  create mode 100644 src/android/jpeg/thumbnailer.h
> > 
> > diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp
> > index 510613c..7d097c6 100644
> > --- a/src/android/jpeg/encoder_libjpeg.cpp
> > +++ b/src/android/jpeg/encoder_libjpeg.cpp
> > @@ -6,6 +6,7 @@
> >   */
> >  
> >  #include "encoder_libjpeg.h"
> > +#include "thumbnailer.h"
> >  
> >  #include <fcntl.h>
> >  #include <iomanip>
> > @@ -214,6 +215,17 @@ int EncoderLibJpeg::encode(const FrameBuffer *source,
> >  	LOG(JPEG, Debug) << "JPEG Encode Starting:" << compress_.image_width
> >  			 << "x" << compress_.image_height;
> >  
> > +	Thumbnailer thScaler;
> > +	libcamera::Span<uint8_t> thumbnail;
> > +	thScaler.configure(Size (compress_.image_width, compress_.image_height),
> > +			   pixelFormatInfo_->format);
> > +	thScaler.scaleBuffer(source, thumbnail);
> > +	/*
> > +	 * \todo: Discuss if we require scaling here or one level above where
> > +	 * exif data is set (setThumbnail()?).
> > +	 * Setting thumbnailed scaled data seems a bit convulated initially.
> > +	 */
> 
> Here's a proposal.
> 
> As the JPEG encoder could also be used to create the thumbnail, I would
> create an additional layer. A new JPEGPostProcessor (we can bikeshed the
> name) class would handle the whole JFIF image creation. It would would
> internally encode the image to JPEG using EncoderLibjpeg, downscale it
> to thumbnail size, compress the thumbnail with EncoderLibjpeg, generate
> the Exif, and store the JPEG-compressed thumbnail in the Exif. That way,
> JPEG encoding, Exif generation and thumbnail generation are all handled
> inside JPEGPostProcessor and not visible to the CameraDevice or
> CameraStream, and the JPEG encoder itself is kept generic enough to be
> used for both the main image and the thumbnail.
> 
> > +
> >  	if (nv_)
> >  		compressNV(&frame);
> >  	else
> > diff --git a/src/android/jpeg/thumbnailer.cpp b/src/android/jpeg/thumbnailer.cpp
> > new file mode 100644
> > index 0000000..d706ea6
> > --- /dev/null
> > +++ b/src/android/jpeg/thumbnailer.cpp
> > @@ -0,0 +1,106 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2020, Google Inc.
> > + *
> > + * thumbnailer.cpp - Basic image thumbnailer from NV12
> > + */
> > +
> > +#include "thumbnailer.h"
> > +#include "system/graphics.h"
> 
> This header shouldn't be needed, the implementation of this class should
> not be Android-specific.
> 
> > +
> > +#include "libcamera/internal/file.h"
> > +#include "libcamera/internal/log.h"
> > +
> > +using namespace libcamera;
> > +
> > +LOG_DEFINE_CATEGORY(Thumbnailer)
> > +
> > +Thumbnailer::Thumbnailer()
> > +	: validConfiguration_(false)
> > +{
> > +}
> > +
> > +void Thumbnailer::configure(const Size &sourceSize, PixelFormat pixelFormat)
> > +{
> > +	sourceSize_ = sourceSize;
> > +	pixelFormat_ = pixelFormat;
> > +
> > +	if (pixelFormat_ .fourcc() == HAL_PIXEL_FORMAT_YV12) {
> 
> Please don't use fourcc(), this is completely wrong. PixelFormat stores
> a DRM fourcc, and you're comparing it to a completely unrelated value.
> The PixelFormat class is meant to abstract pixel formats, you must
> compare it with formats::NV12 instead.
> 
> And shouldn't the check be reversed ? This accepts all formats except
> NV12.
> 
> > +		LOG (Thumbnailer, Error) << "Failed to configure: Pixel Format "
> > +				    << pixelFormat_.toString() << " unsupported.";
> > +		return;
> > +	}
> > +
> > +	validConfiguration_ = true;
> > +}
> 
> I'd simplify the API by passing the pixel format and size to the
> scaleBuffer() functionn and remove the configure() function.
> 
> > +
> > +/* Compute a thumbnail size with same aspect ratio as source. */
> 
> Could you please extend this comment to explain how the size is computed
> ? Not a description of the implementation, but the rules that the
> function implements.
> 
> > +void Thumbnailer::computeThumbnailSize()
> > +{
> > +	unsigned int baseWidth = 160;
> > +
> > +	unsigned int width = baseWidth * sourceSize_.width / sourceSize_.height;
> > +	targetSize_.width = width - (width % 16);
> > +	targetSize_.height = targetSize_.width * sourceSize_.height
> > +			     / sourceSize_.width;
> > +	if (targetSize_.height & 1)
> > +		targetSize_.height++;
> 
> With a source aspect ratio of 4:3, this outputs 208x156 instead of the
> expected 160x120.
> 
> > +}
> > +
> > +int Thumbnailer::scaleBuffer(const FrameBuffer *source, Span<uint8_t> &dest)
> > +{
> > +	MappedFrameBuffer frame(source, PROT_READ);
> 
> Mappings are expensive. I think the caller should create the
> MappedFrameBuffer and pass it to both the JPEG encoder and the thumbnail
> generator.
> 
> > +	if (!frame.isValid()) {
> > +		LOG(Thumbnailer, Error) << "Failed to map FrameBuffer : "
> > +				        << strerror(frame.error());
> > +		return frame.error();
> > +	}
> > +
> > +	if (!validConfiguration_) {
> > +		LOG(Thumbnailer, Error) << "config is unconfigured or invalid.";
> > +		return -1;
> > +	}
> > +
> > +	computeThumbnailSize();
> 
> You can make the function return the size instead of storing it in a
> member variable.
> 
> > +
> > +	const unsigned int sw = sourceSize_.width;
> > +	const unsigned int sh = sourceSize_.height;
> > +	const unsigned int tw = targetSize_.width;
> > +	const unsigned int th = targetSize_.height;
> > +
> > +	/* Image scaling block implementing nearest-neighbour algorithm. */
> > +	unsigned char *src = static_cast<unsigned char *>(frame.maps()[0].data());
> > +	unsigned char *src_c = src + sh * sw;

The stride shouldn't be hardcoded to sh, but should come from the stream
configuration. This doesn't have to be implemented yet, a \todo comment
would be fine for now (see encoder_libjpeg.cpp). I would however
recommend using PixelFormatInfo to calculate the stride for now (see
encoder_libjpeg.cpp too :-)).

> > +	unsigned int cb_pos = 0;
> > +	unsigned int cr_pos = 1;
> > +	unsigned char *src_cb, *src_cr;
> > +
> > +	size_t dstSize = (th * tw) + ((th/2) * tw);
> 
> No need for parentheses.
> 
> > +	unsigned char *destination = static_cast<unsigned char *>(malloc(dstSize));
> 
> C++ uses new.
> 
> > +	unsigned char *dst = destination;
> > +	unsigned char *dst_c = destination + th * tw;
> > +
> > +	for (unsigned int y = 0; y < th; y+=2) {
> 
> s/+=/ += /
> 
> There are a few other locations below that need spaces around operators.
> 
> > +		unsigned int sourceY = (sh*y + th/2) / th;
> > +
> > +		src_cb = src_c + (sourceY/2) * sw + cb_pos;
> > +		src_cr = src_c + (sourceY/2) * sw + cr_pos;
> 
> I'd create a new src_y variable to handle the luma plane the same way as
> the chroma plane.
> 
> > +
> > +		for (unsigned int x = 0; x < tw; x+=2) {
> > +			unsigned int sourceX = (sw*x + tw/2) / tw;
> > +
> > +			dst[y     * tw + x]     = src[sw * sourceY     + sourceX];
> > +			dst[(y+1) * tw + x]     = src[sw * (sourceY+1) + sourceX];
> > +			dst[y     * tw + (x+1)] = src[sw * sourceY     + (sourceX+1)];
> > +			dst[(y+1) * tw + (x+1)] = src[sw * (sourceY+1) + (sourceX+1)];
> > +
> > +			dst_c[(y/2) * tw + x + cb_pos] = src_cb[(sourceX/2) * 2];
> > +			dst_c[(y/2) * tw + x + cr_pos] = src_cr[(sourceX/2) * 2];
> 
> You can just do
> 
> 			dst_c[(y/2) * tw + x + 0] = src_c[(sourceX/2) * 2 + 0];
> 			dst_c[(y/2) * tw + x + 1] = src_c[(sourceX/2) * 2 + 1];
> 
> and drop cb_pos, cr_pos, src_cb and src_cr, as the only thing that
> matters it to copy both pixels.
> 
> src_c should be handled the same way as src_cb (computed in the outer
> loop), and I would do the same for dst_c (and add a new dst_y).
> 
> > +		}
> > +	}
> > +
> > +	/* Write scaled pixels to dest */
> > +	dest = { destination, dstSize };
> 
> This is a very good way to cause memory leaks :-S There's nothing in the
> function signature that tells that it allocates memory internally. Let's
> create a better interface that makes leaks impossible.
> 
> > +
> > +	return 0;
> > +}
> > diff --git a/src/android/jpeg/thumbnailer.h b/src/android/jpeg/thumbnailer.h
> > new file mode 100644
> > index 0000000..8ba9816
> > --- /dev/null
> > +++ b/src/android/jpeg/thumbnailer.h
> > @@ -0,0 +1,34 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2020, Google Inc.
> > + *
> > + * thumbnailer.h - Basic image thumbnailer from NV12
> > + */
> > +#ifndef __ANDROID_JPEG_THUMBNAILER_H__
> > +#define __ANDROID_JPEG_THUMBNAILER_H__
> > +
> > +#include <libcamera/geometry.h>
> > +
> > +#include "libcamera/internal/buffer.h"
> > +#include "libcamera/internal/formats.h"
> > +
> > +class Thumbnailer
> > +{
> > +public:
> > +	Thumbnailer();
> > +
> > +	void configure(const libcamera::Size &sourceSize,
> > +		       libcamera::PixelFormat pixelFormat);
> > +	int scaleBuffer(const libcamera::FrameBuffer *source, libcamera::Span<uint8_t> &dest);
> > +
> > +private:
> > +	void computeThumbnailSize();
> > +
> > +	libcamera::PixelFormat pixelFormat_;
> > +	libcamera::Size sourceSize_;
> > +	libcamera::Size targetSize_;
> > +
> > +	bool validConfiguration_;
> > +};
> > +
> > +#endif /* __ANDROID_JPEG_THUMBNAILER_H__ */
> > diff --git a/src/android/meson.build b/src/android/meson.build
> > index 0293c20..f35ba04 100644
> > --- a/src/android/meson.build
> > +++ b/src/android/meson.build
> > @@ -22,6 +22,7 @@ android_hal_sources = files([
> >      'camera_ops.cpp',
> >      'jpeg/encoder_libjpeg.cpp',
> >      'jpeg/exif.cpp',
> > +    'jpeg/thumbnailer.cpp',
> >  ])
> >  
> >  android_camera_metadata_sources = files([

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list