[libcamera-devel,v4] cam: sdl_sink: Use libjpeg over SDL2_image
diff mbox series

Message ID 20220719091745.8040-1-ecurtin@redhat.com
State Accepted
Headers show
Series
  • [libcamera-devel,v4] cam: sdl_sink: Use libjpeg over SDL2_image
Related show

Commit Message

Eric Curtin July 19, 2022, 9:17 a.m. UTC
We were using the libjpeg functionality of SDL2_image only, instead just
use libjpeg directly to reduce our dependancy count, it is a more
commonly available library.

Signed-off-by: Eric Curtin <ecurtin@redhat.com>
---
Changes in v4:
- decompress function takes a Span<const uint8_t>&

Changes in v3:
- create JpegErrorManager struct
- change c cast to reinterpret_cast

Changes in v2:
- alphabetically sorted various orderings
- pitch_ is const again
- added setjmp logic for error handling in libjpeg
- rgbdata_ to unique_ptr and renamed to rgb_
- removed a copy from buffer to rgb_
- removed a destructor
---
 README.rst                     |  2 +-
 src/cam/jpeg_error_manager.cpp | 26 ++++++++++++++++++++
 src/cam/jpeg_error_manager.h   | 21 ++++++++++++++++
 src/cam/meson.build            |  9 +++----
 src/cam/sdl_sink.cpp           |  4 ++--
 src/cam/sdl_texture_mjpg.cpp   | 44 +++++++++++++++++++++++++++++-----
 src/cam/sdl_texture_mjpg.h     |  6 +++++
 7 files changed, 99 insertions(+), 13 deletions(-)
 create mode 100644 src/cam/jpeg_error_manager.cpp
 create mode 100644 src/cam/jpeg_error_manager.h

Comments

Laurent Pinchart July 20, 2022, 11:55 a.m. UTC | #1
Hi Eric,

Thank you for the patch.

On Tue, Jul 19, 2022 at 10:17:46AM +0100, Eric Curtin wrote:
> We were using the libjpeg functionality of SDL2_image only, instead just
> use libjpeg directly to reduce our dependancy count, it is a more
> commonly available library.
> 
> Signed-off-by: Eric Curtin <ecurtin@redhat.com>
> ---
> Changes in v4:
> - decompress function takes a Span<const uint8_t>&
> 
> Changes in v3:
> - create JpegErrorManager struct
> - change c cast to reinterpret_cast
> 
> Changes in v2:
> - alphabetically sorted various orderings
> - pitch_ is const again
> - added setjmp logic for error handling in libjpeg
> - rgbdata_ to unique_ptr and renamed to rgb_
> - removed a copy from buffer to rgb_
> - removed a destructor
> ---
>  README.rst                     |  2 +-
>  src/cam/jpeg_error_manager.cpp | 26 ++++++++++++++++++++
>  src/cam/jpeg_error_manager.h   | 21 ++++++++++++++++
>  src/cam/meson.build            |  9 +++----
>  src/cam/sdl_sink.cpp           |  4 ++--
>  src/cam/sdl_texture_mjpg.cpp   | 44 +++++++++++++++++++++++++++++-----
>  src/cam/sdl_texture_mjpg.h     |  6 +++++
>  7 files changed, 99 insertions(+), 13 deletions(-)
>  create mode 100644 src/cam/jpeg_error_manager.cpp
>  create mode 100644 src/cam/jpeg_error_manager.h
> 
> diff --git a/README.rst b/README.rst
> index b9e72d81..47b914f0 100644
> --- a/README.rst
> +++ b/README.rst
> @@ -92,8 +92,8 @@ for cam: [optional]
>          tool:
>  
>          - libdrm-dev: Enables the KMS sink
> +        - libjpeg-dev: Enables MJPEG on the SDL sink
>          - libsdl2-dev: Enables the SDL sink
> -        - libsdl2-image-dev: Supports MJPEG on the SDL sink
>  
>  for qcam: [optional]
>          qtbase5-dev libqt5core5a libqt5gui5 libqt5widgets5 qttools5-dev-tools libtiff-dev
> diff --git a/src/cam/jpeg_error_manager.cpp b/src/cam/jpeg_error_manager.cpp
> new file mode 100644
> index 00000000..7e45e577
> --- /dev/null
> +++ b/src/cam/jpeg_error_manager.cpp
> @@ -0,0 +1,26 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2022, Ideas on Board Oy
> + *
> + * jpeg_error_manager.cpp - JPEG Error Manager
> + */
> +
> +#include "jpeg_error_manager.h"
> +
> +static void errorExit(j_common_ptr cinfo)
> +{
> +	JpegErrorManager *self =
> +		reinterpret_cast<JpegErrorManager *>(cinfo->err);
> +	longjmp(self->escape_, 1);
> +}
> +
> +static void outputMessage([[maybe_unused]] j_common_ptr cinfo)
> +{
> +}
> +
> +JpegErrorManager::JpegErrorManager(struct jpeg_decompress_struct &cinfo)

I wouldn't mix the error manager and decompressor. You can keep the
jpeg_std_error() call below, and move the assignment of cinfo.err to the
code that creates the decompressor.

> +{
> +	cinfo.err = jpeg_std_error(&errmgr_);
> +	errmgr_.error_exit = errorExit;
> +	errmgr_.output_message = outputMessage;
> +}

This can live in sdl_texture_mjpg.cpp as it's internal to the
implementation of the SDL texture for MJPEG, there's no need to have one
file per class.

