summaryrefslogtreecommitdiff
path: root/src/android/jpeg/encoder_libjpeg.cpp
diff options
context:
space:
mode:
authorKieran Bingham <kieran.bingham@ideasonboard.com>2020-06-24 14:15:15 +0100
committerKieran Bingham <kieran.bingham@ideasonboard.com>2020-08-06 15:46:02 +0100
commit998f4de65ebccf638b19a90172db74b2dc469c78 (patch)
treee4fc1bb0369963e31674c7ce708f078b9e62e93a /src/android/jpeg/encoder_libjpeg.cpp
parentc09aee4ccb7043484047d060f7f8bc1e0192ea23 (diff)
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>
Diffstat (limited to 'src/android/jpeg/encoder_libjpeg.cpp')
-rw-r--r--src/android/jpeg/encoder_libjpeg.cpp218
1 files changed, 218 insertions, 0 deletions
diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp
new file mode 100644
index 00000000..977538ce
--- /dev/null
+++ b/src/android/jpeg/encoder_libjpeg.cpp
@@ -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;
+}