From 97036161f78b336d9778460335d8fedb42259e5f Mon Sep 17 00:00:00 2001
From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Date: Mon, 19 Mar 2012 12:58:24 +0100
Subject: snapshot: Add JPEG compression support

Specifying the -j flag results in captured images being saved in JPEG
format instead of raw YUYV. JPEG compression implies YUYV capture,
software demosaicing isn't supported.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 Makefile   |   9 ++-
 jpeg.c     | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 jpeg.h     |  39 +++++++++++
 snapshot.c |  69 +++++++++++++++++---
 4 files changed, 321 insertions(+), 10 deletions(-)
 create mode 100644 jpeg.c
 create mode 100644 jpeg.h

diff --git a/Makefile b/Makefile
index 107e34d..07d1973 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,13 @@ LIBS	:= -lomap3isp -lrt
 OBJ_LIVE := live.o events.o iq.o videoout.o
 OBJ_SNAP := snapshot.o events.o iq.o
 
+ifneq (x$(LIBJPEG), x)
+CFLAGS += -I$(LIBJPEG)/include -DUSE_LIBJPEG=1
+SNAP_LDFLAGS = -L$(LIBJPEG)/lib
+SNAP_LIBS = -ljpeg
+OBJ_SNAP += jpeg.o
+endif
+
 %.o : %.c
 	$(CC) $(CFLAGS) -c -o $@ $<
 
@@ -18,7 +25,7 @@ live: $(OBJ_LIVE) isp/libomap3isp.so
 	$(CC) $(LDFLAGS) -o $@ $(OBJ_LIVE) $(LIBS)
 
 snapshot: $(OBJ_SNAP) isp/libomap3isp.so
-	$(CC) $(LDFLAGS) -o $@ $(OBJ_SNAP) $(LIBS)
+	$(CC) $(LDFLAGS) $(SNAP_LDFLAGS) -o $@ $(OBJ_SNAP) $(LIBS) $(SNAP_LIBS)
 
 __isp:
 	$(MAKE) -C isp CROSS_COMPILE=$(CROSS_COMPILE) KDIR=$(KDIR)
