mirror of
https://git.libcamera.org/libcamera/libcamera.git
synced 2025-07-17 17:35:06 +03:00
android: Introduce JPEG encoding
Provide an encoder interface and implement a JPEG encoder using libjpeg. Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
parent
c09aee4ccb
commit
998f4de65e
5 changed files with 291 additions and 0 deletions
24
src/android/jpeg/encoder.h
Normal file
24
src/android/jpeg/encoder.h
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020, Google Inc.
|
||||||
|
*
|
||||||
|
* encoder.h - Image encoding interface
|
||||||
|
*/
|
||||||
|
#ifndef __ANDROID_JPEG_ENCODER_H__
|
||||||
|
#define __ANDROID_JPEG_ENCODER_H__
|
||||||
|
|
||||||
|
#include <libcamera/buffer.h>
|
||||||
|
#include <libcamera/span.h>
|
||||||
|
#include <libcamera/stream.h>
|
||||||
|
|
||||||
|
class Encoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~Encoder() {};
|
||||||
|
|
||||||
|
virtual int configure(const libcamera::StreamConfiguration &cfg) = 0;
|
||||||
|
virtual int encode(const libcamera::FrameBuffer *source,
|
||||||
|
const libcamera::Span<uint8_t> &destination) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __ANDROID_JPEG_ENCODER_H__ */
|
218
src/android/jpeg/encoder_libjpeg.cpp
Normal file
218
src/android/jpeg/encoder_libjpeg.cpp
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020, Google Inc.
|
||||||
|
*
|
||||||
|
* encoder_libjpeg.cpp - JPEG encoding using libjpeg native API
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "encoder_libjpeg.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <libcamera/camera.h>
|
||||||
|
#include <libcamera/formats.h>
|
||||||
|
#include <libcamera/pixel_format.h>
|
||||||
|
|
||||||
|
#include "libcamera/internal/formats.h"
|
||||||
|
#include "libcamera/internal/log.h"
|
||||||
|
|
||||||
|
using namespace libcamera;
|
||||||
|
|
||||||
|
LOG_DEFINE_CATEGORY(JPEG)
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct JPEGPixelFormatInfo {
|
||||||
|
J_COLOR_SPACE colorSpace;
|
||||||
|
const PixelFormatInfo &pixelFormatInfo;
|
||||||
|
bool nvSwap;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::map<PixelFormat, JPEGPixelFormatInfo> pixelInfo{
|
||||||
|
{ formats::R8, { JCS_GRAYSCALE, PixelFormatInfo::info(formats::R8), false } },
|
||||||
|
|
||||||
|
{ formats::RGB888, { JCS_EXT_BGR, PixelFormatInfo::info(formats::RGB888), false } },
|
||||||
|
{ formats::BGR888, { JCS_EXT_RGB, PixelFormatInfo::info(formats::BGR888), false } },
|
||||||
|
|
||||||
|
{ formats::NV12, { JCS_YCbCr, PixelFormatInfo::info(formats::NV12), false } },
|
||||||
|
{ formats::NV21, { JCS_YCbCr, PixelFormatInfo::info(formats::NV21), true } },
|
||||||
|
{ formats::NV16, { JCS_YCbCr, PixelFormatInfo::info(formats::NV16), false } },
|
||||||
|
{ formats::NV61, { JCS_YCbCr, PixelFormatInfo::info(formats::NV61), true } },
|
||||||
|
{ formats::NV24, { JCS_YCbCr, PixelFormatInfo::info(formats::NV24), false } },
|
||||||
|
{ formats::NV42, { JCS_YCbCr, PixelFormatInfo::info(formats::NV42), true } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct JPEGPixelFormatInfo &findPixelInfo(const PixelFormat &format)
|
||||||
|
{
|
||||||
|
static const struct JPEGPixelFormatInfo invalidPixelFormat {
|
||||||
|
JCS_UNKNOWN, PixelFormatInfo(), false
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto iter = pixelInfo.find(format);
|
||||||
|
if (iter == pixelInfo.end()) {
|
||||||
|
LOG(JPEG, Error) << "Unsupported pixel format for JPEG encoder: "
|
||||||
|
<< format.toString();
|
||||||
|
return invalidPixelFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace */
|
||||||
|
|
||||||
|
EncoderLibJpeg::EncoderLibJpeg()
|
||||||
|
: quality_(95)
|
||||||
|
{
|
||||||
|
/* \todo Expand error handling coverage with a custom handler. */
|
||||||
|
compress_.err = jpeg_std_error(&jerr_);
|
||||||
|
|
||||||
|
jpeg_create_compress(&compress_);
|
||||||
|
}
|
||||||
|
|
||||||
|
EncoderLibJpeg::~EncoderLibJpeg()
|
||||||
|
{
|
||||||
|
jpeg_destroy_compress(&compress_);
|
||||||
|
}
|
||||||
|
|
||||||
|
int EncoderLibJpeg::configure(const StreamConfiguration &cfg)
|
||||||
|
{
|
||||||
|
const struct JPEGPixelFormatInfo info = findPixelInfo(cfg.pixelFormat);
|
||||||
|
if (info.colorSpace == JCS_UNKNOWN)
|
||||||
|
return -ENOTSUP;
|
||||||
|
|
||||||
|
compress_.image_width = cfg.size.width;
|
||||||
|
compress_.image_height = cfg.size.height;
|
||||||
|
compress_.in_color_space = info.colorSpace;
|
||||||
|
|
||||||
|
compress_.input_components = info.colorSpace == JCS_GRAYSCALE ? 1 : 3;
|
||||||
|
|
||||||
|
jpeg_set_defaults(&compress_);
|
||||||
|
jpeg_set_quality(&compress_, quality_, TRUE);
|
||||||
|
|
||||||
|
pixelFormatInfo_ = &info.pixelFormatInfo;
|
||||||
|
|
||||||
|
nv_ = pixelFormatInfo_->numPlanes() == 2;
|
||||||
|
nvSwap_ = info.nvSwap;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EncoderLibJpeg::compressRGB(const libcamera::MappedBuffer *frame)
|
||||||
|
{
|
||||||
|
unsigned char *src = static_cast<unsigned char *>(frame->maps()[0].data());
|
||||||
|
/* \todo Stride information should come from buffer configuration. */
|
||||||
|
unsigned int stride = pixelFormatInfo_->stride(compress_.image_width, 0);
|
||||||
|
|
||||||
|
JSAMPROW row_pointer[1];
|
||||||
|
|
||||||
|
while (compress_.next_scanline < compress_.image_height) {
|
||||||
|
row_pointer[0] = &src[compress_.next_scanline * stride];
|
||||||
|
jpeg_write_scanlines(&compress_, row_pointer, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compress the incoming buffer from a supported NV format.
|
||||||
|
* This naively unpacks the semi-planar NV12 to a YUV888 format for libjpeg.
|
||||||
|
*/
|
||||||
|
void EncoderLibJpeg::compressNV(const libcamera::MappedBuffer *frame)
|
||||||
|
{
|
||||||
|
uint8_t tmprowbuf[compress_.image_width * 3];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* \todo Use the raw api, and only unpack the cb/cr samples to new line
|
||||||
|
* buffers. If possible, see if we can set appropriate pixel strides
|
||||||
|
* too to save even that copy.
|
||||||
|
*
|
||||||
|
* Possible hints at:
|
||||||
|
* https://sourceforge.net/p/libjpeg/mailman/message/30815123/
|
||||||
|
*/
|
||||||
|
unsigned int y_stride = pixelFormatInfo_->stride(compress_.image_width, 0);
|
||||||
|
unsigned int c_stride = pixelFormatInfo_->stride(compress_.image_width, 1);
|
||||||
|
|
||||||
|
unsigned int horzSubSample = 2 * compress_.image_width / c_stride;
|
||||||
|
unsigned int vertSubSample = pixelFormatInfo_->planes[1].verticalSubSampling;
|
||||||
|
|
||||||
|
unsigned int c_inc = horzSubSample == 1 ? 2 : 0;
|
||||||
|
unsigned int cb_pos = nvSwap_ ? 1 : 0;
|
||||||
|
unsigned int cr_pos = nvSwap_ ? 0 : 1;
|
||||||
|
|
||||||
|
const unsigned char *src = static_cast<unsigned char *>(frame->maps()[0].data());
|
||||||
|
const unsigned char *src_c = src + y_stride * compress_.image_height;
|
||||||
|
|
||||||
|
JSAMPROW row_pointer[1];
|
||||||
|
row_pointer[0] = &tmprowbuf[0];
|
||||||
|
|
||||||
|
for (unsigned int y = 0; y < compress_.image_height; y++) {
|
||||||
|
unsigned char *dst = &tmprowbuf[0];
|
||||||
|
|
||||||
|
const unsigned char *src_y = src + y * compress_.image_width;
|
||||||
|
const unsigned char *src_cb = src_c + (y / vertSubSample) * c_stride + cb_pos;
|
||||||
|
const unsigned char *src_cr = src_c + (y / vertSubSample) * c_stride + cr_pos;
|
||||||
|
|
||||||
|
for (unsigned int x = 0; x < compress_.image_width; x += 2) {
|
||||||
|
dst[0] = *src_y;
|
||||||
|
dst[1] = *src_cb;
|
||||||
|
dst[2] = *src_cr;
|
||||||
|
src_y++;
|
||||||
|
src_cb += c_inc;
|
||||||
|
src_cr += c_inc;
|
||||||
|
dst += 3;
|
||||||
|
|
||||||
|
dst[0] = *src_y;
|
||||||
|
dst[1] = *src_cb;
|
||||||
|
dst[2] = *src_cr;
|
||||||
|
src_y++;
|
||||||
|
src_cb += 2;
|
||||||
|
src_cr += 2;
|
||||||
|
dst += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
jpeg_write_scanlines(&compress_, row_pointer, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int EncoderLibJpeg::encode(const FrameBuffer *source,
|
||||||
|
const libcamera::Span<uint8_t> &dest)
|
||||||
|
{
|
||||||
|
MappedFrameBuffer frame(source, PROT_READ);
|
||||||
|
if (!frame.isValid()) {
|
||||||
|
LOG(JPEG, Error) << "Failed to map FrameBuffer : "
|
||||||
|
<< strerror(frame.error());
|
||||||
|
return frame.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char *destination = dest.data();
|
||||||
|
unsigned long size = dest.size();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The jpeg_mem_dest will reallocate if the required size is not
|
||||||
|
* sufficient. That means the output won't be written to the correct
|
||||||
|
* buffers.
|
||||||
|
*
|
||||||
|
* \todo Implement our own custom memory destination to prevent
|
||||||
|
* reallocation and prefer failure with correct reporting.
|
||||||
|
*/
|
||||||
|
jpeg_mem_dest(&compress_, &destination, &size);
|
||||||
|
|
||||||
|
jpeg_start_compress(&compress_, TRUE);
|
||||||
|
|
||||||
|
LOG(JPEG, Debug) << "JPEG Encode Starting:" << compress_.image_width
|
||||||
|
<< "x" << compress_.image_height;
|
||||||
|
|
||||||
|
if (nv_)
|
||||||
|
compressNV(&frame);
|
||||||
|
else
|
||||||
|
compressRGB(&frame);
|
||||||
|
|
||||||
|
jpeg_finish_compress(&compress_);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
42
src/android/jpeg/encoder_libjpeg.h
Normal file
42
src/android/jpeg/encoder_libjpeg.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020, Google Inc.
|
||||||
|
*
|
||||||
|
* encoder_libjpeg.h - JPEG encoding using libjpeg
|
||||||
|
*/
|
||||||
|
#ifndef __ANDROID_JPEG_ENCODER_LIBJPEG_H__
|
||||||
|
#define __ANDROID_JPEG_ENCODER_LIBJPEG_H__
|
||||||
|
|
||||||
|
#include "encoder.h"
|
||||||
|
|
||||||
|
#include "libcamera/internal/buffer.h"
|
||||||
|
#include "libcamera/internal/formats.h"
|
||||||
|
|
||||||
|
#include <jpeglib.h>
|
||||||
|
|
||||||
|
class EncoderLibJpeg : public Encoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EncoderLibJpeg();
|
||||||
|
~EncoderLibJpeg();
|
||||||
|
|
||||||
|
int configure(const libcamera::StreamConfiguration &cfg) override;
|
||||||
|
int encode(const libcamera::FrameBuffer *source,
|
||||||
|
const libcamera::Span<uint8_t> &destination) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void compressRGB(const libcamera::MappedBuffer *frame);
|
||||||
|
void compressNV(const libcamera::MappedBuffer *frame);
|
||||||
|
|
||||||
|
struct jpeg_compress_struct compress_;
|
||||||
|
struct jpeg_error_mgr jerr_;
|
||||||
|
|
||||||
|
unsigned int quality_;
|
||||||
|
|
||||||
|
const libcamera::PixelFormatInfo *pixelFormatInfo_;
|
||||||
|
|
||||||
|
bool nv_;
|
||||||
|
bool nvSwap_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __ANDROID_JPEG_ENCODER_LIBJPEG_H__ */
|
|
@ -6,12 +6,17 @@ android_hal_sources = files([
|
||||||
'camera_device.cpp',
|
'camera_device.cpp',
|
||||||
'camera_metadata.cpp',
|
'camera_metadata.cpp',
|
||||||
'camera_ops.cpp',
|
'camera_ops.cpp',
|
||||||
|
'jpeg/encoder_libjpeg.cpp',
|
||||||
])
|
])
|
||||||
|
|
||||||
android_camera_metadata_sources = files([
|
android_camera_metadata_sources = files([
|
||||||
'metadata/camera_metadata.c',
|
'metadata/camera_metadata.c',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
android_deps = [
|
||||||
|
dependency('libjpeg'),
|
||||||
|
]
|
||||||
|
|
||||||
android_camera_metadata = static_library('camera_metadata',
|
android_camera_metadata = static_library('camera_metadata',
|
||||||
android_camera_metadata_sources,
|
android_camera_metadata_sources,
|
||||||
include_directories : android_includes)
|
include_directories : android_includes)
|
||||||
|
|
|
@ -125,6 +125,8 @@ if get_option('android')
|
||||||
libcamera_sources += android_hal_sources
|
libcamera_sources += android_hal_sources
|
||||||
includes += android_includes
|
includes += android_includes
|
||||||
libcamera_link_with += android_camera_metadata
|
libcamera_link_with += android_camera_metadata
|
||||||
|
|
||||||
|
libcamera_deps += android_deps
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# We add '/' to the build_rpath as a 'safe' path to act as a boolean flag.
|
# We add '/' to the build_rpath as a 'safe' path to act as a boolean flag.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue