From 32ef6e729fb50dc57f00c9040073a4cf96de9f03 Mon Sep 17 00:00:00 2001 From: Steffen Pohle Date: Tue, 21 Sep 2021 22:58:08 +0200 Subject: [PATCH] video controls are working and can be set --- ChangeLog | 4 ++ gui.cc | 1 + gui.h | 3 + simpleskycam.ui | 79 +++++++++++++++++++++--- video.cc | 157 ++++++++++++++++++++++++++++++++++++++++++++++-- video.h | 15 +++++ videodev.cc | 152 ++++++++++++++++++++++++++++++++++++---------- 7 files changed, 365 insertions(+), 46 deletions(-) diff --git a/ChangeLog b/ChangeLog index 94a677a..968d341 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,8 @@ +2021-09-21: +- scaling the video to screensize works. +- controls can be set via gui + 2021-09-08: - grab video from old type of cameras is working diff --git a/gui.cc b/gui.cc index 248ab45..1ae6b37 100644 --- a/gui.cc +++ b/gui.cc @@ -36,6 +36,7 @@ void cb_window_show (GtkWidget *widget, gpointer data) { gtk_widget_set_sensitive(btnstart, true); gtk_widget_set_sensitive(btnstop, false); + g_timeout_add(2000, videoctrl_update, NULL); }; diff --git a/gui.h b/gui.h index f910a2b..165e85e 100644 --- a/gui.h +++ b/gui.h @@ -42,6 +42,9 @@ G_MODULE_EXPORT void cb_video_btnstop (GtkWidget *widget, gpointer data); G_MODULE_EXPORT void cb_videoda_draw(GtkWidget *area, cairo_t *cr, int w, int h, gpointer data); G_MODULE_EXPORT gboolean video_display(gpointer data); +G_MODULE_EXPORT void cb_vidctrl_entry_change (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_vidctrl_scale_change (GtkRange *range, gpointer data); +gboolean videoctrl_update(gpointer data); // diff --git a/simpleskycam.ui b/simpleskycam.ui index 3dede4a..de6c469 100644 --- a/simpleskycam.ui +++ b/simpleskycam.ui @@ -12,7 +12,7 @@ True True - 700 + 800 True True @@ -20,7 +20,7 @@ True True vertical - 250 + 480 True True @@ -147,7 +147,56 @@ - + + True + True + in + + + True + False + + + + True + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + 1 + @@ -158,20 +207,27 @@ True False - page 1 + Video Device False - + + True + False + nix da - page 2 + + + 1 + True False - page 2 + Detection 1 @@ -179,13 +235,20 @@ - + + True + False + nix da + + + 2 + True False - page 3 + Output 2 diff --git a/video.cc b/video.cc index 6dc6312..5153e33 100644 --- a/video.cc +++ b/video.cc @@ -6,6 +6,7 @@ #include #include +#include #include "gui.h" #include "video.h" @@ -15,6 +16,30 @@ GdkPixbuf *video_pixbuf = NULL; +gboolean videoctrl_update(gpointer data) { + GtkWidget *grid = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vidctrl-grid")); + GtkWidget *scale = NULL; + GtkWidget *entry = NULL; + GtkWidget *label = NULL; + + int value; + int min; + int max; + int i; + + for (i = 0; (label = gtk_grid_get_child_at(GTK_GRID(grid), 0, i)) != NULL; i++) { + if (videodev.GetCtrlMinMaxValue(gtk_label_get_text(GTK_LABEL(label)), &min, &max, &value) == VDEV_STATUS_OK) { + scale = gtk_grid_get_child_at(GTK_GRID(grid), 1, i); + gtk_range_set_value(GTK_RANGE(scale), value); + entry = gtk_grid_get_child_at(GTK_GRID(grid), 2, i); + gtk_entry_set_text(GTK_ENTRY(entry), std::to_string(value).c_str()); + } + } + + return TRUE; +} + + void videoframe_to_pixbuf(GdkPixbuf* dest, VideoFrame *src) { int destw, desth; unsigned char *destpixel; @@ -33,21 +58,43 @@ void videoframe_to_pixbuf(GdkPixbuf* dest, VideoFrame *src) { void cb_videoda_draw(GtkWidget *area, cairo_t *cr, int w, int h, gpointer data) { - if (video_da == NULL) return; + int clientw, clienth, pixbufw, pixbufh; + float clientar, pixbufar; + + GdkPixbuf *pixbuf = NULL; + + if (video_da == NULL) return; + + clienth = gtk_widget_get_allocated_height(video_da); + clientw = gtk_widget_get_allocated_width(video_da); + clientar = (float)clientw/(float)clienth; + pixbufh = gdk_pixbuf_get_height(video_pixbuf); + pixbufw = gdk_pixbuf_get_width(video_pixbuf); + pixbufar = (float)pixbufw/(float)pixbufh; + + if (pixbufar < clientar) { + clientw = (pixbufar * (float) clienth); + } + else { + clienth = ((float) clientw / pixbufar); + } + + pixbuf = gdk_pixbuf_scale_simple (video_pixbuf, clientw, clienth, GDK_INTERP_NEAREST); //cairo_move_to(cr, 30, 30); //cairo_set_font_size(cr,15); //cairo_show_text(cr, "hello world"); - - gdk_cairo_set_source_pixbuf(cr, video_pixbuf, 0, 0); + gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0); cairo_paint(cr); cairo_fill (cr); + g_object_unref (pixbuf); }; void video_draw_image (VideoFrame *vf) { int pix_w; int pix_h; + int x, y; if (video_da == NULL) { video_da = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "video-da")); @@ -67,7 +114,6 @@ void video_draw_image (VideoFrame *vf) { // display error screen? if (vf == NULL) { unsigned char *pixels; - int x, y; // need to allocate? if (video_pixbuf == NULL) { @@ -94,6 +140,7 @@ void video_draw_image (VideoFrame *vf) { else { // // changes in resolution? + if (video_pixbuf == NULL || pix_h != vf->h || pix_w != vf->w) { if (video_pixbuf != NULL) g_object_unref (video_pixbuf); @@ -138,12 +185,19 @@ void cb_video_btnrefreshlist (GtkWidget *widget, gpointer data) { // // start recording from the videodev (will start a new thread) +// load list of controls from the video device and create the GtkGrid +// with all elements. Also connect the signals to the callback functions. void cb_video_btnrec (GtkWidget *widget, gpointer data) { + std::list list; + std::list::iterator iter; GtkWidget *cbox = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "cb-videodev")); GtkWidget *btnstart = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "btn-video-rec")); GtkWidget *btnstop = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "btn-video-stop")); - GtkWidget *cbdevice = gtk_bin_get_child(GTK_BIN(cbox)); + GtkWidget *grid = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vidctrl-grid")); + GtkWidget *gridchild = NULL; + int i; + GtkWidget *cbdevice = gtk_bin_get_child(GTK_BIN(cbox)); std::string device = gtk_entry_get_text(GTK_ENTRY(cbdevice)); device = device.substr (0, device.find(' ')); @@ -152,6 +206,54 @@ void cb_video_btnrec (GtkWidget *widget, gpointer data) { gtk_widget_set_sensitive(btnstop, true); videodev.Start(device, cb_thread_video); + videodev.GetCtrlList(&list); + + // + // clear grid + while ((gridchild = gtk_grid_get_child_at(GTK_GRID(grid), 0, 0)) != NULL) { + gtk_grid_remove_row (GTK_GRID(grid), 0); + } + + // + // add elements + for (i = 0, iter = list.begin(); iter != list.end(); iter++, i++) { + int min = 0; + int max = 100; + int value = 50; + + videodev.GetCtrlMinMaxValue((*iter), &min, &max, &value); + + // + // label + GtkWidget *label = gtk_label_new(iter->c_str()); + + // + // scale/range + GtkWidget *scale = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, min, max, 1); + gtk_range_set_value(GTK_RANGE(scale),value); + gtk_widget_set_hexpand (scale,true); + gtk_scale_set_draw_value(GTK_SCALE(scale), false); + g_signal_connect (GTK_RANGE(scale), "value-changed", G_CALLBACK(cb_vidctrl_scale_change), (void*)(long int)i); + + // + // entry field + GtkWidget *entry = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(entry), std::to_string(value).c_str()); + g_signal_connect (entry, "activate", G_CALLBACK(cb_vidctrl_entry_change), (void*)(long int)i); + + gtk_grid_insert_row(GTK_GRID(grid), i); + if (i == 0) { + gtk_grid_insert_column(GTK_GRID(grid), 0); + gtk_grid_insert_column(GTK_GRID(grid), 0); + gtk_grid_insert_column(GTK_GRID(grid), 0); + } + gtk_grid_attach(GTK_GRID(grid), label, 0, i, 1, 1); + gtk_grid_attach(GTK_GRID(grid), scale, 1, i, 1, 1); + gtk_grid_attach(GTK_GRID(grid), entry, 2, i, 1, 1); + gtk_widget_show (label); + gtk_widget_show (scale); + gtk_widget_show (entry); + } }; @@ -170,6 +272,9 @@ void cb_video_btnstop (GtkWidget *widget, gpointer data) { +// +// callback from videodev thread. data will point to the latest VideoFrame. +// Access to this data must be Locked before use. gboolean cb_thread_video (gpointer data) { GtkWidget *btnstart; GtkWidget *btnstop; @@ -192,3 +297,45 @@ gboolean cb_thread_video (gpointer data) { return false; }; + +// +// set ctrl on the device +void videoctrl_set(std::string name, int value) { + videodev.SetCtrlValue(name, value); +} + +// +// callback video control scale change +void cb_vidctrl_scale_change (GtkRange *range, gpointer data) { + GtkWidget *grid = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vidctrl-grid")); + GtkWidget *scale = NULL; + GtkWidget *label = NULL; + int idx = (long int)data; + double value; + + label = gtk_grid_get_child_at(GTK_GRID(grid), 0, idx); + scale = gtk_grid_get_child_at(GTK_GRID(grid), 1, idx); + if (scale == NULL) return; + + value = gtk_range_get_value(GTK_RANGE(scale)); + videoctrl_set((std::string)gtk_label_get_text(GTK_LABEL(label)), (int)value); +}; + + +// +// callback video control entry change +void cb_vidctrl_entry_change (GtkWidget *widget, gpointer data) { + GtkWidget *grid = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vidctrl-grid")); + GtkWidget *label = NULL; + GtkWidget *entry = NULL; + int idx = (long int)data; + int value; + + label = gtk_grid_get_child_at(GTK_GRID(grid), 0, idx); + entry = gtk_grid_get_child_at(GTK_GRID(grid), 2, idx); + if (entry == NULL) return; + + value = atoi(gtk_entry_get_text(GTK_ENTRY(entry))); + videoctrl_set((std::string)gtk_label_get_text(GTK_LABEL(label)), (int)value); +}; + diff --git a/video.h b/video.h index c25ff24..ca8b63a 100644 --- a/video.h +++ b/video.h @@ -50,6 +50,15 @@ enum { }; +struct { + unsigned int id; + int min; + int max; + int value; + std::string name; +} typedef VideoDevCtrl; + + struct { unsigned int size; unsigned char* data; @@ -79,6 +88,7 @@ private: struct v4l2_crop crop; struct v4l2_format fmt; VideoFrame vf; + std::list vidctrls; struct jpg_error_mgr jerr; struct jpeg_decompress_struct cinfo; @@ -99,6 +109,8 @@ private: int OpenInit (); int Close (); int Grab(VideoFrame *vf); + int SetDevCtrl(unsigned int id, int value); + int GetDevCtrl(unsigned int id, int *value); int xioctl (int fd, int request, void *arg); public: VideoDev(); @@ -110,6 +122,9 @@ public: int Stop(); // stop video processing and stop the pthread int GetDeviceList(std::list *list); + int GetCtrlList(std::list *list); + int GetCtrlMinMaxValue(std::string name, int *min, int *max, int *value); + int SetCtrlValue(std::string name, int value); int IsRunning() { return running; }; void PrintCaps (uint32_t caps); diff --git a/videodev.cc b/videodev.cc index 6534013..41f1995 100644 --- a/videodev.cc +++ b/videodev.cc @@ -143,7 +143,66 @@ int VideoDev::GetDeviceList(std::list *list) { // -// start the video thread (on error, call cb_thread_videodev with NULL data) +// return a list of strings for controls +// +int VideoDev::GetCtrlList(std::list *list) { + std::list::iterator iter; + if (list == NULL) return VDEV_STATUS_ERROR; + + list->clear(); + LockMutex(); + + for (iter = vidctrls.begin(); iter != vidctrls.end(); iter++) { + list->push_back ((*iter).name); + } + + UnLockMutex(); + + return VDEV_STATUS_OK; +}; + + +// +// return the values for an control, on error VDEV_STATUS_UNKNOWN +// +int VideoDev::GetCtrlMinMaxValue(std::string name, int *min, int *max, int *value) { + std::list::iterator iter; + LockMutex(); + for (iter = vidctrls.begin(); iter != vidctrls.end(); iter++) + if (iter->name.compare(name) == 0) { + GetDevCtrl(iter->id, &(iter->value)); + if (value != NULL) *value = iter->value; + if (min != NULL) *min = iter->min; + if (max != NULL) *max = iter->max; + break; + } + UnLockMutex(); + if (iter == vidctrls.end()) return VDEV_STATUS_ERROR; + return VDEV_STATUS_OK; +}; + + +// +// set the value for an control, on error VDEV_STATUS_UNKNOWN +// +int VideoDev::SetCtrlValue(std::string name, int value) { + std::list::iterator iter; + LockMutex(); + for (iter = vidctrls.begin(); iter != vidctrls.end(); iter++) + if (iter->name.compare(name) == 0) { + SetDevCtrl(iter->id, value); + break; + } + UnLockMutex(); + if (iter == vidctrls.end()) return VDEV_STATUS_ERROR; + return VDEV_STATUS_OK; +}; + + + +// +// start the video, start capturing, start thread +// after return of this function we can call the Ctrl thread // int VideoDev::Start(std::string dev, gboolean (*callback_func)(gpointer data)) { if (running != 0 || thread != NULL) return VDEV_STATUS_ERROR; @@ -151,9 +210,24 @@ int VideoDev::Start(std::string dev, gboolean (*callback_func)(gpointer data)) { running = 1; conf_device = dev; callback = callback_func; + + // + // open and init device buffers device + if (OpenInit() != VDEV_STATUS_OK) { + Close(); + return VDEV_STATUS_ERROR; + } + + // + // start capturing + if (CaptureStart() != VDEV_STATUS_OK) { + Close(); + return VDEV_STATUS_ERROR; + } + thread = g_thread_new("VideoDev", _VideoDevThread, NULL); - return VDEV_STATUS_ERROR; + return VDEV_STATUS_OK; }; @@ -188,14 +262,6 @@ void VideoDev::Thread() { printf ("%s:%d %s Enter\n", __FILE__, __LINE__, __FUNCTION__); cycle_time = get_cycletime(&cycle_timestamp); // just start counting - // - // open and init device buffers device - if (OpenInit() != VDEV_STATUS_OK) running = 0; - - // - // start capturing - if (CaptureStart() != VDEV_STATUS_OK) running = 0; - // // read untill something bad happens.. while (running) { @@ -318,8 +384,7 @@ int VideoDev::Grab(VideoFrame *vf) { if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { printf ( "%s:%d error on VIDIOC_QBUF %s\n", __FILE__, __LINE__, strerror(errno)); - exit (1); - } + return VDEV_STATUS_ERROR; } if (++inbuffer_idx >= VDEV_INBUFFERS) inbuffer_idx = 0; break; @@ -337,6 +402,7 @@ int VideoDev::Grab(VideoFrame *vf) { int VideoDev::OpenInit() { int i; struct v4l2_capability vcap; + VideoDevCtrl vctl; printf ("%s:%d %s Device: '%s'\n", __FILE__, __LINE__, __FUNCTION__, conf_device.c_str()); if (fd != -1) return VDEV_STATUS_ERROR; @@ -365,33 +431,21 @@ int VideoDev::OpenInit() { // // query controls struct v4l2_queryctrl queryctrl; - struct v4l2_control control; uint32_t min; - printf (" Controls: \n"); + vidctrls.clear(); memset (&queryctrl, 0, sizeof (queryctrl)); - for (i = V4L2_CID_BASE; i < V4L2_CID_LASTP1; i++) { + for (i = V4L2_CID_BASE; i < V4L2_CID_DETECT_CLASS_BASE+0x1000; i++) { queryctrl.id = i; if (0 == ioctl (fd, VIDIOC_QUERYCTRL, &queryctrl)) { - if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) - printf (" *"); - - memset (&control, 0, sizeof (control)); - control.id = i; - ioctl (fd, VIDIOC_G_CTRL, &control); - printf (" [%d] %s:%d\n", i, queryctrl.name, control.value); - } - } - printf ("\n Private Controls: "); - for (i = V4L2_CID_PRIVATE_BASE;; i++) { - queryctrl.id = i; - if (0 == ioctl (fd, VIDIOC_QUERYCTRL, &queryctrl)) { - printf (" [%d] %s\n", i, queryctrl.name); - } else { - break; + vctl.name = (char*)queryctrl.name; + vctl.id = queryctrl.id; + vctl.min = queryctrl.minimum; + vctl.max = queryctrl.maximum; + GetDevCtrl(queryctrl.id, &vctl.value); + vidctrls.push_back(vctl); } } - printf ("\n"); // // check for cropping.. if we have it setup default @@ -406,7 +460,6 @@ int VideoDev::OpenInit() { printf ("%s:%d %s VIDEOC_S_CROP Errorcode: %s\n", __FILE__, __LINE__, __FUNCTION__, strerror(errno)); } } - printf (" max image size: %d + %d : %d x %d fmt size:%d\n", cropcap.defrect.left, cropcap.defrect.top, cropcap.defrect.width, cropcap.defrect.height, fmt.fmt.pix.sizeimage); CLEAR (fmt); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; @@ -446,6 +499,39 @@ int VideoDev::OpenInit() { }; +// +// set video control identified by id +int VideoDev::SetDevCtrl(unsigned int id, int value) { + struct v4l2_control ctrl; + + CLEAR(ctrl); + ctrl.id = id; + ctrl.value = value; + if (-1 == xioctl (fd, VIDIOC_S_CTRL, &ctrl)) { + return VDEV_STATUS_ERROR; + } + + return VDEV_STATUS_OK; +}; + + +// +// get video control identified by id +int VideoDev::GetDevCtrl(unsigned int id, int *value) { + struct v4l2_control ctrl; + + CLEAR(ctrl); + ctrl.id = id; + if (-1 == xioctl (fd, VIDIOC_G_CTRL, &ctrl)) { + return VDEV_STATUS_ERROR; + } + *value = ctrl.value; + + return VDEV_STATUS_OK; +}; + + + // // prepare memory mapped buffers int VideoDev::InitMMap() {