diff --git a/jpeg.c b/jpeg.c
new file mode 100644
index 0000000..a7e1963
--- /dev/null
+++ b/jpeg.c
@@ -0,0 +1,214 @@
+/*
+ * OMAP3 ISP snapshot test application
+ *
+ * Copyright (C) 2012 Ideas on board SPRL
+ *
+ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#define _BSD_SOURCE
+#include <errno.h>
+#include <setjmp.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <jpeglib.h>
+
+#include "isp/tools.h"
+#include "isp/v4l2-pool.h"
+
+#include "jpeg.h"
+
+struct jpeg {
+	struct jpeg_compress_struct cinfo;
+	struct jpeg_error_mgr error;
+	unsigned int width;
+	unsigned int height;
+	uint8_t *buffer;
+	jmp_buf jump;
+};
+
+static void jpeg_error_exit(j_common_ptr cinfo)
+{
+	struct jpeg *jpeg = container_of(cinfo, struct jpeg, cinfo);
+
+	longjmp(jpeg->jump, 1);
+}
+
+int jpeg_save(struct jpeg *jpeg, FILE *file, struct v4l2_video_buffer *buffer)
+{
+	JSAMPROW rows_y[DCTSIZE];
+	JSAMPROW rows_u[DCTSIZE];
+	JSAMPROW rows_v[DCTSIZE];
+	JSAMPARRAY image[3];
+	unsigned int width;
+	unsigned int height;
+	uint8_t *planes[3];
+	unsigned int x;
+	unsigned int y;
+	uint8_t *mem;
+	unsigned int i;
+
+	/* Convert the YUYV input buffer to a planar format for JPEG
+	 * compression.
+	 */
+	width = (jpeg->width + 2 * DCTSIZE - 1) / (2 * DCTSIZE) * (2 * DCTSIZE);
+	height = (jpeg->height + DCTSIZE - 1) / DCTSIZE * DCTSIZE;
+
+	planes[0] = jpeg->buffer;
+	planes[1] = jpeg->buffer + width * height;
+	planes[2] = jpeg->buffer + 3 * width / 2 * height;
+
+	mem = buffer->mem;
+
+	for (y = 0; y < jpeg->height; ++y) {
+		for (x = 0; x < jpeg->width; x += 2) {
+			planes[0][x] = *mem++;
+			planes[1][x/2] = *mem++;
+			planes[0][x+1] = *mem++;
+			planes[2][x/2] = *mem++;
+		}
+		planes[0] += width;
+		planes[1] += width / 2;
+		planes[2] += width / 2;
+	}
+
+	/* Now perform JPEG compression. */
+	if (setjmp(jpeg->jump))
+		return -EIO;
+
+	jpeg_stdio_dest(&jpeg->cinfo, file);
+	jpeg_start_compress(&jpeg->cinfo, TRUE);
+
+	rows_y[0] = jpeg->buffer;
+	rows_u[0] = jpeg->buffer + width * height;
+	rows_v[0] = jpeg->buffer + 3 * width / 2 * height;
+
+	for (i = 1; i < DCTSIZE; ++i) {
+		rows_y[i] = rows_y[i-1] + width;
+		rows_u[i] = rows_u[i-1] + width / 2;
+		rows_v[i] = rows_v[i-1] + width / 2;
+	}
+
+	image[0] = rows_y;
+	image[1] = rows_u;
+	image[2] = rows_v;
+
+	while (jpeg->cinfo.next_scanline < jpeg->height) {
+		unsigned int written;
+
+		written = jpeg_write_raw_data(&jpeg->cinfo, image, DCTSIZE);
+
+		for (i = 0; i < DCTSIZE; ++i) {
+			rows_y[i] += width * written;
+			rows_u[i] += width / 2 * written;
+			rows_v[i] += width / 2 * written;
+		}
+	}
+
+	jpeg_finish_compress(&jpeg->cinfo);
+
+	return 0;
+}
+
+static int jpeg_init_compressor(struct jpeg *jpeg)
+{
+	jpeg->cinfo.err = jpeg_std_error(&jpeg->error);
+	jpeg->error.error_exit = jpeg_error_exit;
+	if (setjmp(jpeg->jump)) {
+		jpeg_destroy_compress(&jpeg->cinfo);
+		jpeg->cinfo.err = NULL;
+		printf("Unable to create JPEG compression handler.\n");
+		return -EIO;
+	}
+
+	jpeg_create_compress(&jpeg->cinfo);
+
+	jpeg->cinfo.image_width = jpeg->width;
+	jpeg->cinfo.image_height = jpeg->height;
+	jpeg->cinfo.input_components = 3;
+	jpeg->cinfo.in_color_space = JCS_YCbCr;
+
+	jpeg_set_defaults(&jpeg->cinfo);
+	jpeg_set_quality(&jpeg->cinfo, 80, TRUE);
+
+	jpeg->cinfo.raw_data_in = TRUE;
+	jpeg_set_colorspace(&jpeg->cinfo, JCS_YCbCr);
+	jpeg->cinfo.comp_info[0].h_samp_factor = 2;
+	jpeg->cinfo.comp_info[0].v_samp_factor = 1;
+	jpeg->cinfo.comp_info[1].h_samp_factor = 1;
+	jpeg->cinfo.comp_info[1].v_samp_factor = 1;
+	jpeg->cinfo.comp_info[2].h_samp_factor = 1;
+	jpeg->cinfo.comp_info[2].v_samp_factor = 1;
+
+	return 0;
+}
+
+struct jpeg *jpeg_init(const struct v4l2_mbus_framefmt *format)
+{
+	unsigned int width;
+	unsigned int height;
+	struct jpeg *jpeg;
+
+	jpeg = malloc(sizeof *jpeg);
+	if (jpeg == NULL)
+		return NULL;
+
+	memset(jpeg, 0, sizeof *jpeg);
+	jpeg->width = format->width;
+	jpeg->height = format->height;
+
+	if (jpeg_init_compressor(jpeg) < 0) {
+		free(jpeg);
+		return NULL;
+	}
+
+	/* Allocate a temporary buffer to hold the planar YUV image.
+	 *
+	 * All planes must be padded to the DCT block size horizontally
+	 * and vertically. The luma plane must thus be padded to twice
+	 * the DCT block size horizontally as the chroma planes are
+	 * subsampled by 2 in the horizontal direction. For simplicity,
+	 * pad all planes to twice the DCT block size horizontally.
+	 */
+	width = (format->width + 2 * DCTSIZE - 1) / (2 * DCTSIZE) * (2 * DCTSIZE);
+	height = (format->height + DCTSIZE - 1) / DCTSIZE * DCTSIZE;
+
+	jpeg->buffer = malloc(width * height * 2);
+	if (jpeg->buffer == NULL) {
+		jpeg_destroy_compress(&jpeg->cinfo);
+		jpeg->cinfo.err = NULL;
+		free(jpeg);
+		printf("Unable to allocate memory for JPEG compressor.\n");
+		return NULL;
+	}
+
+	return jpeg;
+}
+
+void jpeg_cleanup(struct jpeg *jpeg)
+{
+	if (jpeg->cinfo.err != NULL) {
+		jpeg_destroy_compress(&jpeg->cinfo);
+		jpeg->cinfo.err = NULL;
+	}
+	free(jpeg->buffer);
+	free(jpeg);
+}
diff --git a/jpeg.h b/jpeg.h
new file mode 100644
index 0000000..0d646d0
--- /dev/null
+++ b/jpeg.h
@@ -0,0 +1,39 @@
+/*
+ * OMAP3 ISP snapshot test application
+ *
+ * Copyright (C) 2012 Ideas on board SPRL
+ *
+ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __JPEG_H__
+#define __JPEG_H__
+
+#include <stdio.h>
+
+#include <linux/v4l2-mediabus.h>
+
+#include "isp/v4l2-pool.h"
+
+struct jpeg;
+
+struct jpeg *jpeg_init(const struct v4l2_mbus_framefmt *format);
+void jpeg_cleanup(struct jpeg *jpeg);
+
+int jpeg_save(struct jpeg *jpeg, FILE *file, struct v4l2_video_buffer *buffer);
+
+#endif
diff --git a/snapshot.c b/snapshot.c
index 958307e..5d29653 100644
--- a/snapshot.c
+++ b/snapshot.c
@@ -42,6 +42,11 @@
 
 #include <linux/spi/spidev.h>
 
+#if USE_LIBJPEG
+#include <jpeglib.h>
+#include <setjmp.h>
+#endif
+
 #include "isp/list.h"
 #include "isp/omap3isp.h"
 #include "isp/tools.h"
@@ -50,6 +55,7 @@
 
 #include "events.h"
 #include "iq.h"
+#include "jpeg.h"
 
 #define MEDIA_DEVICE		"/dev/media0"
 
@@ -86,6 +92,9 @@ struct snapshot {
 	struct timespec time;
 	enum snapshot_trigger_type trigger;
 	bool save;
+
+	bool jpeg_enable;
+	struct jpeg *jpeg;
 };
 
 static struct snapshot snap = {
@@ -151,8 +160,8 @@ static bool snapshot_process(struct omap3_isp_device *isp,
 {
 	static struct timespec ts;
 	char name[33];
+	FILE *file;
 	int ret;
-	int fd;
 
 	if (buffer->error) {
 		printf("warning: error in dequeued buffer, skipping\n");
@@ -172,14 +181,21 @@ static bool snapshot_process(struct omap3_isp_device *isp,
 	printf("frame captured in %lu.%06lus.\n", ts.tv_sec, ts.tv_nsec / 1000);
 
 	if (snap.save && snap.frame >= snap.skip) {
-		sprintf(name, "snapshot-%06u-%02u.bin", snap.cycle, snap.frame);
-		fd = open(name, O_WRONLY | O_CREAT,
-			  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
-		if (fd == -1)
+		sprintf(name, "snapshot-%06u-%02u.%s", snap.cycle, snap.frame,
+#if USE_LIBJPEG
+			snap.jpeg ? "jpg" :
+#endif
+			"bin");
+		file = fopen(name, "wb");
+		if (file == NULL)
 			goto requeue;
-
-		write(fd, buffer->mem, buffer->bytesused);
-		close(fd);
+#if USE_LIBJPEG
+		if (snap.jpeg)
+			jpeg_save(snap.jpeg, file, buffer);
+		else
+#endif
+			fwrite(buffer->mem, buffer->bytesused, 1, file);
+		fclose(file);
 	}
 
 requeue:
@@ -220,12 +236,30 @@ static int snapshot_init(struct omap3_isp_device *isp)
 		return ret;
 	}
 
+#if USE_LIBJPEG
+	if (snap.jpeg_enable) {
+		snap.jpeg = jpeg_init(&snap.format);
+		if (snap.jpeg == NULL) {
+			printf("Unable to initialize JPEG compressor.\n");
+			return -EIO;
+		}
+	}
+#endif
+
 	printf("snapshot configured for %04x %ux%u\n",
 	       snap.format.code, snap.format.width, snap.format.height);
 
 	return 0;
 }
 
+static void snapshot_cleanup(void)
+{
+#if USE_LIBJPEG
+	if (snap.jpeg != NULL)
+		jpeg_cleanup(snap.jpeg);
+#endif
+}
+
 /* -----------------------------------------------------------------------------
  * Viewfinder
  */
