Hi,
I've noticed there is currently no capture-preview support for CHDK,
although some stubs are present in the code. Of course, remote capture
without live view isn't that useful. So here's a patch, which also
updates chdk_live_view.h to the recent version. It supports either
jpeg (needs libjpeg) or ppm output.
Key issue with CHDK live view is that it usually produces a more or less
distorted picture with a wrong aspect ratio (typically 3:2 instead of
4:3). That's due to an internally used representation with "non-square"
pixels, so it seems impossible to do anything with it without
involving a scaling algorithm. Still it is better than nothing.
--
Regards,
Alexey Kryukov <anagnost at yandex dot ru>
Moscow State University
Faculty of History
diff -ubE ptp2.orig/chdk.c ptp2/chdk.c
--- ptp2.orig/chdk.c 2018-03-22 13:54:17.509009162 +0300
+++ ptp2/chdk.c 2018-03-23 13:22:35.392712627 +0300
@@ -27,6 +27,10 @@
#include <stdarg.h>
#include <time.h>
+#ifdef HAVE_LIBJPEG
+# include <jpeglib.h>
+#endif
+
#include <gphoto2/gphoto2-library.h>
#include <gphoto2/gphoto2-port-log.h>
#include <gphoto2/gphoto2-setting.h>
@@ -1152,6 +1156,228 @@
return ret;
}
+#ifdef HAVE_LIBJPEG
+static void yuv_live_to_jpeg(unsigned char *p_yuv,
+ int buf_width, int width, int height,
+ int fb_type, CameraFile *file
+) {
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ JSAMPROW row_ptr[1];
+ uint8_t *outbuf = NULL, *tmprowbuf = NULL;
+ uint64_t outlen = 0;
+ unsigned int row_inc;
+ int sshift, dshift, xshift, skip;
+
+ /* Pre-Digic 6 cameras: 8 bit per element UYVYYY,
+ * 6 bytes used to encode 4 pixels, need 12 bytes raw YUV data for jpeg encoding */
+ if (fb_type == LV_FB_YUV8) {
+ row_inc = buf_width*1.5;
+ sshift = 6;
+ dshift = (width/height > 2) ? 6 : 12;
+ xshift = 4;
+ /* Digic 6 cameras: 8 bit per element UYVY,
+ * 4 bytes used to encode 2 pixels, need 6 bytes raw YUV data for jpeg encoding */
+ } else {
+ row_inc = buf_width*2;
+ sshift = 4;
+ dshift = 6;
+ xshift = 2;
+ }
+ /* Encode only 2 pixels from each UV pair either if it is a UYVY data
+ * (for Digic 6 cameras) or if the width to height ratio provided
+ * by camera is too large (typically width 720 for height 240), so that
+ * the resulting image would be stretched too much in the horizontal
+ * direction if all 4 Y values were used. */
+ skip = (fb_type > LV_FB_YUV8) || (width/height > 2);
+
+ cinfo.err = jpeg_std_error (&jerr);
+ jpeg_create_compress (&cinfo);
+
+ cinfo.image_width = (width/height > 2) ? (width/2) & -1 : width & -1;
+ cinfo.image_height = height & -1;
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_YCbCr; // input color space
+
+ jpeg_mem_dest (&cinfo, &outbuf, &outlen);
+ jpeg_set_defaults (&cinfo);
+ cinfo.dct_method = JDCT_IFAST; // DCT method
+ jpeg_set_quality (&cinfo, 70, TRUE);
+
+ jpeg_start_compress (&cinfo, TRUE);
+
+ tmprowbuf = malloc (cinfo.image_width * 3);
+ row_ptr[0] = &tmprowbuf[0];
+
+ while (cinfo.next_scanline < cinfo.image_height) {
+ unsigned int x, i, j;
+ /* offset to the correct row */
+ unsigned int offset = cinfo.next_scanline * row_inc;
+
+ for (x = 0, i = 0, j = 0; x < width; i += sshift, j += dshift, x += xshift) {
+ int8_t u = (int8_t) p_yuv[offset + i + 0];
+ int8_t v = (int8_t) p_yuv[offset + i + 2];
+ if (fb_type == LV_FB_YUV8) {
+ u += 0x80;
+ v += 0x80;
+ }
+
+ tmprowbuf[j + 0] = p_yuv[offset + i + 1];
+ tmprowbuf[j + 1] = u;
+ tmprowbuf[j + 2] = v;
+ tmprowbuf[j + 3] = p_yuv[offset + i + 3];
+ tmprowbuf[j + 4] = u;
+ tmprowbuf[j + 5] = v;
+
+ if (!skip) {
+ tmprowbuf[j + 6] = p_yuv[offset + i + 4];
+ tmprowbuf[j + 7] = u;
+ tmprowbuf[j + 8] = v;
+ tmprowbuf[j + 9] = p_yuv[offset + i + 5];
+ tmprowbuf[j +10] = u;
+ tmprowbuf[j +11] = v;
+ }
+ }
+ jpeg_write_scanlines (&cinfo, row_ptr, 1);
+ }
+ jpeg_finish_compress (&cinfo);
+ jpeg_destroy_compress (&cinfo);
+
+ gp_file_append (file, (char*)outbuf, outlen);
+ gp_file_set_mime_type (file, GP_MIME_JPEG);
+ gp_file_set_name (file, "chdk_preview.jpg");
+
+ free (outbuf);
+ free (tmprowbuf);
+}
+
+#else
+static inline uint8_t clip_yuv (int v) {
+ if (v<0) return 0;
+ if (v>255) return 255;
+ return v;
+}
+
+static inline uint8_t yuv_to_r (uint8_t y, int8_t v) {
+ return clip_yuv (((y<<12) + v*5743 + 2048)>>12);
+}
+
+static inline uint8_t yuv_to_g (uint8_t y, int8_t u, int8_t v) {
+ return clip_yuv (((y<<12) - u*1411 - v*2925 + 2048)>>12);
+}
+
+static inline uint8_t yuv_to_b (uint8_t y, int8_t u) {
+ return clip_yuv (((y<<12) + u*7258 + 2048)>>12);
+}
+
+static void yuv_live_to_ppm (unsigned char *p_yuv,
+ int buf_width, int width, int height,
+ int fb_type, CameraFile *file
+) {
+ const unsigned char *p_row = p_yuv;
+ const unsigned char *p;
+ unsigned int row, x;
+ unsigned int row_inc;
+ int pshift, xshift, skip;
+ char ppm_header[32];
+ uint8_t rgb[6];
+
+ /* Pre-Digic 6 cameras:
+ * 8 bit per element UYVYYY, 6 bytes used to encode 4 rgb values */
+ if (fb_type == LV_FB_YUV8) {
+ row_inc = buf_width*1.5;
+ pshift = 6;
+ xshift = 4;
+ /* Digic 6 cameras:
+ * 8 bit per element UYVY, 4 bytes used to encode 2 rgb values */
+ } else {
+ row_inc = buf_width*2;
+ pshift = 4;
+ xshift = 2;
+ }
+ /* Encode only 2 pixels from each UV pair either if it is a UYVY data
+ * (for Digic 6 cameras) or if the width to height ratio provided
+ * by camera is too large (typically width 720 for height 240), so that
+ * the resulting image would be stretched too much in the horizontal
+ * direction if all 4 Y values were used. */
+ skip = (fb_type > LV_FB_YUV8) || (width/height > 2);
+
+ sprintf (ppm_header, "P6 %d %d 255\n", (width/height > 2) ? width/2 : width, height);
+ gp_file_append (file, ppm_header, strlen (ppm_header));
+
+ for (row=0; row<height; row++, p_row += row_inc) {
+ for (x=0, p=p_row; x<width; x+=xshift, p+=pshift) {
+ /* these are signed unlike the Y values */
+ int8_t u = (int8_t) p[0];
+ int8_t v = (int8_t) p[2];
+ /* See for example
+ * https://chdk.setepontos.com/index.php?topic=12692.msg130137#msg130137 */
+ if (fb_type > LV_FB_YUV8) {
+ u -= 0x80;
+ v -= 0x80;
+ }
+ rgb[0] = yuv_to_r (p[1], v);
+ rgb[1] = yuv_to_g (p[1], u, v);
+ rgb[2] = yuv_to_b (p[1], u);
+
+ rgb[3] = yuv_to_r (p[3], v);
+ rgb[4] = yuv_to_g (p[3], u, v);
+ rgb[5] = yuv_to_b (p[3], u);
+ gp_file_append (file, (char*)rgb, 6);
+
+ if (!skip) {
+ rgb[0] = yuv_to_r (p[4], v);
+ rgb[1] = yuv_to_g (p[4], u, v);
+ rgb[2] = yuv_to_b (p[4], u);
+
+ rgb[3] = yuv_to_r (p[5], v);
+ rgb[4] = yuv_to_g (p[5], u, v);
+ rgb[5] = yuv_to_b (p[5], u);
+ gp_file_append (file, (char*)rgb, 6);
+ }
+ }
+ }
+ gp_file_set_mime_type (file, GP_MIME_PPM);
+ gp_file_set_name (file, "chdk_preview.ppm");
+}
+#endif
+
+static int
+chdk_camera_capture_preview (Camera *camera, CameraFile *file, GPContext *context)
+{
+ unsigned char *data = NULL;
+ uint32_t size = 0;
+ PTPParams *params = &camera->pl->params;
+ unsigned int flags = LV_TFR_VIEWPORT;
+
+ lv_data_header header;
+ lv_framebuffer_desc vpd;
+ lv_framebuffer_desc bmd;
+
+ memset (&header, 0, sizeof (header));
+ memset (&vpd, 0, sizeof (vpd));
+ memset (&vpd, 0, sizeof (bmd));
+
+ CR (camera_prepare_chdk_capture (camera, context));
+ C_PTP_REP_MSG (ptp_chdk_get_live_data (params, flags, &data, &size),
+ _("CHDK get live data failed"));
+ if (ptp_chdk_parse_live_data (params, data, size, &header, &vpd, &bmd) != PTP_RC_OK) {
+ gp_context_error (context, _("CHDK get live data failed: incomplete data (%d bytes) returned"), size);
+ return GP_ERROR;
+ }
+#ifdef HAVE_LIBJPEG
+ yuv_live_to_jpeg(data+vpd.data_start, vpd.buffer_width, vpd.visible_width,
+ vpd.visible_height, vpd.fb_type, file);
+#else
+ yuv_live_to_ppm (data+vpd.data_start, vpd.buffer_width, vpd.visible_width,
+ vpd.visible_height, vpd.fb_type, file);
+#endif
+
+ free (data);
+ gp_file_set_mtime (file, time (NULL));
+ return GP_OK;
+}
+
int
chdk_init(Camera *camera, GPContext *context) {
camera->functions->about = chdk_camera_about;
@@ -1160,9 +1386,9 @@
camera->functions->summary = chdk_camera_summary;
camera->functions->get_config = chdk_camera_get_config;
camera->functions->set_config = chdk_camera_set_config;
+ camera->functions->capture_preview = chdk_camera_capture_preview;
/*
camera->functions->trigger_capture = camera_trigger_capture;
- camera->functions->capture_preview = camera_capture_preview;
camera->functions->wait_for_event = camera_wait_for_event;
*/
diff -ubE ptp2.orig/chdk_live_view.h ptp2/chdk_live_view.h
--- ptp2.orig/chdk_live_view.h 2018-03-22 13:54:17.509009162 +0300
+++ ptp2/chdk_live_view.h 2018-03-21 07:54:09.000000000 +0300
@@ -16,16 +16,19 @@
- In some cases, the requested data may not be available. If this happens, the framebuffer
or palette data offset will be zero.
- The frame buffer descriptions are returned regardless of whether the data is available
+- New enum values (e.g. aspect ratio, framebuffer type, palette type) may be added in minor
+ versions.
*/
// Live View protocol version
#define LIVE_VIEW_VERSION_MAJOR 2 // increase only with backwards incompatible changes (and reset minor)
-#define LIVE_VIEW_VERSION_MINOR 1 // increase with extensions of functionality
+#define LIVE_VIEW_VERSION_MINOR 2 // increase with extensions of functionality
/*
protocol version history
< 2.0 - development versions
2.0 - initial release, chdk 1.1
2.1 - added palette type 4 - 16 entry VUYA, 2 bit alpha
+2.2 - in development digic 6 support. Added LV_ASPECT_3_2, LV_FB_YUV8B and LV_FB_YUV8C formats
*/
@@ -33,10 +36,13 @@
#define LV_TFR_VIEWPORT 0x01
#define LV_TFR_BITMAP 0x04
#define LV_TFR_PALETTE 0x08
+#define LV_TFR_BITMAP_OPACITY 0x10
enum lv_aspect_rato {
LV_ASPECT_4_3,
LV_ASPECT_16_9,
+ // below added in 2.2
+ LV_ASPECT_3_2,
};
/*
@@ -46,6 +52,10 @@
enum lv_fb_type {
LV_FB_YUV8, // 8 bit per element UYVYYY, used for live view
LV_FB_PAL8, // 8 bit paletted, used for bitmap overlay. Note palette data and type sent separately
+ // below added in 2.2
+ LV_FB_YUV8B,// 8 bit per element UYVY, used for live view and overlay on Digic 6
+ LV_FB_YUV8C,// 8 bit per element UYVY, used for alternate Digic 6 live view
+ LV_FB_OPACITY8,// 8 bit opacity / alpha buffer
};
/*
@@ -94,6 +104,7 @@
// framebuffer descriptions are given as offsets, to allow expanding the structures in minor protocol changes
int vp_desc_start;
int bm_desc_start;
+ int bmo_desc_start; // added in protocol 2.2
} lv_data_header;
#endif // __LIVE_VIEW_H
diff -ubE ptp2.orig/Makefile-files ptp2/Makefile-files
--- ptp2.orig/Makefile-files 2018-03-22 13:54:17.505009150 +0300
+++ ptp2/Makefile-files 2018-03-22 22:55:08.686187011 +0300
@@ -17,4 +17,5 @@
ptp2/chdk.c
ptp2_la_LDFLAGS = $(camlib_ldflags)
ptp2_la_DEPENDENCIES = $(camlib_dependencies)
-ptp2_la_LIBADD = $(camlib_libadd) $(LTLIBICONV) $(LIBXML2_LIBS)
+ptp2_la_LIBADD = $(camlib_libadd) $(LTLIBICONV) $(LIBXML2_LIBS) @LIBJPEG@
+
diff -ubE ptp2.orig/ptp.c ptp2/ptp.c
--- ptp2.orig/ptp.c 2018-03-22 13:54:17.509009162 +0300
+++ ptp2/ptp.c 2018-03-22 14:08:16.107810074 +0300
@@ -4469,7 +4469,29 @@
return PTP_RC_OK;
}
+uint16_t
+ptp_chdk_parse_live_data (PTPParams* params, unsigned char *data, unsigned int data_size,
+ lv_data_header *header,
+ lv_framebuffer_desc *vpd, lv_framebuffer_desc *bmd
+) {
+ int byte_w;
+
+ if (data_size < sizeof (*header))
+ return PTP_ERROR_IO;
+ ptp_unpack_chdk_lv_data_header (params, data, header);
+ if (data_size < (header->vp_desc_start + sizeof (*vpd)) || data_size < (header->bm_desc_start + sizeof (*bmd)))
+ return PTP_ERROR_IO;
+ ptp_unpack_chdk_lv_framebuffer_desc (params, data+header->vp_desc_start, vpd);
+ ptp_unpack_chdk_lv_framebuffer_desc (params, data+header->vp_desc_start, bmd);
+ /* The buffer_width field corresponds to the number of Y values in a row,
+ * so the actual number of bytes would be either one and a half times
+ * or (for Digic 6 cameras) twice so large */
+ byte_w = (vpd->fb_type == LV_FB_YUV8) ? vpd->buffer_width * 1.5 : vpd->buffer_width * 2;
+ if (data_size < (vpd->data_start + (byte_w * vpd->visible_height)))
+ return PTP_ERROR_IO;
+ return PTP_RC_OK;
+}
/**
diff -ubE ptp2.orig/ptp.h ptp2/ptp.h
--- ptp2.orig/ptp.h 2018-03-22 13:54:17.509009162 +0300
+++ ptp2/ptp.h 2018-03-22 13:56:49.205474341 +0300
@@ -3487,6 +3487,8 @@
uint16_t ptp_chdk_write_script_msg(PTPParams* params, char *data, unsigned size, int target_script_id, int *status);
uint16_t ptp_chdk_read_script_msg(PTPParams* params, ptp_chdk_script_msg **msg);
uint16_t ptp_chdk_get_live_data(PTPParams* params, unsigned flags, unsigned char **data, unsigned int *data_size);
+uint16_t ptp_chdk_parse_live_data (PTPParams* params, unsigned char *data, unsigned int data_size,
+ lv_data_header *header, lv_framebuffer_desc *vpd, lv_framebuffer_desc *bmd);
uint16_t ptp_chdk_call_function(PTPParams* params, int *args, int size, int *ret);
/*uint16_t ptp_chdk_get_script_output(PTPParams* params, char **output ); */
diff -ubE ptp2.orig/ptp-pack.c ptp2/ptp-pack.c
--- ptp2.orig/ptp-pack.c 2018-03-22 13:54:17.509009162 +0300
+++ ptp2/ptp-pack.c 2018-03-22 13:57:40.005630477 +0300
@@ -2954,3 +2954,37 @@
free (xoifs);
return 0;
}
+
+static inline void
+ptp_unpack_chdk_lv_data_header (PTPParams *params, unsigned char* data, lv_data_header *header)
+{
+ int off = 0;
+ if (data==NULL)
+ return;
+ header->version_major = dtoh32a(&data[off]);
+ header->version_minor = dtoh32a(&data[off+=4]);
+ header->lcd_aspect_ratio = dtoh32a(&data[off+=4]);
+ header->palette_type = dtoh32a(&data[off+=4]);
+ header->palette_data_start = dtoh32a(&data[off+=4]);
+ header->vp_desc_start = dtoh32a(&data[off+=4]);
+ header->bm_desc_start = dtoh32a(&data[off+=4]);
+ if (header->version_minor > 1)
+ header->bmo_desc_start = dtoh32a(&data[off+=4]);
+}
+
+static inline void
+ptp_unpack_chdk_lv_framebuffer_desc (PTPParams *params, unsigned char* data, lv_framebuffer_desc *fd)
+{
+ int off = 0;
+ if (data==NULL)
+ return;
+ fd->fb_type = dtoh32a(&data[off]);
+ fd->data_start = dtoh32a(&data[off+=4]);
+ fd->buffer_width = dtoh32a(&data[off+=4]);
+ fd->visible_width = dtoh32a(&data[off+=4]);
+ fd->visible_height = dtoh32a(&data[off+=4]);
+ fd->margin_left = dtoh32a(&data[off+=4]);
+ fd->margin_top = dtoh32a(&data[off+=4]);
+ fd->margin_right = dtoh32a(&data[off+=4]);
+ fd->margin_bot = dtoh32a(&data[off+=4]);
+}
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Gphoto-devel mailing list
Gphoto-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/gphoto-devel