> diff --git a/src/cam/jpeg_error_manager.h b/src/cam/jpeg_error_manager.h
> new file mode 100644
> index 00000000..0af28da1
> --- /dev/null
> +++ b/src/cam/jpeg_error_manager.h
> @@ -0,0 +1,21 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2022, Ideas on Board Oy
> + *
> + * jpeg_error_manager.h - JPEG Error Manager
> + */
> +
> +#pragma once
> +
> +#include <setjmp.h>
> +#include <stdio.h>
> +
> +#include <jpeglib.h>
> +
> +struct JpegErrorManager {
> +	JpegErrorManager(struct jpeg_decompress_struct &cinfo);
> +
> +	/* Order very important for reinterpret_cast */
> +	struct jpeg_error_mgr errmgr_;
> +	jmp_buf escape_;
> +};
> diff --git a/src/cam/meson.build b/src/cam/meson.build
> index 5957ce14..86cef683 100644
> --- a/src/cam/meson.build
> +++ b/src/cam/meson.build
> @@ -24,8 +24,8 @@ cam_sources = files([
>  cam_cpp_args = []
>  
>  libdrm = dependency('libdrm', required : false)
> +libjpeg = dependency('libjpeg', required : false)
>  libsdl2 = dependency('SDL2', required : false)
> -libsdl2_image = dependency('SDL2_image', required : false)
>  
>  if libdrm.found()
>      cam_cpp_args += [ '-DHAVE_KMS' ]
> @@ -43,9 +43,10 @@ if libsdl2.found()
>          'sdl_texture_yuyv.cpp'
>      ])
>  
> -    if libsdl2_image.found()
> -        cam_cpp_args += ['-DHAVE_SDL_IMAGE']
> +    if libjpeg.found()
> +        cam_cpp_args += ['-DHAVE_LIBJPEG']
>          cam_sources += files([
> +            'jpeg_error_manager.cpp',
>              'sdl_texture_mjpg.cpp'
>          ])
>      endif
> @@ -57,8 +58,8 @@ cam  = executable('cam', cam_sources,
>                        libcamera_public,
>                        libdrm,
>                        libevent,
> +                      libjpeg,
>                        libsdl2,
> -                      libsdl2_image,
>                        libyaml,
>                    ],
>                    cpp_args : cam_cpp_args,
> diff --git a/src/cam/sdl_sink.cpp b/src/cam/sdl_sink.cpp
> index f8e3e95d..19fdfd6d 100644
> --- a/src/cam/sdl_sink.cpp
> +++ b/src/cam/sdl_sink.cpp
> @@ -21,7 +21,7 @@
>  
>  #include "event_loop.h"
>  #include "image.h"
> -#ifdef HAVE_SDL_IMAGE
> +#ifdef HAVE_LIBJPEG
>  #include "sdl_texture_mjpg.h"
>  #endif
>  #include "sdl_texture_yuyv.h"
> @@ -62,7 +62,7 @@ int SDLSink::configure(const libcamera::CameraConfiguration &config)
>  	rect_.h = cfg.size.height;
>  
>  	switch (cfg.pixelFormat) {
> -#ifdef HAVE_SDL_IMAGE
> +#ifdef HAVE_LIBJPEG
>  	case libcamera::formats::MJPEG:
>  		texture_ = std::make_unique<SDLTextureMJPG>(rect_);
>  		break;
> diff --git a/src/cam/sdl_texture_mjpg.cpp b/src/cam/sdl_texture_mjpg.cpp
> index 69e99ad3..22f570c6 100644
> --- a/src/cam/sdl_texture_mjpg.cpp
> +++ b/src/cam/sdl_texture_mjpg.cpp
> @@ -7,19 +7,51 @@
>  
>  #include "sdl_texture_mjpg.h"
>  
> -#include <SDL2/SDL_image.h>
> +#include "jpeg_error_manager.h"
> +
> +#include <iostream>
>  
>  using namespace libcamera;
>  
>  SDLTextureMJPG::SDLTextureMJPG(const SDL_Rect &rect)
> -	: SDLTexture(rect, SDL_PIXELFORMAT_RGB24, 0)
> +	: SDLTexture(rect, SDL_PIXELFORMAT_RGB24, rect.w * 3),
> +	  rgb_(std::make_unique<unsigned char[]>(pitch_ * rect.h))
> +{
> +}
> +
> +int SDLTextureMJPG::decompress(const Span<uint8_t> &data)

int SDLTextureMJPG::decompress(Span<const uint8_t> data)

for two reasons:

- a Span is a pointer + size, which will fit in registers when calling
  this function, so that should be more efficient than passing a pointer
  to the Span

- Span<const uint8_t> to indicate that the function doesn't modify the
  data. A const Span<uint8_t> prevents changing the Span itself, but
  allows modifying the data that the Span points to.

This would however require changing the update() function accordingly,
so I'll submit a patch on top.

I've made those modifications locally to test and make sure I wasn't
saying something stupid, so I'll send a v5 shortly as it's ready.

>  {
> +	struct jpeg_decompress_struct cinfo;
> +	JpegErrorManager jpegErrorManager(cinfo);
> +	if (setjmp(jpegErrorManager.escape_)) {
> +		/* libjpeg found an error */
> +		jpeg_destroy_decompress(&cinfo);
> +		std::cerr << "JPEG decompression error" << std::endl;
> +		return -EINVAL;
> +	}
> +
> +	jpeg_create_decompress(&cinfo);
> +
> +	jpeg_mem_src(&cinfo, data.data(), data.size());
> +
> +	jpeg_read_header(&cinfo, TRUE);
> +
> +	jpeg_start_decompress(&cinfo);
> +
> +	for (int i = 0; cinfo.output_scanline < cinfo.output_height; ++i) {
> +		JSAMPROW rowptr = rgb_.get() + i * pitch_;
> +		jpeg_read_scanlines(&cinfo, &rowptr, 1);
> +	}
> +
> +	jpeg_finish_decompress(&cinfo);
> +
> +	jpeg_destroy_decompress(&cinfo);
> +
> +	return 0;
>  }
>  
>  void SDLTextureMJPG::update(const Span<uint8_t> &data)
>  {
> -	SDL_RWops *bufferStream = SDL_RWFromMem(data.data(), data.size());
> -	SDL_Surface *frame = IMG_Load_RW(bufferStream, 0);
> -	SDL_UpdateTexture(ptr_, nullptr, frame->pixels, frame->pitch);
> -	SDL_FreeSurface(frame);
> +	decompress(data);
> +	SDL_UpdateTexture(ptr_, nullptr, rgb_.get(), pitch_);
>  }
> diff --git a/src/cam/sdl_texture_mjpg.h b/src/cam/sdl_texture_mjpg.h
> index b103f801..328c45a9 100644
> --- a/src/cam/sdl_texture_mjpg.h
> +++ b/src/cam/sdl_texture_mjpg.h
> @@ -13,5 +13,11 @@ class SDLTextureMJPG : public SDLTexture
>  {
>  public:
>  	SDLTextureMJPG(const SDL_Rect &rect);
> +
>  	void update(const libcamera::Span<uint8_t> &data) override;
> +
> +private:
> +	int decompress(const libcamera::Span<uint8_t> &data);
> +
> +	std::unique_ptr<unsigned char[]> rgb_;
>  };
Eric Curtin July 20, 2022, 1:54 p.m. UTC | #2
On Wed, 20 Jul 2022 at 12:56, Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
>
> Hi Eric,
>
> Thank you for the patch.
>
> On Tue, Jul 19, 2022 at 10:17:46AM +0100, Eric Curtin wrote:
> > We were using the libjpeg functionality of SDL2_image only, instead just
> > use libjpeg directly to reduce our dependancy count, it is a more
> > commonly available library.
> >
> > Signed-off-by: Eric Curtin <ecurtin@redhat.com>
> > ---
> > Changes in v4:
> > - decompress function takes a Span<const uint8_t>&
> >
> > Changes in v3:
> > - create JpegErrorManager struct
> > - change c cast to reinterpret_cast
> >
> > Changes in v2:
> > - alphabetically sorted various orderings
> > - pitch_ is const again
> > - added setjmp logic for error handling in libjpeg
> > - rgbdata_ to unique_ptr and renamed to rgb_
> > - removed a copy from buffer to rgb_
> > - removed a destructor
> > ---
> >  README.rst                     |  2 +-
> >  src/cam/jpeg_error_manager.cpp | 26 ++++++++++++++++++++
> >  src/cam/jpeg_error_manager.h   | 21 ++++++++++++++++
> >  src/cam/meson.build            |  9 +++----
> >  src/cam/sdl_sink.cpp           |  4 ++--
> >  src/cam/sdl_texture_mjpg.cpp   | 44 +++++++++++++++++++++++++++++-----
> >  src/cam/sdl_texture_mjpg.h     |  6 +++++
> >  7 files changed, 99 insertions(+), 13 deletions(-)
> >  create mode 100644 src/cam/jpeg_error_manager.cpp
> >  create mode 100644 src/cam/jpeg_error_manager.h
> >
> > diff --git a/README.rst b/README.rst
> > index b9e72d81..47b914f0 100644
> > --- a/README.rst
> > +++ b/README.rst
> > @@ -92,8 +92,8 @@ for cam: [optional]
> >          tool:
> >
> >          - libdrm-dev: Enables the KMS sink
> > +        - libjpeg-dev: Enables MJPEG on the SDL sink
> >          - libsdl2-dev: Enables the SDL sink
> > -        - libsdl2-image-dev: Supports MJPEG on the SDL sink
> >
> >  for qcam: [optional]
> >          qtbase5-dev libqt5core5a libqt5gui5 libqt5widgets5 qttools5-dev-tools libtiff-dev
> > diff --git a/src/cam/jpeg_error_manager.cpp b/src/cam/jpeg_error_manager.cpp
> > new file mode 100644
> > index 00000000..7e45e577
> > --- /dev/null
> > +++ b/src/cam/jpeg_error_manager.cpp
> > @@ -0,0 +1,26 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2022, Ideas on Board Oy
> > + *
> > + * jpeg_error_manager.cpp - JPEG Error Manager
> > + */
> > +
> > +#include "jpeg_error_manager.h"
> > +
> > +static void errorExit(j_common_ptr cinfo)
> > +{
> > +     JpegErrorManager *self =
> > +             reinterpret_cast<JpegErrorManager *>(cinfo->err);
> > +     longjmp(self->escape_, 1);
> > +}
> > +
> > +static void outputMessage([[maybe_unused]] j_common_ptr cinfo)
> > +{
> > +}
> > +
> > +JpegErrorManager::JpegErrorManager(struct jpeg_decompress_struct &cinfo)
>
> I wouldn't mix the error manager and decompressor. You can keep the
> jpeg_std_error() call below, and move the assignment of cinfo.err to the
> code that creates the decompressor.
>
> > +{
> > +     cinfo.err = jpeg_std_error(&errmgr_);
> > +     errmgr_.error_exit = errorExit;
> > +     errmgr_.output_message = outputMessage;
> > +}
>
> This can live in sdl_texture_mjpg.cpp as it's internal to the
> implementation of the SDL texture for MJPEG, there's no need to have one
> file per class.
>
> > diff --git a/src/cam/jpeg_error_manager.h b/src/cam/jpeg_error_manager.h
> > new file mode 100644
> > index 00000000..0af28da1
> > --- /dev/null
> > +++ b/src/cam/jpeg_error_manager.h
> > @@ -0,0 +1,21 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2022, Ideas on Board Oy
> > + *
> > + * jpeg_error_manager.h - JPEG Error Manager
> > + */
> > +
> > +#pragma once
> > +
> > +#include <setjmp.h>
> > +#include <stdio.h>
> > +
> > +#include <jpeglib.h>
> > +
> > +struct JpegErrorManager {
> > +     JpegErrorManager(struct jpeg_decompress_struct &cinfo);
> > +
> > +     /* Order very important for reinterpret_cast */
> > +     struct jpeg_error_mgr errmgr_;
> > +     jmp_buf escape_;
> > +};
> > diff --git a/src/cam/meson.build b/src/cam/meson.build
> > index 5957ce14..86cef683 100644
> > --- a/src/cam/meson.build
> > +++ b/src/cam/meson.build
> > @@ -24,8 +24,8 @@ cam_sources = files([
> >  cam_cpp_args = []
> >
> >  libdrm = dependency('libdrm', required : false)
> > +libjpeg = dependency('libjpeg', required : false)
> >  libsdl2 = dependency('SDL2', required : false)
> > -libsdl2_image = dependency('SDL2_image', required : false)
> >
> >  if libdrm.found()
> >      cam_cpp_args += [ '-DHAVE_KMS' ]
> > @@ -43,9 +43,10 @@ if libsdl2.found()
> >          'sdl_texture_yuyv.cpp'
> >      ])
> >
> > -    if libsdl2_image.found()
> > -        cam_cpp_args += ['-DHAVE_SDL_IMAGE']
> > +    if libjpeg.found()
> > +        cam_cpp_args += ['-DHAVE_LIBJPEG']
> >          cam_sources += files([
> > +            'jpeg_error_manager.cpp',
> >              'sdl_texture_mjpg.cpp'
> >          ])
> >      endif
> > @@ -57,8 +58,8 @@ cam  = executable('cam', cam_sources,
> >                        libcamera_public,
> >                        libdrm,
> >                        libevent,
> > +                      libjpeg,
> >                        libsdl2,
> > -                      libsdl2_image,
> >                        libyaml,
> >                    ],
> >                    cpp_args : cam_cpp_args,
> > diff --git a/src/cam/sdl_sink.cpp b/src/cam/sdl_sink.cpp
> > index f8e3e95d..19fdfd6d 100644
> > --- a/src/cam/sdl_sink.cpp
> > +++ b/src/cam/sdl_sink.cpp
> > @@ -21,7 +21,7 @@
> >
> >  #include "event_loop.h"
> >  #include "image.h"
> > -#ifdef HAVE_SDL_IMAGE
> > +#ifdef HAVE_LIBJPEG
> >  #include "sdl_texture_mjpg.h"
> >  #endif
> >  #include "sdl_texture_yuyv.h"
> > @@ -62,7 +62,7 @@ int SDLSink::configure(const libcamera::CameraConfiguration &config)
> >       rect_.h = cfg.size.height;
> >
> >       switch (cfg.pixelFormat) {
> > -#ifdef HAVE_SDL_IMAGE
> > +#ifdef HAVE_LIBJPEG
> >       case libcamera::formats::MJPEG:
> >               texture_ = std::make_unique<SDLTextureMJPG>(rect_);
> >               break;
> > diff --git a/src/cam/sdl_texture_mjpg.cpp b/src/cam/sdl_texture_mjpg.cpp
> > index 69e99ad3..22f570c6 100644
> > --- a/src/cam/sdl_texture_mjpg.cpp
> > +++ b/src/cam/sdl_texture_mjpg.cpp
> > @@ -7,19 +7,51 @@
> >
> >  #include "sdl_texture_mjpg.h"
> >
> > -#include <SDL2/SDL_image.h>
> > +#include "jpeg_error_manager.h"
> > +
> > +#include <iostream>
> >
> >  using namespace libcamera;
> >
> >  SDLTextureMJPG::SDLTextureMJPG(const SDL_Rect &rect)
> > -     : SDLTexture(rect, SDL_PIXELFORMAT_RGB24, 0)
> > +     : SDLTexture(rect, SDL_PIXELFORMAT_RGB24, rect.w * 3),
> > +       rgb_(std::make_unique<unsigned char[]>(pitch_ * rect.h))
> > +{
> > +}
> > +
> > +int SDLTextureMJPG::decompress(const Span<uint8_t> &data)
>
> int SDLTextureMJPG::decompress(Span<const uint8_t> data)
>
> for two reasons:
>
> - a Span is a pointer + size, which will fit in registers when calling
>   this function, so that should be more efficient than passing a pointer
>   to the Span

I'm not sure about that:

#include <libcamera/libcamera/base/span.h>
#include <iostream>

int main() {
  std::cout << sizeof(const libcamera::Span<const uint8_t>) << std::endl;
  std::cout << sizeof(const libcamera::Span<const uint8_t>*) << std::endl;
  std::cout << sizeof(const libcamera::Span<const uint8_t>&) << std::endl;
}

Output:

16
8
16

Even though pass by value and pass by reference are the same amount of
bytes (both 16 bytes and the pointer is 8 on x86), I would expect pass
by reference in the worst case to be of equal performance because it
ensures no constructor of any kind is called (where another copy or
two could occur in pass by value, I don't even need to read the
constructor's source code if it's a reference, that's why class/struct
as reference parameter is a great rule of thumb). But I'm not sure
something like this is worth a v6 either, so it's fine the way it is I
guess.

>
> - Span<const uint8_t> to indicate that the function doesn't modify the
>   data. A const Span<uint8_t> prevents changing the Span itself, but
>   allows modifying the data that the Span points to.
>
> This would however require changing the update() function accordingly,
> so I'll submit a patch on top.
>
> I've made those modifications locally to test and make sure I wasn't
> saying something stupid, so I'll send a v5 shortly as it's ready.

Gonna run a test.

>
> >  {
> > +     struct jpeg_decompress_struct cinfo;
> > +     JpegErrorManager jpegErrorManager(cinfo);
> > +     if (setjmp(jpegErrorManager.escape_)) {
> > +             /* libjpeg found an error */
> > +             jpeg_destroy_decompress(&cinfo);
> > +             std::cerr << "JPEG decompression error" << std::endl;
> > +             return -EINVAL;
> > +     }
> > +
> > +     jpeg_create_decompress(&cinfo);
> > +
> > +     jpeg_mem_src(&cinfo, data.data(), data.size());
> > +
> > +     jpeg_read_header(&cinfo, TRUE);
> > +
> > +     jpeg_start_decompress(&cinfo);
> > +
> > +     for (int i = 0; cinfo.output_scanline < cinfo.output_height; ++i) {
> > +             JSAMPROW rowptr = rgb_.get() + i * pitch_;
> > +             jpeg_read_scanlines(&cinfo, &rowptr, 1);
> > +     }
> > +
> > +     jpeg_finish_decompress(&cinfo);
> > +
> > +     jpeg_destroy_decompress(&cinfo);
> > +
> > +     return 0;
> >  }
> >
> >  void SDLTextureMJPG::update(const Span<uint8_t> &data)
> >  {
> > -     SDL_RWops *bufferStream = SDL_RWFromMem(data.data(), data.size());
> > -     SDL_Surface *frame = IMG_Load_RW(bufferStream, 0);
> > -     SDL_UpdateTexture(ptr_, nullptr, frame->pixels, frame->pitch);
> > -     SDL_FreeSurface(frame);
> > +     decompress(data);
> > +     SDL_UpdateTexture(ptr_, nullptr, rgb_.get(), pitch_);
> >  }
> > diff --git a/src/cam/sdl_texture_mjpg.h b/src/cam/sdl_texture_mjpg.h
> > index b103f801..328c45a9 100644
> > --- a/src/cam/sdl_texture_mjpg.h
> > +++ b/src/cam/sdl_texture_mjpg.h
> > @@ -13,5 +13,11 @@ class SDLTextureMJPG : public SDLTexture
> >  {
> >  public:
> >       SDLTextureMJPG(const SDL_Rect &rect);
> > +
> >       void update(const libcamera::Span<uint8_t> &data) override;
> > +
> > +private:
> > +     int decompress(const libcamera::Span<uint8_t> &data);
> > +
> > +     std::unique_ptr<unsigned char[]> rgb_;
> >  };
>
> --
> Regards,
>
> Laurent Pinchart
>
Laurent Pinchart July 21, 2022, 8:48 p.m. UTC | #3
Hi Eric,

On Wed, Jul 20, 2022 at 02:54:29PM +0100, Eric Curtin wrote:
> On Wed, 20 Jul 2022 at 12:56, Laurent Pinchart wrote:
> > On Tue, Jul 19, 2022 at 10:17:46AM +0100, Eric Curtin wrote:
> > > We were using the libjpeg functionality of SDL2_image only, instead just
> > > use libjpeg directly to reduce our dependancy count, it is a more
> > > commonly available library.
> > >
> > > Signed-off-by: Eric Curtin <ecurtin@redhat.com>
> > > ---
> > > Changes in v4:
> > > - decompress function takes a Span<const uint8_t>&
> > >
> > > Changes in v3:
> > > - create JpegErrorManager struct
> > > - change c cast to reinterpret_cast
> > >
> > > Changes in v2:
> > > - alphabetically sorted various orderings
> > > - pitch_ is const again
> > > - added setjmp logic for error handling in libjpeg
> > > - rgbdata_ to unique_ptr and renamed to rgb_
> > > - removed a copy from buffer to rgb_
> > > - removed a destructor
> > > ---
> > >  README.rst                     |  2 +-
> > >  src/cam/jpeg_error_manager.cpp | 26 ++++++++++++++++++++
> > >  src/cam/jpeg_error_manager.h   | 21 ++++++++++++++++
> > >  src/cam/meson.build            |  9 +++----
> > >  src/cam/sdl_sink.cpp           |  4 ++--
> > >  src/cam/sdl_texture_mjpg.cpp   | 44 +++++++++++++++++++++++++++++-----
> > >  src/cam/sdl_texture_mjpg.h     |  6 +++++
> > >  7 files changed, 99 insertions(+), 13 deletions(-)
> > >  create mode 100644 src/cam/jpeg_error_manager.cpp
> > >  create mode 100644 src/cam/jpeg_error_manager.h
> > >
> > > diff --git a/README.rst b/README.rst
> > > index b9e72d81..47b914f0 100644
> > > --- a/README.rst
> > > +++ b/README.rst
> > > @@ -92,8 +92,8 @@ for cam: [optional]
> > >          tool:
> > >
> > >          - libdrm-dev: Enables the KMS sink
> > > +        - libjpeg-dev: Enables MJPEG on the SDL sink
> > >          - libsdl2-dev: Enables the SDL sink
> > > -        - libsdl2-image-dev: Supports MJPEG on the SDL sink
> > >
> > >  for qcam: [optional]
> > >          qtbase5-dev libqt5core5a libqt5gui5 libqt5widgets5 qttools5-dev-tools libtiff-dev
> > > diff --git a/src/cam/jpeg_error_manager.cpp b/src/cam/jpeg_error_manager.cpp
> > > new file mode 100644
> > > index 00000000..7e45e577
> > > --- /dev/null
> > > +++ b/src/cam/jpeg_error_manager.cpp
> > > @@ -0,0 +1,26 @@
> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > +/*
> > > + * Copyright (C) 2022, Ideas on Board Oy
> > > + *
> > > + * jpeg_error_manager.cpp - JPEG Error Manager
> > > + */
> > > +
> > > +#include "jpeg_error_manager.h"
> > > +
> > > +static void errorExit(j_common_ptr cinfo)
> > > +{
> > > +     JpegErrorManager *self =
> > > +             reinterpret_cast<JpegErrorManager *>(cinfo->err);
> > > +     longjmp(self->escape_, 1);
> > > +}
> > > +
> > > +static void outputMessage([[maybe_unused]] j_common_ptr cinfo)
> > > +{
> > > +}
> > > +
> > > +JpegErrorManager::JpegErrorManager(struct jpeg_decompress_struct &cinfo)
> >
> > I wouldn't mix the error manager and decompressor. You can keep the
> > jpeg_std_error() call below, and move the assignment of cinfo.err to the
> > code that creates the decompressor.
> >
> > > +{
> > > +     cinfo.err = jpeg_std_error(&errmgr_);
> > > +     errmgr_.error_exit = errorExit;
> > > +     errmgr_.output_message = outputMessage;
> > > +}
> >
> > This can live in sdl_texture_mjpg.cpp as it's internal to the
> > implementation of the SDL texture for MJPEG, there's no need to have one
> > file per class.
> >
> > > diff --git a/src/cam/jpeg_error_manager.h b/src/cam/jpeg_error_manager.h
> > > new file mode 100644
> > > index 00000000..0af28da1
> > > --- /dev/null
> > > +++ b/src/cam/jpeg_error_manager.h
> > > @@ -0,0 +1,21 @@
> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > +/*
> > > + * Copyright (C) 2022, Ideas on Board Oy
> > > + *
> > > + * jpeg_error_manager.h - JPEG Error Manager
> > > + */
> > > +
> > > +#pragma once
> > > +
> > > +#include <setjmp.h>
> > > +#include <stdio.h>
> > > +
> > > +#include <jpeglib.h>
> > > +
> > > +struct JpegErrorManager {
> > > +     JpegErrorManager(struct jpeg_decompress_struct &cinfo);
> > > +
> > > +     /* Order very important for reinterpret_cast */
> > > +     struct jpeg_error_mgr errmgr_;
> > > +     jmp_buf escape_;
> > > +};
> > > diff --git a/src/cam/meson.build b/src/cam/meson.build
> > > index 5957ce14..86cef683 100644
> > > --- a/src/cam/meson.build
> > > +++ b/src/cam/meson.build
> > > @@ -24,8 +24,8 @@ cam_sources = files([
> > >  cam_cpp_args = []
> > >
> > >  libdrm = dependency('libdrm', required : false)
> > > +libjpeg = dependency('libjpeg', required : false)
> > >  libsdl2 = dependency('SDL2', required : false)
> > > -libsdl2_image = dependency('SDL2_image', required : false)
> > >
> > >  if libdrm.found()
> > >      cam_cpp_args += [ '-DHAVE_KMS' ]
> > > @@ -43,9 +43,10 @@ if libsdl2.found()
> > >          'sdl_texture_yuyv.cpp'
> > >      ])
> > >
> > > -    if libsdl2_image.found()
> > > -        cam_cpp_args += ['-DHAVE_SDL_IMAGE']
> > > +    if libjpeg.found()
> > > +        cam_cpp_args += ['-DHAVE_LIBJPEG']
> > >          cam_sources += files([
> > > +            'jpeg_error_manager.cpp',
> > >              'sdl_texture_mjpg.cpp'
> > >          ])
> > >      endif
> > > @@ -57,8 +58,8 @@ cam  = executable('cam', cam_sources,
> > >                        libcamera_public,
> > >                        libdrm,
> > >                        libevent,
> > > +                      libjpeg,
> > >                        libsdl2,
> > > -                      libsdl2_image,
> > >                        libyaml,
> > >                    ],
> > >                    cpp_args : cam_cpp_args,
> > > diff --git a/src/cam/sdl_sink.cpp b/src/cam/sdl_sink.cpp
> > > index f8e3e95d..19fdfd6d 100644
> > > --- a/src/cam/sdl_sink.cpp
> > > +++ b/src/cam/sdl_sink.cpp
> > > @@ -21,7 +21,7 @@
> > >
> > >  #include "event_loop.h"
> > >  #include "image.h"
> > > -#ifdef HAVE_SDL_IMAGE
> > > +#ifdef HAVE_LIBJPEG
> > >  #include "sdl_texture_mjpg.h"
> > >  #endif
> > >  #include "sdl_texture_yuyv.h"
> > > @@ -62,7 +62,7 @@ int SDLSink::configure(const libcamera::CameraConfiguration &config)
> > >       rect_.h = cfg.size.height;
> > >
> > >       switch (cfg.pixelFormat) {
> > > -#ifdef HAVE_SDL_IMAGE
> > > +#ifdef HAVE_LIBJPEG
> > >       case libcamera::formats::MJPEG:
> > >               texture_ = std::make_unique<SDLTextureMJPG>(rect_);
> > >               break;
> > > diff --git a/src/cam/sdl_texture_mjpg.cpp b/src/cam/sdl_texture_mjpg.cpp
> > > index 69e99ad3..22f570c6 100644
> > > --- a/src/cam/sdl_texture_mjpg.cpp
> > > +++ b/src/cam/sdl_texture_mjpg.cpp
> > > @@ -7,19 +7,51 @@
> > >
> > >  #include "sdl_texture_mjpg.h"
> > >
> > > -#include <SDL2/SDL_image.h>
> > > +#include "jpeg_error_manager.h"
> > > +
> > > +#include <iostream>
> > >
> > >  using namespace libcamera;
> > >
> > >  SDLTextureMJPG::SDLTextureMJPG(const SDL_Rect &rect)
> > > -     : SDLTexture(rect, SDL_PIXELFORMAT_RGB24, 0)
> > > +     : SDLTexture(rect, SDL_PIXELFORMAT_RGB24, rect.w * 3),
> > > +       rgb_(std::make_unique<unsigned char[]>(pitch_ * rect.h))
> > > +{
> > > +}
> > > +
> > > +int SDLTextureMJPG::decompress(const Span<uint8_t> &data)
> >
> > int SDLTextureMJPG::decompress(Span<const uint8_t> data)
> >
> > for two reasons:
> >
> > - a Span is a pointer + size, which will fit in registers when calling
> >   this function, so that should be more efficient than passing a pointer
> >   to the Span
> 
> I'm not sure about that:
> 
> #include <libcamera/libcamera/base/span.h>
> #include <iostream>
> 
> int main() {
>   std::cout << sizeof(const libcamera::Span<const uint8_t>) << std::endl;
>   std::cout << sizeof(const libcamera::Span<const uint8_t>*) << std::endl;
>   std::cout << sizeof(const libcamera::Span<const uint8_t>&) << std::endl;

This one is a bit misleading, it will indeed print 16, but passing by
reference will pass the address of the variable, so 8 bytes only.

--------
#include <libcamera/base/span.h>

namespace libcamera {

void by_pointer(Span<uint8_t> *data);
void by_reference(Span<uint8_t> &data);
void by_value(Span<uint8_t> data);

void test_function()
{
	Span data{ reinterpret_cast<uint8_t *>(0x01234567), 0x89abcdef };

	by_pointer(&data);
	by_reference(data);
	by_value(data);
}

}
--------

Compiled with -O0 and disassembled with

$ objdump -d -r --demangle src/libcamera/libcamera.so.0.0.0.p/reference.cpp.o 

produces

--------
0000000000000000 <libcamera::test_function()>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 20             sub    $0x20,%rsp
   8:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
   f:   00 00 
  11:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  15:   31 c0                   xor    %eax,%eax
  17:   48 8d 45 e0             lea    -0x20(%rbp),%rax
  1b:   ba ef cd ab 89          mov    $0x89abcdef,%edx
  20:   be 67 45 23 01          mov    $0x1234567,%esi
  25:   48 89 c7                mov    %rax,%rdi
  28:   e8 00 00 00 00          call   2d <libcamera::test_function()+0x2d>
                        29: R_X86_64_PLT32      libcamera::Span<unsigned char, 18446744073709551615ul>::Span(unsigned char*, unsigned long)-0x4
  2d:   48 8d 45 e0             lea    -0x20(%rbp),%rax
  31:   48 89 c7                mov    %rax,%rdi
  34:   e8 00 00 00 00          call   39 <libcamera::test_function()+0x39>
                        35: R_X86_64_PLT32      libcamera::by_pointer(libcamera::Span<unsigned char, 18446744073709551615ul>*)-0x4
  39:   48 8d 45 e0             lea    -0x20(%rbp),%rax
  3d:   48 89 c7                mov    %rax,%rdi
  40:   e8 00 00 00 00          call   45 <libcamera::test_function()+0x45>
                        41: R_X86_64_PLT32      libcamera::by_reference(libcamera::Span<unsigned char, 18446744073709551615ul>&)-0x4
  45:   48 8b 55 e0             mov    -0x20(%rbp),%rdx
  49:   48 8b 45 e8             mov    -0x18(%rbp),%rax
  4d:   48 89 d7                mov    %rdx,%rdi
  50:   48 89 c6                mov    %rax,%rsi
  53:   e8 00 00 00 00          call   58 <libcamera::test_function()+0x58>
                        54: R_X86_64_PLT32      libcamera::by_value(libcamera::Span<unsigned char, 18446744073709551615ul>)-0x4
  58:   90                      nop
  59:   48 8b 45 f8             mov    -0x8(%rbp),%rax
  5d:   64 48 2b 04 25 28 00    sub    %fs:0x28,%rax
  64:   00 00 
  66:   74 05                   je     6d <libcamera::test_function()+0x6d>
  68:   e8 00 00 00 00          call   6d <libcamera::test_function()+0x6d>
                        69: R_X86_64_PLT32      __stack_chk_fail-0x4
  6d:   c9                      leave  
  6e:   c3                      ret
--------

As you can see, both by_pointer() and by_reference() get passed the
address of the data variable in rdi, while by_value() get the contents
of the Span in rdi and rsi.

Obviously this only works if the number of parameters to the function is
low enough to fit everything in registers, and it only helps if the
caller has the Span data and size in registers already, as if it has to
load them from memory, the callee could do so as well.

I any case, I agree it's not a big optimization (if at all). The second
point I mentioned (Span<const uint8_t>) is more important.

> }
> 
> Output:
> 
> 16
> 8
> 16
> 
> Even though pass by value and pass by reference are the same amount of
> bytes (both 16 bytes and the pointer is 8 on x86), I would expect pass
> by reference in the worst case to be of equal performance because it
> ensures no constructor of any kind is called (where another copy or
> two could occur in pass by value, I don't even need to read the
> constructor's source code if it's a reference, that's why class/struct
> as reference parameter is a great rule of thumb). But I'm not sure
> something like this is worth a v6 either, so it's fine the way it is I
> guess.
> 
> >
> > - Span<const uint8_t> to indicate that the function doesn't modify the
> >   data. A const Span<uint8_t> prevents changing the Span itself, but
> >   allows modifying the data that the Span points to.
> >
> > This would however require changing the update() function accordingly,
> > so I'll submit a patch on top.
> >
> > I've made those modifications locally to test and make sure I wasn't
> > saying something stupid, so I'll send a v5 shortly as it's ready.
> 
> Gonna run a test.
> 
> > >  {
> > > +     struct jpeg_decompress_struct cinfo;
> > > +     JpegErrorManager jpegErrorManager(cinfo);
> > > +     if (setjmp(jpegErrorManager.escape_)) {
> > > +             /* libjpeg found an error */
> > > +             jpeg_destroy_decompress(&cinfo);
> > > +             std::cerr << "JPEG decompression error" << std::endl;
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     jpeg_create_decompress(&cinfo);
> > > +
> > > +     jpeg_mem_src(&cinfo, data.data(), data.size());
> > > +
> > > +     jpeg_read_header(&cinfo, TRUE);
> > > +
> > > +     jpeg_start_decompress(&cinfo);
> > > +
> > > +     for (int i = 0; cinfo.output_scanline < cinfo.output_height; ++i) {
> > > +             JSAMPROW rowptr = rgb_.get() + i * pitch_;
> > > +             jpeg_read_scanlines(&cinfo, &rowptr, 1);
> > > +     }
> > > +
> > > +     jpeg_finish_decompress(&cinfo);
> > > +
> > > +     jpeg_destroy_decompress(&cinfo);
> > > +
> > > +     return 0;
> > >  }
> > >
> > >  void SDLTextureMJPG::update(const Span<uint8_t> &data)
> > >  {
> > > -     SDL_RWops *bufferStream = SDL_RWFromMem(data.data(), data.size());
> > > -     SDL_Surface *frame = IMG_Load_RW(bufferStream, 0);
> > > -     SDL_UpdateTexture(ptr_, nullptr, frame->pixels, frame->pitch);
> > > -     SDL_FreeSurface(frame);
> > > +     decompress(data);
> > > +     SDL_UpdateTexture(ptr_, nullptr, rgb_.get(), pitch_);
> > >  }
> > > diff --git a/src/cam/sdl_texture_mjpg.h b/src/cam/sdl_texture_mjpg.h
> > > index b103f801..328c45a9 100644
> > > --- a/src/cam/sdl_texture_mjpg.h
> > > +++ b/src/cam/sdl_texture_mjpg.h
> > > @@ -13,5 +13,11 @@ class SDLTextureMJPG : public SDLTexture
> > >  {
> > >  public:
> > >       SDLTextureMJPG(const SDL_Rect &rect);
> > > +
> > >       void update(const libcamera::Span<uint8_t> &data) override;
> > > +
> > > +private:
> > > +     int decompress(const libcamera::Span<uint8_t> &data);
> > > +
> > > +     std::unique_ptr<unsigned char[]> rgb_;
> > >  };
Eric Curtin July 22, 2022, 9:32 a.m. UTC | #4
On Thu, 21 Jul 2022 at 21:49, Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
>
> Hi Eric,
>
> On Wed, Jul 20, 2022 at 02:54:29PM +0100, Eric Curtin wrote:
> > On Wed, 20 Jul 2022 at 12:56, Laurent Pinchart wrote:
> > > On Tue, Jul 19, 2022 at 10:17:46AM +0100, Eric Curtin wrote:
> > > > We were using the libjpeg functionality of SDL2_image only, instead just
> > > > use libjpeg directly to reduce our dependancy count, it is a more
> > > > commonly available library.
> > > >
> > > > Signed-off-by: Eric Curtin <ecurtin@redhat.com>
> > > > ---
> > > > Changes in v4:
> > > > - decompress function takes a Span<const uint8_t>&
> > > >
> > > > Changes in v3:
> > > > - create JpegErrorManager struct
> > > > - change c cast to reinterpret_cast
> > > >
> > > > Changes in v2:
> > > > - alphabetically sorted various orderings
> > > > - pitch_ is const again
> > > > - added setjmp logic for error handling in libjpeg
> > > > - rgbdata_ to unique_ptr and renamed to rgb_
> > > > - removed a copy from buffer to rgb_
> > > > - removed a destructor
> > > > ---
> > > >  README.rst                     |  2 +-
> > > >  src/cam/jpeg_error_manager.cpp | 26 ++++++++++++++++++++
> > > >  src/cam/jpeg_error_manager.h   | 21 ++++++++++++++++
> > > >  src/cam/meson.build            |  9 +++----
> > > >  src/cam/sdl_sink.cpp           |  4 ++--
> > > >  src/cam/sdl_texture_mjpg.cpp   | 44 +++++++++++++++++++++++++++++-----
> > > >  src/cam/sdl_texture_mjpg.h     |  6 +++++
> > > >  7 files changed, 99 insertions(+), 13 deletions(-)
> > > >  create mode 100644 src/cam/jpeg_error_manager.cpp
> > > >  create mode 100644 src/cam/jpeg_error_manager.h
> > > >
> > > > diff --git a/README.rst b/README.rst
> > > > index b9e72d81..47b914f0 100644
> > > > --- a/README.rst
> > > > +++ b/README.rst
> > > > @@ -92,8 +92,8 @@ for cam: [optional]
> > > >          tool:
> > > >
> > > >          - libdrm-dev: Enables the KMS sink
> > > > +        - libjpeg-dev: Enables MJPEG on the SDL sink
> > > >          - libsdl2-dev: Enables the SDL sink
> > > > -        - libsdl2-image-dev: Supports MJPEG on the SDL sink
> > > >
> > > >  for qcam: [optional]
> > > >          qtbase5-dev libqt5core5a libqt5gui5 libqt5widgets5 qttools5-dev-tools libtiff-dev
> > > > diff --git a/src/cam/jpeg_error_manager.cpp b/src/cam/jpeg_error_manager.cpp
> > > > new file mode 100644
> > > > index 00000000..7e45e577
> > > > --- /dev/null
> > > > +++ b/src/cam/jpeg_error_manager.cpp
> > > > @@ -0,0 +1,26 @@
> > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > > +/*
> > > > + * Copyright (C) 2022, Ideas on Board Oy
> > > > + *
> > > > + * jpeg_error_manager.cpp - JPEG Error Manager
> > > > + */
> > > > +
> > > > +#include "jpeg_error_manager.h"
> > > > +
> > > > +static void errorExit(j_common_ptr cinfo)
> > > > +{
> > > > +     JpegErrorManager *self =
> > > > +             reinterpret_cast<JpegErrorManager *>(cinfo->err);
> > > > +     longjmp(self->escape_, 1);
> > > > +}
> > > > +
> > > > +static void outputMessage([[maybe_unused]] j_common_ptr cinfo)
> > > > +{
> > > > +}
> > > > +
> > > > +JpegErrorManager::JpegErrorManager(struct jpeg_decompress_struct &cinfo)
> > >
> > > I wouldn't mix the error manager and decompressor. You can keep the
> > > jpeg_std_error() call below, and move the assignment of cinfo.err to the
> > > code that creates the decompressor.
> > >
> > > > +{
> > > > +     cinfo.err = jpeg_std_error(&errmgr_);
> > > > +     errmgr_.error_exit = errorExit;
> > > > +     errmgr_.output_message = outputMessage;
> > > > +}
> > >
> > > This can live in sdl_texture_mjpg.cpp as it's internal to the
> > > implementation of the SDL texture for MJPEG, there's no need to have one
> > > file per class.
> > >
> > > > diff --git a/src/cam/jpeg_error_manager.h b/src/cam/jpeg_error_manager.h
> > > > new file mode 100644
> > > > index 00000000..0af28da1
> > > > --- /dev/null
> > > > +++ b/src/cam/jpeg_error_manager.h
> > > > @@ -0,0 +1,21 @@
> > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > > +/*
> > > > + * Copyright (C) 2022, Ideas on Board Oy
> > > > + *
> > > > + * jpeg_error_manager.h - JPEG Error Manager
> > > > + */
> > > > +
> > > > +#pragma once
> > > > +
> > > > +#include <setjmp.h>
> > > > +#include <stdio.h>
> > > > +
> > > > +#include <jpeglib.h>
> > > > +
> > > > +struct JpegErrorManager {
> > > > +     JpegErrorManager(struct jpeg_decompress_struct &cinfo);
> > > > +
> > > > +     /* Order very important for reinterpret_cast */
> > > > +     struct jpeg_error_mgr errmgr_;
> > > > +     jmp_buf escape_;
> > > > +};
> > > > diff --git a/src/cam/meson.build b/src/cam/meson.build
> > > > index 5957ce14..86cef683 100644
> > > > --- a/src/cam/meson.build
> > > > +++ b/src/cam/meson.build
> > > > @@ -24,8 +24,8 @@ cam_sources = files([
> > > >  cam_cpp_args = []
> > > >
> > > >  libdrm = dependency('libdrm', required : false)
> > > > +libjpeg = dependency('libjpeg', required : false)
> > > >  libsdl2 = dependency('SDL2', required : false)
> > > > -libsdl2_image = dependency('SDL2_image', required : false)
> > > >
> > > >  if libdrm.found()
> > > >      cam_cpp_args += [ '-DHAVE_KMS' ]
> > > > @@ -43,9 +43,10 @@ if libsdl2.found()
> > > >          'sdl_texture_yuyv.cpp'
> > > >      ])
> > > >
> > > > -    if libsdl2_image.found()
> > > > -        cam_cpp_args += ['-DHAVE_SDL_IMAGE']
> > > > +    if libjpeg.found()
> > > > +        cam_cpp_args += ['-DHAVE_LIBJPEG']
> > > >          cam_sources += files([
> > > > +            'jpeg_error_manager.cpp',
> > > >              'sdl_texture_mjpg.cpp'
> > > >          ])
> > > >      endif
> > > > @@ -57,8 +58,8 @@ cam  = executable('cam', cam_sources,
> > > >                        libcamera_public,
> > > >                        libdrm,
> > > >                        libevent,
> > > > +                      libjpeg,
> > > >                        libsdl2,
> > > > -                      libsdl2_image,
> > > >                        libyaml,
> > > >                    ],
> > > >                    cpp_args : cam_cpp_args,
> > > > diff --git a/src/cam/sdl_sink.cpp b/src/cam/sdl_sink.cpp
> > > > index f8e3e95d..19fdfd6d 100644
> > > > --- a/src/cam/sdl_sink.cpp
> > > > +++ b/src/cam/sdl_sink.cpp
> > > > @@ -21,7 +21,7 @@
> > > >
> > > >  #include "event_loop.h"
> > > >  #include "image.h"
> > > > -#ifdef HAVE_SDL_IMAGE
> > > > +#ifdef HAVE_LIBJPEG
> > > >  #include "sdl_texture_mjpg.h"
> > > >  #endif
> > > >  #include "sdl_texture_yuyv.h"
> > > > @@ -62,7 +62,7 @@ int SDLSink::configure(const libcamera::CameraConfiguration &config)
> > > >       rect_.h = cfg.size.height;
> > > >
> > > >       switch (cfg.pixelFormat) {
> > > > -#ifdef HAVE_SDL_IMAGE
> > > > +#ifdef HAVE_LIBJPEG
> > > >       case libcamera::formats::MJPEG:
> > > >               texture_ = std::make_unique<SDLTextureMJPG>(rect_);
> > > >               break;
> > > > diff --git a/src/cam/sdl_texture_mjpg.cpp b/src/cam/sdl_texture_mjpg.cpp
> > > > index 69e99ad3..22f570c6 100644
> > > > --- a/src/cam/sdl_texture_mjpg.cpp
> > > > +++ b/src/cam/sdl_texture_mjpg.cpp
> > > > @@ -7,19 +7,51 @@
> > > >
> > > >  #include "sdl_texture_mjpg.h"
> > > >
> > > > -#include <SDL2/SDL_image.h>
> > > > +#include "jpeg_error_manager.h"
> > > > +
> > > > +#include <iostream>
> > > >
> > > >  using namespace libcamera;
> > > >
> > > >  SDLTextureMJPG::SDLTextureMJPG(const SDL_Rect &rect)
> > > > -     : SDLTexture(rect, SDL_PIXELFORMAT_RGB24, 0)
> > > > +     : SDLTexture(rect, SDL_PIXELFORMAT_RGB24, rect.w * 3),
> > > > +       rgb_(std::make_unique<unsigned char[]>(pitch_ * rect.h))
> > > > +{
> > > > +}
> > > > +
> > > > +int SDLTextureMJPG::decompress(const Span<uint8_t> &data)
> > >
> > > int SDLTextureMJPG::decompress(Span<const uint8_t> data)
> > >
> > > for two reasons:
> > >
> > > - a Span is a pointer + size, which will fit in registers when calling
> > >   this function, so that should be more efficient than passing a pointer
> > >   to the Span
> >
> > I'm not sure about that:
> >
> > #include <libcamera/libcamera/base/span.h>
> > #include <iostream>
> >
> > int main() {
> >   std::cout << sizeof(const libcamera::Span<const uint8_t>) << std::endl;
> >   std::cout << sizeof(const libcamera::Span<const uint8_t>*) << std::endl;
> >   std::cout << sizeof(const libcamera::Span<const uint8_t>&) << std::endl;
>
> This one is a bit misleading, it will indeed print 16, but passing by
> reference will pass the address of the variable, so 8 bytes only.

I was confused by that I'll admit, I realized afterwards by
investigating more. I'm good with v5.

>
> --------
> #include <libcamera/base/span.h>
>
> namespace libcamera {
>
> void by_pointer(Span<uint8_t> *data);
> void by_reference(Span<uint8_t> &data);
> void by_value(Span<uint8_t> data);
>
> void test_function()
> {
>         Span data{ reinterpret_cast<uint8_t *>(0x01234567), 0x89abcdef };
>
>         by_pointer(&data);
>         by_reference(data);
>         by_value(data);
> }
>
> }
> --------
>
> Compiled with -O0 and disassembled with
>
> $ objdump -d -r --demangle src/libcamera/libcamera.so.0.0.0.p/reference.cpp.o
>
> produces
>
> --------
> 0000000000000000 <libcamera::test_function()>:
>    0:   55                      push   %rbp
>    1:   48 89 e5                mov    %rsp,%rbp
>    4:   48 83 ec 20             sub    $0x20,%rsp
>    8:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
>    f:   00 00
>   11:   48 89 45 f8             mov    %rax,-0x8(%rbp)
>   15:   31 c0                   xor    %eax,%eax
>   17:   48 8d 45 e0             lea    -0x20(%rbp),%rax
>   1b:   ba ef cd ab 89          mov    $0x89abcdef,%edx
>   20:   be 67 45 23 01          mov    $0x1234567,%esi
>   25:   48 89 c7                mov    %rax,%rdi
>   28:   e8 00 00 00 00          call   2d <libcamera::test_function()+0x2d>
>                         29: R_X86_64_PLT32      libcamera::Span<unsigned char, 18446744073709551615ul>::Span(unsigned char*, unsigned long)-0x4
>   2d:   48 8d 45 e0             lea    -0x20(%rbp),%rax
>   31:   48 89 c7                mov    %rax,%rdi
>   34:   e8 00 00 00 00          call   39 <libcamera::test_function()+0x39>
>                         35: R_X86_64_PLT32      libcamera::by_pointer(libcamera::Span<unsigned char, 18446744073709551615ul>*)-0x4
>   39:   48 8d 45 e0             lea    -0x20(%rbp),%rax
>   3d:   48 89 c7                mov    %rax,%rdi
>   40:   e8 00 00 00 00          call   45 <libcamera::test_function()+0x45>
>                         41: R_X86_64_PLT32      libcamera::by_reference(libcamera::Span<unsigned char, 18446744073709551615ul>&)-0x4
>   45:   48 8b 55 e0             mov    -0x20(%rbp),%rdx
>   49:   48 8b 45 e8             mov    -0x18(%rbp),%rax
>   4d:   48 89 d7                mov    %rdx,%rdi
>   50:   48 89 c6                mov    %rax,%rsi
>   53:   e8 00 00 00 00          call   58 <libcamera::test_function()+0x58>
>                         54: R_X86_64_PLT32      libcamera::by_value(libcamera::Span<unsigned char, 18446744073709551615ul>)-0x4
>   58:   90                      nop
>   59:   48 8b 45 f8             mov    -0x8(%rbp),%rax
>   5d:   64 48 2b 04 25 28 00    sub    %fs:0x28,%rax
>   64:   00 00
>   66:   74 05                   je     6d <libcamera::test_function()+0x6d>
>   68:   e8 00 00 00 00          call   6d <libcamera::test_function()+0x6d>
>                         69: R_X86_64_PLT32      __stack_chk_fail-0x4
>   6d:   c9                      leave
>   6e:   c3                      ret
> --------
>
> As you can see, both by_pointer() and by_reference() get passed the
> address of the data variable in rdi, while by_value() get the contents
> of the Span in rdi and rsi.
>
> Obviously this only works if the number of parameters to the function is
> low enough to fit everything in registers, and it only helps if the
> caller has the Span data and size in registers already, as if it has to
> load them from memory, the callee could do so as well.
>
> I any case, I agree it's not a big optimization (if at all). The second
> point I mentioned (Span<const uint8_t>) is more important.
>
> > }
> >
> > Output:
> >
> > 16
> > 8
> > 16
> >
> > Even though pass by value and pass by reference are the same amount of
> > bytes (both 16 bytes and the pointer is 8 on x86), I would expect pass
> > by reference in the worst case to be of equal performance because it
> > ensures no constructor of any kind is called (where another copy or
> > two could occur in pass by value, I don't even need to read the
> > constructor's source code if it's a reference, that's why class/struct
> > as reference parameter is a great rule of thumb). But I'm not sure
> > something like this is worth a v6 either, so it's fine the way it is I
> > guess.
> >
> > >
> > > - Span<const uint8_t> to indicate that the function doesn't modify the
> > >   data. A const Span<uint8_t> prevents changing the Span itself, but
> > >   allows modifying the data that the Span points to.
> > >
> > > This would however require changing the update() function accordingly,
> > > so I'll submit a patch on top.
> > >
> > > I've made those modifications locally to test and make sure I wasn't
> > > saying something stupid, so I'll send a v5 shortly as it's ready.
> >
> > Gonna run a test.
> >
> > > >  {
> > > > +     struct jpeg_decompress_struct cinfo;
> > > > +     JpegErrorManager jpegErrorManager(cinfo);
> > > > +     if (setjmp(jpegErrorManager.escape_)) {
> > > > +             /* libjpeg found an error */
> > > > +             jpeg_destroy_decompress(&cinfo);
> > > > +             std::cerr << "JPEG decompression error" << std::endl;
> > > > +             return -EINVAL;
> > > > +     }
> > > > +
> > > > +     jpeg_create_decompress(&cinfo);
> > > > +
> > > > +     jpeg_mem_src(&cinfo, data.data(), data.size());
> > > > +
> > > > +     jpeg_read_header(&cinfo, TRUE);
> > > > +
> > > > +     jpeg_start_decompress(&cinfo);
> > > > +
> > > > +     for (int i = 0; cinfo.output_scanline < cinfo.output_height; ++i) {
> > > > +             JSAMPROW rowptr = rgb_.get() + i * pitch_;
> > > > +             jpeg_read_scanlines(&cinfo, &rowptr, 1);
> > > > +     }
> > > > +
> > > > +     jpeg_finish_decompress(&cinfo);
> > > > +
> > > > +     jpeg_destroy_decompress(&cinfo);
> > > > +
> > > > +     return 0;
> > > >  }
> > > >
> > > >  void SDLTextureMJPG::update(const Span<uint8_t> &data)
> > > >  {
> > > > -     SDL_RWops *bufferStream = SDL_RWFromMem(data.data(), data.size());
> > > > -     SDL_Surface *frame = IMG_Load_RW(bufferStream, 0);
> > > > -     SDL_UpdateTexture(ptr_, nullptr, frame->pixels, frame->pitch);
> > > > -     SDL_FreeSurface(frame);
> > > > +     decompress(data);
> > > > +     SDL_UpdateTexture(ptr_, nullptr, rgb_.get(), pitch_);
> > > >  }
> > > > diff --git a/src/cam/sdl_texture_mjpg.h b/src/cam/sdl_texture_mjpg.h
> > > > index b103f801..328c45a9 100644
> > > > --- a/src/cam/sdl_texture_mjpg.h
> > > > +++ b/src/cam/sdl_texture_mjpg.h
> > > > @@ -13,5 +13,11 @@ class SDLTextureMJPG : public SDLTexture
> > > >  {
> > > >  public:
> > > >       SDLTextureMJPG(const SDL_Rect &rect);
> > > > +
> > > >       void update(const libcamera::Span<uint8_t> &data) override;
> > > > +
> > > > +private:
> > > > +     int decompress(const libcamera::Span<uint8_t> &data);
> > > > +
> > > > +     std::unique_ptr<unsigned char[]> rgb_;
> > > >  };
>
> --
> Regards,
>
> Laurent Pinchart
>

Patch
diff mbox series

diff --git a/README.rst b/README.rst
index b9e72d81..47b914f0 100644
--- a/README.rst
+++ b/README.rst
@@ -92,8 +92,8 @@  for cam: [optional]
         tool:
 
         - libdrm-dev: Enables the KMS sink
+        - libjpeg-dev: Enables MJPEG on the SDL sink
         - libsdl2-dev: Enables the SDL sink
-        - libsdl2-image-dev: Supports MJPEG on the SDL sink
 
 for qcam: [optional]
         qtbase5-dev libqt5core5a libqt5gui5 libqt5widgets5 qttools5-dev-tools libtiff-dev
diff --git a/src/cam/jpeg_error_manager.cpp b/src/cam/jpeg_error_manager.cpp
new file mode 100644
index 00000000..7e45e577
--- /dev/null
+++ b/src/cam/jpeg_error_manager.cpp
@@ -0,0 +1,26 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * jpeg_error_manager.cpp - JPEG Error Manager
+ */
+
+#include "jpeg_error_manager.h"
+
+static void errorExit(j_common_ptr cinfo)
+{
+	JpegErrorManager *self =
+		reinterpret_cast<JpegErrorManager *>(cinfo->err);
+	longjmp(self->escape_, 1);
+}
+
+static void outputMessage([[maybe_unused]] j_common_ptr cinfo)
+{
+}
+
+JpegErrorManager::JpegErrorManager(struct jpeg_decompress_struct &cinfo)
+{
+	cinfo.err = jpeg_std_error(&errmgr_);
+	errmgr_.error_exit = errorExit;
+	errmgr_.output_message = outputMessage;
+}
diff --git a/src/cam/jpeg_error_manager.h b/src/cam/jpeg_error_manager.h
new file mode 100644
index 00000000..0af28da1
--- /dev/null
+++ b/src/cam/jpeg_error_manager.h
@@ -0,0 +1,21 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * jpeg_error_manager.h - JPEG Error Manager
+ */
+
+#pragma once
+
+#include <setjmp.h>
+#include <stdio.h>
+
+#include <jpeglib.h>
+
+struct JpegErrorManager {
+	JpegErrorManager(struct jpeg_decompress_struct &cinfo);
+
+	/* Order very important for reinterpret_cast */
+	struct jpeg_error_mgr errmgr_;
+	jmp_buf escape_;
+};
diff --git a/src/cam/meson.build b/src/cam/meson.build
index 5957ce14..86cef683 100644
--- a/src/cam/meson.build
+++ b/src/cam/meson.build
@@ -24,8 +24,8 @@  cam_sources = files([
 cam_cpp_args = []
 
 libdrm = dependency('libdrm', required : false)
+libjpeg = dependency('libjpeg', required : false)
 libsdl2 = dependency('SDL2', required : false)
-libsdl2_image = dependency('SDL2_image', required : false)
 
 if libdrm.found()
     cam_cpp_args += [ '-DHAVE_KMS' ]
@@ -43,9 +43,10 @@  if libsdl2.found()
         'sdl_texture_yuyv.cpp'
     ])
 
-    if libsdl2_image.found()
-        cam_cpp_args += ['-DHAVE_SDL_IMAGE']
+    if libjpeg.found()
+        cam_cpp_args += ['-DHAVE_LIBJPEG']
         cam_sources += files([
+            'jpeg_error_manager.cpp',
             'sdl_texture_mjpg.cpp'
         ])
     endif
@@ -57,8 +58,8 @@  cam  = executable('cam', cam_sources,
                       libcamera_public,
                       libdrm,
                       libevent,
+                      libjpeg,
                       libsdl2,
-                      libsdl2_image,
                       libyaml,
                   ],
                   cpp_args : cam_cpp_args,
diff --git a/src/cam/sdl_sink.cpp b/src/cam/sdl_sink.cpp
index f8e3e95d..19fdfd6d 100644
--- a/src/cam/sdl_sink.cpp
+++ b/src/cam/sdl_sink.cpp
@@ -21,7 +21,7 @@ 
 
 #include "event_loop.h"
 #include "image.h"
-#ifdef HAVE_SDL_IMAGE
+#ifdef HAVE_LIBJPEG
 #include "sdl_texture_mjpg.h"
 #endif
 #include "sdl_texture_yuyv.h"
@@ -62,7 +62,7 @@  int SDLSink::configure(const libcamera::CameraConfiguration &config)
 	rect_.h = cfg.size.height;
 
 	switch (cfg.pixelFormat) {
-#ifdef HAVE_SDL_IMAGE
+#ifdef HAVE_LIBJPEG
 	case libcamera::formats::MJPEG:
 		texture_ = std::make_unique<SDLTextureMJPG>(rect_);
 		break;
diff --git a/src/cam/sdl_texture_mjpg.cpp b/src/cam/sdl_texture_mjpg.cpp
index 69e99ad3..22f570c6 100644
--- a/src/cam/sdl_texture_mjpg.cpp
+++ b/src/cam/sdl_texture_mjpg.cpp
@@ -7,19 +7,51 @@ 
 
 #include "sdl_texture_mjpg.h"
 
-#include <SDL2/SDL_image.h>
+#include "jpeg_error_manager.h"
+
+#include <iostream>
 
 using namespace libcamera;
 
 SDLTextureMJPG::SDLTextureMJPG(const SDL_Rect &rect)
-	: SDLTexture(rect, SDL_PIXELFORMAT_RGB24, 0)
+	: SDLTexture(rect, SDL_PIXELFORMAT_RGB24, rect.w * 3),
+	  rgb_(std::make_unique<unsigned char[]>(pitch_ * rect.h))
+{
+}
+
+int SDLTextureMJPG::decompress(const Span<uint8_t> &data)
 {
+	struct jpeg_decompress_struct cinfo;
+	JpegErrorManager jpegErrorManager(cinfo);
+	if (setjmp(jpegErrorManager.escape_)) {
+		/* libjpeg found an error */
+		jpeg_destroy_decompress(&cinfo);
+		std::cerr << "JPEG decompression error" << std::endl;
+		return -EINVAL;
+	}
+
+	jpeg_create_decompress(&cinfo);
+
+	jpeg_mem_src(&cinfo, data.data(), data.size());
+
+	jpeg_read_header(&cinfo, TRUE);
+
+	jpeg_start_decompress(&cinfo);
+
+	for (int i = 0; cinfo.output_scanline < cinfo.output_height; ++i) {
+		JSAMPROW rowptr = rgb_.get() + i * pitch_;
+		jpeg_read_scanlines(&cinfo, &rowptr, 1);
+	}
+
+	jpeg_finish_decompress(&cinfo);
+
+	jpeg_destroy_decompress(&cinfo);
+
+	return 0;
 }
 
 void SDLTextureMJPG::update(const Span<uint8_t> &data)
 {
-	SDL_RWops *bufferStream = SDL_RWFromMem(data.data(), data.size());
-	SDL_Surface *frame = IMG_Load_RW(bufferStream, 0);
-	SDL_UpdateTexture(ptr_, nullptr, frame->pixels, frame->pitch);
-	SDL_FreeSurface(frame);
+	decompress(data);
+	SDL_UpdateTexture(ptr_, nullptr, rgb_.get(), pitch_);
 }
diff --git a/src/cam/sdl_texture_mjpg.h b/src/cam/sdl_texture_mjpg.h
index b103f801..328c45a9 100644
--- a/src/cam/sdl_texture_mjpg.h
+++ b/src/cam/sdl_texture_mjpg.h
@@ -13,5 +13,11 @@  class SDLTextureMJPG : public SDLTexture
 {
 public:
 	SDLTextureMJPG(const SDL_Rect &rect);
+
 	void update(const libcamera::Span<uint8_t> &data) override;
+
+private:
+	int decompress(const libcamera::Span<uint8_t> &data);
+
+	std::unique_ptr<unsigned char[]> rgb_;
 };