@@ -394,6 +428,9 @@ static void usage(const char *argv0)
 	printf("-f, --format fmt	Snapshot format\n");
 	printf("-h, --help		Show this help screen\n");
 	printf("-i, --interval n	Capture a snapshot every n frames (default 20)\n");
+#if USE_LIBJPEG
+	printf("-j, --jpeg		Save snapshots in JPEG format (implies YUV mode)\n");
+#endif
 	printf("-k, --skip n		Skip n frames per snapshot when saving to disk (default 2)\n");
 	printf("-N, --snap-frames n	Capture n frames per snapshot (default 3)\n");
 	printf("-n, --snap-number n	Capture n snapshots (default 1)\n");
@@ -425,6 +462,9 @@ static struct option opts[] = {
 	{ "format", 1, 0, 'f' },
 	{ "help", 0, 0, 'h' },
 	{ "interval", 1, 0, 'i' },
+#if USE_LIBJPEG
+	{ "jpeg", 0, 0, 'j' },
+#endif
 	{ "save", 0, 0, 'S' },
 	{ "size", 1, 0, 's' },
 	{ "snap-frames", 1, 0, 'N' },
@@ -451,7 +491,11 @@ int main(int argc __attribute__((__unused__)), char *argv[] __attribute__((__unu
 
 	iq_params_init(&iq_params);
 
-	while ((c = getopt_long(argc, argv, "c:f:hi:k:N:n:Ss:v", opts, NULL)) != -1) {
+	while ((c = getopt_long(argc, argv, "c:f:hi:"
+#if USE_LIBJPEG
+	"j"
+#endif
+	"k:N:n:Ss:v", opts, NULL)) != -1) {
 		switch (c) {
 		case 'c':
 			if (parse_crop(optarg, &snap.crop)) {
@@ -476,6 +520,12 @@ int main(int argc __attribute__((__unused__)), char *argv[] __attribute__((__unu
 				return 1;
 			}
 			break;
+#if USE_LIBJPEG
+		case 'j':
+			snap.jpeg_enable = true;
+			snap.format.code = V4L2_MBUS_FMT_YUYV8_1X16;
+			break;
+#endif
 		case 'k':
 			snap.skip = strtoul(optarg, NULL, 10);
 			break;
@@ -612,6 +662,7 @@ cleanup:
 		viewfinder_cleanup(isp);
 	if (iq)
 		iq_cleanup(iq);
+	snapshot_cleanup();
 
 	return exit_code;
 }
-- 
cgit v1.2.3