diff --git a/ChangeLog b/ChangeLog index 8d365a5..18cc795 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,5 @@ +2021-11-15: +- adding support for multiple drivers 2021-10-25: - adding support for different resolution and pixelformat diff --git a/Makefile b/Makefile index 1127d48..049cd1e 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ APP = simpleskycam -include Makefile.rules -OBJECTS = gui.oo main.oo video.oo videoframe.oo videodev.oo filter.oo detect.oo json.oo configuration.oo +OBJECTS = gui.oo main.oo video.oo videoframe.oo videodev.oo videodev-v4l2.oo convert.oo filter.oo detect.oo json.oo configuration.oo DISTNAME=simpleskycam-$(VERSION) ifeq ($(TARGET),) diff --git a/Makefile.rules.linux b/Makefile.rules.linux index 3857cad..9b719ae 100644 --- a/Makefile.rules.linux +++ b/Makefile.rules.linux @@ -2,7 +2,7 @@ TARGET = $(APP) CPP = c++ -CPPFLAGS = -std=c++11 -ggdb -Wall -O0 `pkg-config --cflags gtk+-3.0 gmodule-export-2.0` -Wl,--export-dynamic -I/usr/include -DBUILD_LINUX=1 +CPPFLAGS = -std=c++11 -ggdb -Wall -Werror -O0 `pkg-config --cflags gtk+-3.0 gmodule-export-2.0` -Wl,--export-dynamic -I/usr/include -DBUILD_LINUX=1 INCLUDES = LDFLAGS = LIBS = `pkg-config --libs gtk+-3.0 gmodule-export-2.0` -L/usr/lib -ljpeg diff --git a/configuration.cc b/configuration.cc index a8a7f29..1523f23 100644 --- a/configuration.cc +++ b/configuration.cc @@ -5,7 +5,7 @@ #include -extern VideoDev videodev; +extern VideoDev *videodev; extern GtkBuilder *_builder_; // work around for threads @@ -71,7 +71,12 @@ void Configuration::SaveConfig(std::string filename) { cb = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "cb-videofmt")); cbe = gtk_bin_get_child(GTK_BIN(cb)); jp.AddObject("video_format", (string) gtk_entry_get_text(GTK_ENTRY(cbe))); - jp.AddObject("device", videodev.GetDevice()); + if (videodev != NULL) { + jp.AddObject("device", videodev->GetDevice()); + } + else { + jp.AddObject("device", ""); + } // // save button config diff --git a/convert.cc b/convert.cc new file mode 100644 index 0000000..097083b --- /dev/null +++ b/convert.cc @@ -0,0 +1,253 @@ + +#include +#include "convert.h" +#include "gui.h" +#include "video.h" + + +/* + * helper part for converting different video types + */ + +// +// jpeg: replacement for error_exit +// +METHODDEF(void) jpg_error_exit (j_common_ptr cinfo) { + jpg_error_ptr myerr = (jpg_error_ptr) cinfo->err; + (*cinfo->err->output_message) (cinfo); + longjmp(myerr->setjmp_buffer, 1); +} + + + +// +// clamp and convert2rgb is build on the sample of the v4l2 api documentation +// +inline unsigned char clamp (double x) { + int r = (int)x; + + if (r < 0) return 0; + else if (r > 255) return 255; + else return r; +}; + + +inline void convert2rgb (unsigned char Y1, unsigned char Cb, unsigned char Cr, + unsigned char *ER, unsigned char *EB, unsigned char *EG) { + register int y1, pb, pr; + + y1 = Y1 - 16; + pb = Cb - 128; + pr = Cr - 128; + + *ER = clamp (y1 + 1.402 * pr); + *EB = clamp (y1 - 0.344 * pb - 0.714 * pr); + *EG = clamp (y1 + 1.772 * pb); +}; + + +int ConvertStart(ConvertData *cdata, uint32_t pixelformat) { + if (cdata == NULL) return VDEV_STATUS_ERROR; + + if (pixelformat == V4L2_PIX_FMT_MJPEG) { + jpeg_create_decompress(&cdata->cinfo); + } + + return VDEV_STATUS_OK; +}; + +int ConvertStop(ConvertData *cdata, uint32_t pixelformat) { + if (cdata == NULL) return VDEV_STATUS_ERROR; + + if (pixelformat == V4L2_PIX_FMT_MJPEG) jpeg_destroy_decompress(&cdata->cinfo); + + return VDEV_STATUS_OK; +}; + +/* + * converts the video from input type to RGB24 type - 24Bit + */ +int Convert (ConvertData *cdata, VideoFrame *dest, unsigned char *ptrsrc, int srcsize, uint32_t pixelformat, int srcw, int srch) { + int xs, ys; + int xd, yd; + unsigned char r,g,b; + unsigned char cb, cr, y1; + unsigned char *ptrdst = NULL; + + struct jpg_error_mgr jerr; + if (cdata == NULL) return VDEV_STATUS_ERROR; + + // check if there is a destination and that the destination is large to keep + // the full image + if (dest == NULL || ptrsrc == NULL) + return VDEV_STATUS_ERROR; + + if (dest->data != NULL && dest->w != srcw && dest->h != srch) { + free (dest->data); + dest->data = NULL; + } + + if (dest->data == NULL) { + dest->w = srcw; + dest->h = srch; + dest->size = srcw * srch * 3; + dest->data = (unsigned char*) malloc (dest->size); + } + ptrdst = dest->data; + + switch (pixelformat) { + case (V4L2_PIX_FMT_RGB32): + for (ys = 0, yd = 0; ys < (signed int)srch; ys++) { + for (xs = 0, xd = 0; xs < (signed int)srcw; xs++) { + /* read the pixel */ + + ptrsrc++; + r = *(ptrsrc++); + g = *(ptrsrc++); + b = *(ptrsrc++); + + /* only paint the image if the source is within the destination */ + if (xd < dest->w) { + /* set the pixel */ + *(ptrdst++) = b; + *(ptrdst++) = g; + *(ptrdst++) = r; + xd++; + } + } + + /* if the source image is too small ignore the other places.. */ + if (xd < dest->w) + ptrdst += 3 * (dest->w - xd); + yd++; + } + break; + + case (V4L2_PIX_FMT_BGR32): + for (ys = 0, yd = 0; ys < (signed int)srch; ys++) { + for (xs = 0, xd = 0; xs < (signed int)srcw; xs++) { + /* read the pixel */ + + b = *(ptrsrc++); + g = *(ptrsrc++); + r = *(ptrsrc++); + ptrsrc++; + + /* only paint the image if the source is within the destination */ + if (xd < dest->w) { + /* set the pixel */ + *(ptrdst++) = b; + *(ptrdst++) = g; + *(ptrdst++) = r; + xd++; + } + } + + /* if the source image is too small ignore the other places.. */ + if (xd < dest->w) + ptrdst += 3 * (dest->w - xd); + yd++; + } + break; + + case (V4L2_PIX_FMT_UYVY): + for (ys = 0, yd = 0; ys < (signed int)srch; ys++) { + for (xs = 0, xd = 0; xs < (signed int)srcw; xs++) { + /* read the pixel */ + if (xs & 1) { + y1 = (unsigned char)*(ptrsrc + 1); + cr = (unsigned char)*(ptrsrc); + cb = (unsigned char)*(ptrsrc - 2); + } + else { + y1 = (unsigned char)*(ptrsrc + 1); + cr = (unsigned char)*(ptrsrc + 2); + cb = (unsigned char)*(ptrsrc); + } + + convert2rgb (y1, cr, cb, &r, &g, &b); + ptrsrc += 2; + + /* only paint the image if the source is within the destination */ + if (xd < dest->w) { + /* set the pixel */ + *(ptrdst++) = b; + *(ptrdst++) = g; + *(ptrdst++) = r; + xd++; + } + } + + /* if the source image is too small ignore the other places.. */ + if (xd < dest->w) + ptrdst += 3 * (dest->w - xd); + yd++; + } + break; + + case (V4L2_PIX_FMT_YUYV): + for (ys = 0, yd = 0; ys < (signed int)srch; ys++) { + if (yd < dest->h) { + for (xs = 0, xd = 0; xs < (signed int)srcw; xs++) { + /* read the pixel */ + if (xs & 1) { + y1 = *(ptrsrc); + cb = *(ptrsrc + 1); + cr = *(ptrsrc - 1); + } + else { + y1 = *(ptrsrc); + cb = *(ptrsrc + 3); + cr = *(ptrsrc + 1); + } + convert2rgb (y1, cr, cb, &r, &g, &b); + ptrsrc += 2; + + /* only paint the image if the source is within the destination */ + if (xd < dest->w) { + /* set the pixel */ + *(ptrdst++) = r; + *(ptrdst++) = g; + *(ptrdst++) = b; + xd++; + } + } + + /* if the source image is too small ignore the other places.. */ + if (xd < dest->w) + ptrdst += 3 * (dest->w - xd); + yd++; + } + } + break; + + case (V4L2_PIX_FMT_MJPEG): + cdata->cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = jpg_error_exit; + if (setjmp(jerr.setjmp_buffer)) { + // jpeg data and allocations will be destroyed via StopCapture. + printf ("%s:%d %s JPEG Error\n", __FILE__, __LINE__, __FUNCTION__); + return VDEV_STATUS_ERROR; + } + + jpeg_mem_src(&cdata->cinfo, ptrsrc, srcsize); + jpeg_read_header(&cdata->cinfo, TRUE); + jpeg_start_decompress(&cdata->cinfo); + + while (cdata->cinfo.output_scanline < cdata->cinfo.output_height) { + unsigned char *temp_array[] = {ptrdst + (cdata->cinfo.output_scanline) * srcw * 3}; + jpeg_read_scanlines(&cdata->cinfo, temp_array, 1); + } + + jpeg_finish_decompress(&cdata->cinfo); + + break; + default: + break; + } + + return VDEV_STATUS_OK; +}; + + + diff --git a/convert.h b/convert.h new file mode 100644 index 0000000..90800ea --- /dev/null +++ b/convert.h @@ -0,0 +1,24 @@ + +#ifndef _CONVERT_H_ +#define _CONVERT_H_ + +#include +#include +#include + +#include "video.h" +#include "videoframe.h" + +#ifndef CLEAR +#define CLEAR(x) memset (&(x), 0, sizeof (x)) +#endif + +struct { + struct jpeg_decompress_struct cinfo; +} typedef ConvertData; + +int Convert (ConvertData *cdata, VideoFrame *dest, unsigned char *ptrsrc, int srcsize, uint32_t pixelformat, int srcw, int srch); +int ConvertStart(ConvertData *cdata, uint32_t pixelformat); +int ConvertStop(ConvertData *cdata, uint32_t pixelformat); + +#endif diff --git a/detect.cc b/detect.cc index b08ed3f..7396cc5 100644 --- a/detect.cc +++ b/detect.cc @@ -76,7 +76,7 @@ void Detect::Thread() { objectX = -1; objectY = -1; - output.detmatrix = malloc (sizeof(uint32_t) * DET_MAXSHIFT * DET_MAXSHIFT); + output.detmatrix = (uint32_t*)malloc (sizeof(uint32_t) * DET_MAXSHIFT * DET_MAXSHIFT); while (running) { // check for new frame @@ -127,7 +127,8 @@ void Detect::Thread() { output.posx = objectX; output.posy = objectY; output.image = &imagegtk; - memcpy (output.detmatrix, detmatrix, sizeof(uint32_t) * DET_MAXSHIFT * DET_MAXSHIFT); + if (detmatrix != NULL) + memcpy (output.detmatrix, detmatrix, sizeof(uint32_t) * DET_MAXSHIFT * DET_MAXSHIFT); UnLockMutex(); // unlock Config diff --git a/detect.h b/detect.h index 2ce8da2..ee90f8f 100644 --- a/detect.h +++ b/detect.h @@ -29,7 +29,7 @@ enum { struct { VideoFrame *image; // detected image - uint16_t *detmatrix; + uint32_t *detmatrix; int posx; // position of the detected object int posy; // position of the detected object } typedef DetectOutput; diff --git a/video.cc b/video.cc index 387c940..de2a8f9 100644 --- a/video.cc +++ b/video.cc @@ -11,11 +11,14 @@ #include #include "gui.h" #include "video.h" +#include "videodev.h" +#include "videodev-v4l2.h" #include "filter.h" #include "detect.h" #include "configuration.h" -VideoDev videodev; +VideoDev *videodev = NULL; +GThread *videodev_thread = NULL; GtkWidget *video_da = NULL; GdkPixbuf *video_pixbuf = NULL; extern GtkBuilder *_builder_; // work around for threads @@ -36,8 +39,10 @@ gboolean videoctrl_update(gpointer data) { int max; int i; + if (videodev == NULL) return TRUE; + 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) { + 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); if (!gtk_widget_has_focus(scale)) gtk_range_set_value(GTK_RANGE(scale), value); @@ -250,24 +255,33 @@ void cb_video_btnrefreshlist (GtkWidget *widget, gpointer data) { gtk_list_store_clear(GTK_LIST_STORE(model)); - if (videodev.GetDeviceList(&devlist)) { - for (iter = devlist.begin(); iter != devlist.end(); iter++) { - gtk_list_store_insert_with_values(GTK_LIST_STORE(model), NULL, -1, - 0, iter->c_str(), - -1); - } + devlist.clear(); + + VideoDev vdef1; vdef1.GetDeviceList(&devlist); + VideoDev_V4L2 vdef2; vdef2.GetDeviceList(&devlist); + + for (iter = devlist.begin(); iter != devlist.end(); iter++) { + gtk_list_store_insert_with_values(GTK_LIST_STORE(model), NULL, -1, + 0, iter->c_str(), + -1); } }; +gpointer videodev_threadprocess_wrapper (gpointer data) { + if (videodev == NULL) return NULL; + videodev->ThreadProcess(); + + return NULL; +} + + /* * 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 *cbdevice = gtk_bin_get_child(GTK_BIN(cbox)); GtkWidget *cbres = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "cb-videores")); @@ -276,15 +290,34 @@ void cb_video_btnrec (GtkWidget *widget, gpointer data) { GtkWidget *cbfmtentry = gtk_bin_get_child(GTK_BIN(cbfmt)); 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 *grid = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vidctrl-grid")); - GtkWidget *gridchild = NULL; int i, w, h; // - // read values from GUI elements + // read values from GUI elements, and start videodev with the correct driver std::string device = gtk_entry_get_text(GTK_ENTRY(cbdevice)); + std::string driver; + std::string parameter; + + i = device.find(' '); + driver = device.substr (0, i); + device = device.substr (i+1, std::string::npos); device = device.substr (0, device.find(' ')); + if (videodev != NULL) { + // + // before we can delete this object we must stop the thread. + // + videodev->Stop(); + if (videodev_thread != NULL) g_thread_join (videodev_thread); + videodev_thread = NULL; + + delete videodev; + videodev = NULL; + } + if (driver.compare("V4L2") == 0) videodev = new VideoDev_V4L2; + else if (driver.compare("DUMMY") == 0) videodev = new VideoDev; + else videodev = new VideoDev; + std::string format = gtk_entry_get_text(GTK_ENTRY(cbfmtentry)); std::string resolution = gtk_entry_get_text(GTK_ENTRY(cbresentry)); w = h = -1; @@ -294,8 +327,33 @@ void cb_video_btnrec (GtkWidget *widget, gpointer data) { gtk_widget_set_sensitive(btnstart, false); gtk_widget_set_sensitive(btnstop, true); - videodev.Start(device, w, h, format, cb_thread_video); - videodev.GetCtrlList(&list); + videodev->SetConfig(device, w, h, format, parameter, cb_thread_video); + videodev_thread = g_thread_new(driver.c_str(), videodev_threadprocess_wrapper, NULL); + // FIXME: workaround, soltuion should be: create a timer and request all controls +}; + + +/* + * after the video is started and we get the first callback from the VideoDev thread + * we will call this function to read all ctrls and creade the grid table. + * function gezts called if: threaddata.runnin == 1 + */ +void video_refreshctrls () { + std::list list; + std::list::iterator iter; + GtkWidget *cbres = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "cb-videores")); + GtkWidget *cbresentry = gtk_bin_get_child(GTK_BIN(cbres)); + GtkWidget *cbfmt = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "cb-videofmt")); + GtkWidget *cbfmtentry = gtk_bin_get_child(GTK_BIN(cbfmt)); + GtkWidget *grid = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vidctrl-grid")); + GtkWidget *gridchild = NULL; + int i, w, h; + std::string format; + std::string resolution; + + if (videodev == NULL) return; + + videodev->GetCtrlList(&list); // // clear grid @@ -310,7 +368,7 @@ void cb_video_btnrec (GtkWidget *widget, gpointer data) { int max = 100; int value = 50; - videodev.GetCtrlMinMaxValue((*iter), &min, &max, &value); + videodev->GetCtrlMinMaxValue((*iter), &min, &max, &value); // // label @@ -346,25 +404,32 @@ void cb_video_btnrec (GtkWidget *widget, gpointer data) { // // read video information - videodev.GetVideoInfo(&w, &h, &format); + videodev->GetVideoInfo(&w, &h, &format); resolution = std::to_string(w) + "x" + std::to_string(h); gtk_entry_set_text (GTK_ENTRY(cbresentry), resolution.c_str()); gtk_entry_set_text (GTK_ENTRY(cbfmtentry), format.c_str()); }; - /* * stop recording from the videodev (will stop the running thread) + * we need to wait for the thread to complete and then we can delete this object. */ void cb_video_btnstop (GtkWidget *widget, gpointer data) { 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")); printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + if (videodev == NULL) return; - videodev.Stop(); + videodev->Stop(); gtk_widget_set_sensitive(btnstart, true); gtk_widget_set_sensitive(btnstop, false); + + if (videodev_thread != NULL) g_thread_join (videodev_thread); + videodev_thread = NULL; + + delete videodev; + videodev = NULL; }; @@ -375,7 +440,15 @@ void cb_video_btnstop (GtkWidget *widget, gpointer data) { gboolean cb_thread_video (gpointer data) { GtkWidget *btnstart; GtkWidget *btnstop; - VideoFrame *vf = (VideoFrame *) data; + VideoDevThreadData *cbdata = (VideoDevThreadData*) data; + VideoFrame *vf = NULL; + + if (cbdata != NULL) { + vf = &cbdata->vf; + if (cbdata->running == 1) video_refreshctrls(); + if (vf->w <= 0 || vf->h <= 0 || vf->data == NULL) vf = NULL; + } + if (videodev == NULL) return false; btnstop = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "btn-video-stop")); btnstart = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "btn-video-rec")); @@ -383,21 +456,24 @@ gboolean cb_thread_video (gpointer data) { if (video_da == NULL) video_da = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "video-da")); - if (vf == NULL) { - printf ("%s:%d %s something went wrong\n", __FILE__, __LINE__, __FUNCTION__); - videodev.Stop(); - if (video_pixbuf == NULL) { - video_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, false, 8, 100, 100); - } + if (video_pixbuf == NULL) { + video_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, false, 8, 100, 100); + memset (gdk_pixbuf_get_pixels(video_pixbuf), 0, 3 * gdk_pixbuf_get_height(video_pixbuf) * gdk_pixbuf_get_width(video_pixbuf)); + } + + if (cbdata == NULL) { + printf ("%s:%d %s something went wrong CBData == NULL\n", __FILE__, __LINE__, __FUNCTION__); + videodev->Stop(); memset (gdk_pixbuf_get_pixels(video_pixbuf), 0, 3 * gdk_pixbuf_get_height(video_pixbuf) * gdk_pixbuf_get_width(video_pixbuf)); gtk_widget_set_sensitive(btnstart, true); gtk_widget_set_sensitive(btnstop, false); + gdk_window_invalidate_rect(gtk_widget_get_window(video_da), NULL, true); } - else { + else if (vf != NULL) { int pix_h, pix_w; - videodev.LockMutex(); + videodev->LockMutex(); if (video_pixbuf) { pix_h = gdk_pixbuf_get_height(video_pixbuf); pix_w = gdk_pixbuf_get_width(video_pixbuf); @@ -414,9 +490,9 @@ gboolean cb_thread_video (gpointer data) { } videoframe_to_pixbuf(video_pixbuf, vf); detect.NewFrame(vf); - videodev.UnLockMutex(); + videodev->UnLockMutex(); + gdk_window_invalidate_rect(gtk_widget_get_window(video_da), NULL, true); } - gdk_window_invalidate_rect(gtk_widget_get_window(video_da), NULL, true); return false; }; @@ -426,7 +502,8 @@ gboolean cb_thread_video (gpointer data) { * set ctrl on the device */ void videoctrl_set(std::string name, int value) { - videodev.SetCtrlValue(name, value); + if (videodev == NULL) return; + videodev->SetCtrlValue(name, value); } @@ -529,11 +606,13 @@ void cb_video_pre_click (GtkWidget *widget, gpointer data) { int idx; GtkWidget *btn; + if (videodev == NULL) return; + if (pushtime > BTNDOWN_TIME && presetbtn.idx >= 0 && presetbtn.idx < BTN_PRESET_MAX) { // // save the settings // - list ctrls = videodev.GetCtrlsMinMaxValue(); + list ctrls = videodev->GetCtrlsMinMaxValue(); config.SetPresetButton(presetbtn.idx, &ctrls); // @@ -558,9 +637,9 @@ void cb_video_pre_click (GtkWidget *widget, gpointer data) { for (retry = 5; retry > 0 && again; retry--) { again = 0; for (iter = ctrls.begin(); iter != ctrls.end(); iter++) { - videodev.SetCtrlValue(iter->name, iter->value); + videodev->SetCtrlValue(iter->name, iter->value); usleep(10000); - videodev.GetCtrlMinMaxValue(iter->name, NULL, NULL, &i); + videodev->GetCtrlMinMaxValue(iter->name, NULL, NULL, &i); if (i != iter->value) again = 1; } } diff --git a/video.h b/video.h index 7812501..b89af88 100644 --- a/video.h +++ b/video.h @@ -18,128 +18,7 @@ #include "gui.h" #include "config.h" #include "videoframe.h" - - -enum { - IOMODE_READ, - IOMODE_MMAP -}; - - -enum { - VDEV_STATUS_UNKNOWN, - VDEV_STATUS_OK, - VDEV_STATUS_AGAIN, - VDEV_STATUS_ERROR -}; - -// -// jpeg error handling -// -struct jpg_error_mgr { - struct jpeg_error_mgr pub; /* "public" fields */ - - jmp_buf setjmp_buffer; /* for return to caller */ -}; -typedef struct jpg_error_mgr *jpg_error_ptr; - - -// callback status -enum { - VDEV_CBSTATUS_NOTHING = 1, - VDEV_CBSTATUS_NEWFRAME, - VDEV_CBSTATUS_ERROR -}; - - -struct { - unsigned int id; - int min; - int max; - int value; - std::string name; -} typedef VideoDevCtrl; - - -struct { - unsigned int size; - unsigned char* data; -} typedef VideoInBuffer; - - -#define VDEV_INBUFFERS 3 -class VideoDev { -private: - std::string conf_device; - std::string conf_devicename; - std::string conf_format; - int conf_width; - int conf_height; - gboolean (*callback)(gpointer data); - int running; - GMutex mutex; - GThread *thread; - - int inbuffer_idx; - VideoInBuffer inbuffer[VDEV_INBUFFERS]; - struct v4l2_cropcap cropcap; - struct v4l2_crop crop; - struct v4l2_format fmt; - VideoFrame vf; - std::list vidctrls; - - struct jpg_error_mgr jerr; - struct jpeg_decompress_struct cinfo; - - int vf_rec; - int vf_get; - int io; // IO Mode - int fd; - - int InitMMap(); - int InitUserPtr(); - int CaptureStart(); - int CaptureStop(); - int UnInit(); - - int Convert (VideoFrame *dest, unsigned char *ptrsrc, int srcsize, - uint32_t pixelformat, int srcw, int srch); - - 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(); - ~VideoDev(); - - void Thread(); - - int Start(std::string dev, int w, int h, std::string format, gboolean (*callback_func)(gpointer data)); - int Stop(); // stop video processing and stop the pthread - - void GetVideoInfo(int *w, int *h, std::string *format); - - int GetDeviceList(std::list *list); - int GetCtrlList(std::list *list); - int GetCtrlMinMaxValue(std::string name, int *min, int *max, int *value); - list GetCtrlsMinMaxValue(); - int SetCtrlValue(std::string name, int value); - int IsRunning() { return running; }; - - void PrintCaps (uint32_t caps); - void PrintFmt(struct v4l2_format *f); - - void LockMutex() { g_mutex_lock(&mutex); }; - void UnLockMutex() { g_mutex_unlock(&mutex); }; - - std::string GetDevice() { return conf_device; }; -}; - - -extern VideoDev videodev; +#include "videodev.h" extern void videoframe_to_pixbuf(GdkPixbuf* dest, VideoFrame *src); diff --git a/videodev-v4l2.cc b/videodev-v4l2.cc new file mode 100644 index 0000000..7be4ef7 --- /dev/null +++ b/videodev-v4l2.cc @@ -0,0 +1,543 @@ + +#include "convert.h" +#include "videodev-v4l2.h" + +VideoDev_V4L2::VideoDev_V4L2() { + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + io = IOMODE_MMAP; + fd = -1; + inbuffer_idx = 0; +}; + +VideoDev_V4L2::~VideoDev_V4L2() { + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + if (running > 0) CaptureStop(); + if (fd >= 0) Close (); +} + + + +/* + * try to send ioctl command as long as EINTR is valid. But abort after 2 seconds. + */ +int VideoDev_V4L2::xioctl(int fd, int request, void *arg) { + int r; + int errnoioctl; + struct timeval to1; + struct timeval to2; + float to; + + gettimeofday(&to1, NULL); + do { + r = ioctl(fd, request, arg); + errnoioctl = errno; + gettimeofday(&to2, NULL); + to = (float)(to2.tv_sec - to1.tv_sec) + ((to2.tv_usec - to1.tv_usec) / 1000000.0); + } while (r == -1 && errnoioctl == EINTR && to < 2.0); + + return r; +}; + + + +/* + * return a list of /dev/video* devices found on the system, and read out its human friendly name + * output will be a lit of: "V4L2 /dev/videoX [Name]" + */ +int VideoDev_V4L2::GetDeviceList(std::list *list) { + std::string device; + int devnum; + + if (list == NULL) return 0; + + for (devnum = 0; devnum < 255; devnum++) { + device = "/dev/video"+std::to_string(devnum); + + if (device.compare (conf_device) != 0) { + int fd; + struct v4l2_capability vcap; + + if((fd = open(device.c_str(), O_RDONLY)) == -1){ + continue; + } + + if(ioctl(fd, VIDIOC_QUERYCAP, &vcap) == -1) + strncpy ((char*)&vcap.card, "unknown", sizeof(vcap.card)); + close(fd); + device += " [" + (std::string) ((char*)vcap.card) + "]"; + } + else { + device += " [" + (std::string) conf_devicename + "]"; + } + + list->push_back("V4L2 " + device); + } + + return 1; +} + + +/* + * print out the important capabilities, which are reported by the V4L2 device + */ +void VideoDev_V4L2::PrintCaps(uint32_t caps) { + printf ("%s:%d %s Caps: %x\n", __FILE__, __LINE__, __FUNCTION__, caps); + + if (caps & V4L2_CAP_VIDEO_CAPTURE) printf (" V4L2_CAP_VIDEO_CAPTURE\n"); + if (caps & V4L2_CAP_EXT_PIX_FORMAT) printf (" V4L2_CAP_EXT_PIX_FORMAT\n"); +#ifdef V4L2_CAP_META_CAPTURE + if (caps & V4L2_CAP_META_CAPTURE) printf (" V4L2_CAP_META_CAPTURE\n"); +#endif + if (caps & V4L2_CAP_STREAMING) printf (" V4L2_CAP_STREAMING\n"); + if (caps & V4L2_CAP_DEVICE_CAPS) printf (" V4L2_CAP_DEVICE_CAPS\n"); + if (caps & V4L2_CAP_TUNER) printf (" V4L2_CAP_TUNER\n"); + if (caps & V4L2_CAP_MODULATOR) printf (" V4L2_CAP_MODULATOR\n"); + if (caps & V4L2_CAP_READWRITE) printf (" V4L2_CAP_READWRITE\n"); + if (caps & V4L2_CAP_ASYNCIO) printf (" V4L2_CAP_ASYNCIO\n"); +} + + +/* + * convert the V4L2 Format data into a text humans can read. + * the pixelformat is coded in 4 chars, maybe we need a general coding table + * convert still need the v4l2 pixelformats. + * as soon as we support GPhoto or something else we need our own pixelformat table. + */ +void VideoDev_V4L2::PrintFmt(struct v4l2_format *f) { + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + printf (" type : %u\n", f->type); + printf (" fmt.pix.width : %d\n", f->fmt.pix.width); + printf (" fmt.pix.height : %d\n", f->fmt.pix.height); + printf (" fmt.fmt.pix.pixelformat : %c%c%c%c\n", ((char*)&fmt.fmt.pix.pixelformat)[0], + ((char*)&fmt.fmt.pix.pixelformat)[1], + ((char*)&fmt.fmt.pix.pixelformat)[2], + ((char*)&fmt.fmt.pix.pixelformat)[3]); + printf (" fmt.pix.field : %d\n", f->fmt.pix.field); + printf (" fmt.pix.bytesperline : %d\n", f->fmt.pix.bytesperline); + printf (" fmt.pix.sizeimage : %d\n", f->fmt.pix.sizeimage); +} + + + +/* + * Open Device + * prepare the buffer, InitMMAP and read all controls + */ +int VideoDev_V4L2::Open() { + int i; + struct v4l2_capability vcap; + VideoDevCtrl vctl; + char txt[32]; + + printf ("%s:%d %s Device: '%s'\n", __FILE__, __LINE__, __FUNCTION__, conf_device.c_str()); + if (fd != -1) return VDEV_STATUS_ERROR; + + // + // open device and get device name and capabilities | O_NONBLOCK + if((fd = open(conf_device.c_str(), O_RDWR)) == -1){ + return VDEV_STATUS_ERROR; + } + + if(ioctl(fd, VIDIOC_QUERYCAP, &vcap) == -1) + strncpy ((char*)&vcap.card, "unknown", sizeof(vcap.card)); + conf_devicename = (char*) vcap.card; + + printf ("%s:%d %s Capabilities: %u\n", __FILE__, __LINE__, __FUNCTION__, vcap.capabilities); + PrintCaps(vcap.capabilities); + printf ("%s:%d %s Device Capabilities: %u\n", __FILE__, __LINE__, __FUNCTION__, vcap.device_caps); + PrintCaps(vcap.device_caps); + + if (!(vcap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + printf ("%s:%d %s device has no video capture capabilities\n", __FILE__, __LINE__, __FUNCTION__); + return VDEV_STATUS_ERROR; + Close(); + } + + // + // query controls + struct v4l2_queryctrl queryctrl; + uint32_t min; + + vidctrls.clear(); + memset (&queryctrl, 0, sizeof (queryctrl)); + for (i = V4L2_CID_BASE; i < V4L2_CID_DETECT_CLASS_BASE+0x1000; i++) { + queryctrl.id = i; + if (0 == ioctl (fd, VIDIOC_QUERYCTRL, &queryctrl)) { + 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); + } + } + + // + // check for cropping.. if we have it setup default + CLEAR (cropcap); + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (0 == xioctl (fd, VIDIOC_CROPCAP, &cropcap)) { + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; // reset to default + + if (-1 == xioctl (fd, VIDIOC_S_CROP, &crop)) { + printf ("%s:%d %s VIDEOC_S_CROP Errorcode: %s\n", __FILE__, __LINE__, __FUNCTION__, strerror(errno)); + } + } + + // + // prepare resolution and pixelformat + CLEAR (fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (conf_height != -1 && conf_width != -1) { // resolution + fmt.fmt.pix.width = conf_width; + fmt.fmt.pix.height = conf_height; + } + else { + fmt.fmt.pix.width = 1920; + fmt.fmt.pix.height = 1080; + } + + if (conf_format.length() > 0) { // pixelformat + if (conf_format.compare("MJPG") == 0) fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; + else if (conf_format.compare("YUYV") == 0) fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + else if (conf_format.compare("RGB4") == 0) fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; + else fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; + } + else { + // fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; + } + fmt.fmt.pix.field = V4L2_FIELD_NONE; + if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt)) { + fprintf (stderr, "%s:%d VIDIOC_S_FMT : %s\n", __FILE__, __LINE__, strerror (errno)); + return VDEV_STATUS_ERROR; + } + + // Note VIDIOC_S_FMT may change width and height. + // Buggy driver paranoia. - as written in the v4l2 api documentation + min = fmt.fmt.pix.width * 2; + if (fmt.fmt.pix.bytesperline < min) + fmt.fmt.pix.bytesperline = min; + min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; + if (fmt.fmt.pix.sizeimage < min) + fmt.fmt.pix.sizeimage = min; + conf_width = fmt.fmt.pix.width; + conf_height = fmt.fmt.pix.height; + snprintf (txt, 32, "%c%c%c%c", ((char*)&fmt.fmt.pix.pixelformat)[0], + ((char*)&fmt.fmt.pix.pixelformat)[1], + ((char*)&fmt.fmt.pix.pixelformat)[2], + ((char*)&fmt.fmt.pix.pixelformat)[3]); + conf_format = txt; + PrintFmt (&fmt); + + // init buffers + switch (io) { + case IOMODE_MMAP: + if (InitMMap() == VDEV_STATUS_ERROR) + Close(); + break; + case IOMODE_READ: + default: + break; + } + + return VDEV_STATUS_OK; +}; + + +/* + * prepare memory mapped buffers + */ +int VideoDev_V4L2::InitMMap() { + struct v4l2_requestbuffers bufreq; + struct v4l2_buffer bufinfo; + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + + int i; + + CLEAR(bufreq); + CLEAR(bufinfo); + + bufreq.count = VDEV_INBUFFERS; + bufreq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + bufreq.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &bufreq)) { + if (EINVAL == errno) { + printf("%s does not support " + "memory mappingn", conf_device.c_str()); + return VDEV_STATUS_ERROR; + } else { + printf ("%s:%d %s Error %s\n", __FILE__, __LINE__, __FUNCTION__, strerror(errno)); + return VDEV_STATUS_ERROR; + } + } + + if (bufreq.count < 1) { + printf ( "Insufficient buffer memory on %s\n", conf_device.c_str()); + return VDEV_STATUS_ERROR; + } + + for (i = 0; i < VDEV_INBUFFERS; i++) { + CLEAR(bufinfo); + bufinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + bufinfo.memory = V4L2_MEMORY_MMAP; + bufinfo.index = i; + + if(ioctl(fd, VIDIOC_QUERYBUF, &bufinfo) < 0){ + perror("VIDIOC_QUERYBUF"); + exit(1); + } + + inbuffer[i].size = bufinfo.length; + inbuffer[i].data = (unsigned char*)mmap(NULL, bufinfo.length, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, bufinfo.m.offset); + if (inbuffer[i].data == MAP_FAILED) { + printf ( "%s:%d error on mmap %s\n", __FILE__, __LINE__, strerror(errno)); + exit (1); + } + } + return VDEV_STATUS_OK; +} + + +/* + * Close Device + * Free videobuffer + */ +int VideoDev_V4L2::Close() { + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + + if (fd >= 0) { + UnInit(); + close (fd); + fd = -1; + } + + conf_device = ""; + conf_devicename = ""; + + return VDEV_STATUS_OK; +}; + + + +int VideoDev_V4L2::UnInit() { + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + + switch (io) { + case IOMODE_READ: + break; + + case IOMODE_MMAP: + for (inbuffer_idx = 0; inbuffer_idx < VDEV_INBUFFERS; inbuffer_idx++) + if (inbuffer[inbuffer_idx].data) { + if (-1 == munmap(inbuffer[inbuffer_idx].data, inbuffer[inbuffer_idx].size)){ + fprintf(stderr, "Fatal Error @ %s:%d munmap Error:%s\n", __FILE__, __LINE__, strerror(errno)); + exit(1); + } + inbuffer[inbuffer_idx].data = NULL; + inbuffer[inbuffer_idx].size = 0; + } + break; + + default: + printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__); + return VDEV_STATUS_ERROR; + } + + + return VDEV_STATUS_OK; +}; + +/***************************************************************************************************** + * VideoGrabbing + */ + + +/* + * send the start capture signal to the cam + */ +int VideoDev_V4L2::CaptureStart() { + enum v4l2_buf_type type; + + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + + switch (io) { + case IOMODE_READ: + /* Nothing to do. */ + break; + + case IOMODE_MMAP: + struct v4l2_buffer buf; + + for (inbuffer_idx = 0; inbuffer_idx < VDEV_INBUFFERS; inbuffer_idx++) { + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = inbuffer_idx; + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { + printf ( "%s:%d error on VIDIOC_QBUF %s\n", __FILE__, __LINE__, strerror(errno)); + return VDEV_STATUS_ERROR; + } + } + + inbuffer_idx = 0; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) { + printf ( "%s:%d VIDIOC_STREAMON %s\n", __FILE__, __LINE__, strerror(errno)); + return VDEV_STATUS_ERROR; + } + break; + + default: + printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__); + return VDEV_STATUS_ERROR; + } + + ConvertStart(&cdata, fmt.fmt.pix.pixelformat); + + return VDEV_STATUS_OK; +}; + + +int VideoDev_V4L2::CaptureStop() { + enum v4l2_buf_type type; + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + + ConvertStop(&cdata, fmt.fmt.pix.pixelformat); + + switch (io) { + case IOMODE_READ: + /* Nothing to do. */ + break; + case IOMODE_MMAP: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) { + fprintf(stderr, "%s:%d VIDIOC_STREAMOFF Error:%s\n", __FILE__, __LINE__, strerror(errno)); + return VDEV_STATUS_ERROR; + } + break; + default: + printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__); + return VDEV_STATUS_ERROR; + } + return VDEV_STATUS_OK; +}; + + +/* + * try to grab one frame and convert it into RGB32. + * If something goes wrong return an error code. + * Return code VDEV_STATUS_AGAIN is not an error. There was no video image ready to read. + */ +int VideoDev_V4L2::Grab(VideoFrame *vf) { + struct v4l2_buffer buf; + int len; + + if (vf == NULL) return VDEV_STATUS_ERROR; + + switch (io) { + case IOMODE_READ: + if ((len = read (fd, inbuffer[0].data, fmt.fmt.pix.sizeimage)) == -1) { + switch (errno) { + case EAGAIN: + return VDEV_STATUS_AGAIN; + case EIO: + default: + printf ("v4l2_grab IOM_READ: %s dest:%p size:%d\n", strerror (errno), vf, fmt.fmt.pix.sizeimage); + return VDEV_STATUS_ERROR; + } + } + else { + LockMutex(); + Convert(&cdata, vf, inbuffer[0].data, len, fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height); + UnLockMutex(); + } + break; + + case IOMODE_MMAP: + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { + switch (errno) { + case EAGAIN: + return VDEV_STATUS_AGAIN; + + case EIO: + printf ( "%s:%d error on VIDIOC_DQBUF EIO %s\n", __FILE__, __LINE__, strerror(errno)); + default: + printf ( "%s:%d error on VIDIOC_DQBUF %s\n", __FILE__, __LINE__, strerror(errno)); + return VDEV_STATUS_ERROR; + } + } + + if (buf.index >= 0 && buf.index < VDEV_INBUFFERS) { + LockMutex(); + Convert(&cdata, vf, inbuffer[buf.index].data, buf.bytesused, fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height); + UnLockMutex(); + } + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { + printf ( "%s:%d error on VIDIOC_QBUF %s\n", __FILE__, __LINE__, strerror(errno)); + return VDEV_STATUS_ERROR; } + + if (++inbuffer_idx >= VDEV_INBUFFERS) inbuffer_idx = 0; + break; + + default: + printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__); + return VDEV_STATUS_ERROR; + } + + return VDEV_STATUS_OK; +} + + + + + +/***************************************************************************************************** + * Controls + */ + +/* + * set video control identified by id + */ +int VideoDev_V4L2::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_V4L2::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; +}; + + diff --git a/videodev-v4l2.h b/videodev-v4l2.h new file mode 100644 index 0000000..b8392e5 --- /dev/null +++ b/videodev-v4l2.h @@ -0,0 +1,71 @@ + +#ifndef _H_VIDEODEV_V4L2_H_ +#define _H_VIDEODEV_V4L2_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +#include "convert.h" +#include "gui.h" +#include "videodev.h" + + +enum { + IOMODE_READ, + IOMODE_MMAP +}; + + + +#define VDEV_INBUFFERS 3 +class VideoDev_V4L2: public VideoDev { +private: + int io; // IO Mode + int fd; + + int inbuffer_idx; + VideoInBuffer inbuffer[VDEV_INBUFFERS]; + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + struct v4l2_format fmt; + + ConvertData cdata; + + int Grab(VideoFrame *vf); + int Open(); + int Close(); + int CaptureStart(); + int CaptureStop(); + int SetDevCtrl(unsigned int id, int value); + int GetDevCtrl(unsigned int id, int *value); + + int InitMMap(); + int UnInit(); + + void PrintCaps(uint32_t caps); + void PrintFmt (struct v4l2_format *f); + int xioctl(int fd, int request, void *arg); +public: + VideoDev_V4L2(); + ~VideoDev_V4L2(); + int GetDeviceList(std::list *list); +}; + +#endif diff --git a/videodev.cc b/videodev.cc index b9228f1..f63bec4 100644 --- a/videodev.cc +++ b/videodev.cc @@ -25,132 +25,45 @@ #include "gui.h" #include "video.h" - -#define CLEAR(x) memset (&(x), 0, sizeof (x)) - - -// -// jpeg: replacement for error_exit -// -METHODDEF(void) jpg_error_exit (j_common_ptr cinfo) { - jpg_error_ptr myerr = (jpg_error_ptr) cinfo->err; - (*cinfo->err->output_message) (cinfo); - longjmp(myerr->setjmp_buffer, 1); -} - - - -// -// clamp and convert2rgb is build on the sample of the v4l2 api documentation -// -inline unsigned char clamp (double x) { - int r = (int)x; - - if (r < 0) return 0; - else if (r > 255) return 255; - else return r; -}; - - -inline void convert2rgb (unsigned char Y1, unsigned char Cb, unsigned char Cr, - unsigned char *ER, unsigned char *EB, unsigned char *EG) { - register int y1, pb, pr; - - y1 = Y1 - 16; - pb = Cb - 128; - pr = Cr - 128; - - *ER = clamp (y1 + 1.402 * pr); - *EB = clamp (y1 - 0.344 * pb - 0.714 * pr); - *EG = clamp (y1 + 1.772 * pb); -}; - - -// -// C / C++ Wrapper for the thread function -// -gpointer _VideoDevThread (gpointer data) { - videodev.Thread (); - return NULL; -}; +#include "videoframe.h" +#include "videodev.h" VideoDev::VideoDev() { - int i; - - for (i = 0; i < VDEV_INBUFFERS; i++) { - inbuffer[i].size = 0; - inbuffer[i].data = NULL; - } + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); conf_device = ""; conf_devicename = ""; conf_format = ""; - conf_height = -1; - conf_width = -1; - vf.data = NULL; - vf.h = 0; - vf.w = 0; - vf.size = 0; + conf_parameter = ""; + conf_height = 800; + conf_width = 600; running = 0; callback = NULL; - thread = NULL; - fd = -1; - io = IOMODE_MMAP; - inbuffer_idx = 0; - CLEAR(cropcap); - CLEAR(crop); - CLEAR(fmt); g_mutex_init (&mutex); - Close(); // will reset almost everything }; +#define THREAD_WAITWARNING_TO 10000000 VideoDev::~VideoDev() { - Stop(); -} - - -// -// return list of devices in form of /dev/videoYY [Name] -// -int VideoDev::GetDeviceList(std::list *list) { - std::string device; - int devnum; - - if (list == NULL) return 0; - - list->clear(); - for (devnum = 0; devnum < 255; devnum++) { - device = "/dev/video"+std::to_string(devnum); + int to = 0; - if (device.compare (conf_device) != 0) { - int fd; - struct v4l2_capability vcap; - - if((fd = open(device.c_str(), O_RDONLY)) == -1){ - continue; - } + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); - if(ioctl(fd, VIDIOC_QUERYCAP, &vcap) == -1) - strncpy ((char*)&vcap.card, "unknown", sizeof(vcap.card)); - close(fd); - device += " [" + (std::string) ((char*)vcap.card) + "]"; - } - else { - device += " [" + (std::string) conf_devicename + "]"; + if (running) Stop(); + while (running > 0) { + if (--to <= 0) { + to = THREAD_WAITWARNING_TO; + printf ("%s:%d %s still waiting for complete\n", __FILE__, __LINE__, __FUNCTION__); } - - list->push_back(device); } - - return 1; } -// -// return a list of strings for controls -// + +/* + * fill the list with all found controls. + */ int VideoDev::GetCtrlList(std::list *list) { std::list::iterator iter; if (list == NULL) return VDEV_STATUS_ERROR; @@ -168,9 +81,10 @@ int VideoDev::GetCtrlList(std::list *list) { }; -// -// return the values for an control, on error VDEV_STATUS_UNKNOWN -// +/* + * if the control is found, fill out all the fields and return with VDEV_STATUS_OK, + * on error VDEV_STATUS_UNKNOWN. In case he control have not been found. + */ int VideoDev::GetCtrlMinMaxValue(std::string name, int *min, int *max, int *value) { std::list::iterator iter; LockMutex(); @@ -205,70 +119,26 @@ int VideoDev::SetCtrlValue(std::string name, int value) { }; - -// -// start the video, start capturing, start thread -// after return of this function we can call the Ctrl thread -// -int VideoDev::Start(std::string dev, int w, int h, std::string format, gboolean (*callback_func)(gpointer data)) { - if (running != 0 || thread != NULL) return VDEV_STATUS_ERROR; - - running = 1; +/* + * before is it possible to Start the VideoGrabbing it is mandatory to set up some values + */ +void VideoDev::SetConfig(std::string dev, int w, int h, std::string format, std::string parameter, gboolean (*callback_func)(gpointer data)) { conf_device = dev; conf_format = format; + conf_parameter = parameter; conf_width = w; conf_height = h; 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_OK; -}; - - -int VideoDev::Stop() { - if (running == 1) { - running = 0; // we can jump directly to 0 - } - - if (thread) { - g_thread_join (thread); - thread = NULL; - } - - return VDEV_STATUS_OK; }; -void VideoDev::GetVideoInfo(int *w, int *h, std::string *format) { - *format = conf_format; - *w = conf_width; - *h = conf_height; -} - - -// -// try to read a video every 0.05ms (25hz) -// running = 2 ... thread is running normaly -// running = 1 ... thread need to close wait for GTK to set running to 0 -// running = 0 ... thread stopped and all handlers can be freed -// +/* + * start the video, start capturing, start thread + * after return of this function we can call the Ctrl thread + * this function should onyl be called after everything is setup with SetConfig(..) + */ #define CYCLETIME 0.050 -void VideoDev::Thread() { +void VideoDev::ThreadProcess() { struct timeval cycle_timestamp; int lastsec = 0; float cycle_time = 0.0; @@ -278,22 +148,40 @@ void VideoDev::Thread() { printf ("%s:%d %s Enter\n", __FILE__, __LINE__, __FUNCTION__); cycle_time = get_cycletime(&cycle_timestamp); // just start counting + running = 1; + // - // read untill something bad happens.. + // open and init device buffers device + if (Open() != VDEV_STATUS_OK) { + Close(); + running = 0; + } + + // + // start capturing + if (CaptureStart() != VDEV_STATUS_OK) { + Close(); + running = 0; + } + threaddata.running = 1; + if (callback) gdk_threads_add_idle(callback, &threaddata); + threaddata.running = 2; + while (running) { - i = Grab(&vf); + i = Grab(&threaddata.vf); switch (i) { case VDEV_STATUS_OK: - if (callback) gdk_threads_add_idle(callback, &vf); + if (callback) gdk_threads_add_idle(callback, &threaddata); break; case VDEV_STATUS_AGAIN: break; - default: - running = 0; - } + default: + running = 0; + break; + } // // keep 25fps, write every second a message cycle_time = get_cycletime(&cycle_timestamp); @@ -308,661 +196,27 @@ void VideoDev::Thread() { // // stop capturing if (callback) gdk_threads_add_idle(callback, NULL); - CaptureStop(); - UnInit(); Close(); - printf ("%s:%d %s Exit\n", __FILE__, __LINE__, __FUNCTION__); -}; - - -void VideoDev::PrintCaps(uint32_t caps) { - printf ("%s:%d %s Caps: %x\n", __FILE__, __LINE__, __FUNCTION__, caps); - - if (caps & V4L2_CAP_VIDEO_CAPTURE) printf (" V4L2_CAP_VIDEO_CAPTURE\n"); - if (caps & V4L2_CAP_EXT_PIX_FORMAT) printf (" V4L2_CAP_EXT_PIX_FORMAT\n"); -#ifdef V4L2_CAP_META_CAPTURE - if (caps & V4L2_CAP_META_CAPTURE) printf (" V4L2_CAP_META_CAPTURE\n"); -#endif - if (caps & V4L2_CAP_STREAMING) printf (" V4L2_CAP_STREAMING\n"); - if (caps & V4L2_CAP_DEVICE_CAPS) printf (" V4L2_CAP_DEVICE_CAPS\n"); - if (caps & V4L2_CAP_TUNER) printf (" V4L2_CAP_TUNER\n"); - if (caps & V4L2_CAP_MODULATOR) printf (" V4L2_CAP_MODULATOR\n"); - if (caps & V4L2_CAP_READWRITE) printf (" V4L2_CAP_READWRITE\n"); - if (caps & V4L2_CAP_ASYNCIO) printf (" V4L2_CAP_ASYNCIO\n"); -} - - -void VideoDev::PrintFmt(struct v4l2_format *f) { - printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); - printf (" type : %u\n", f->type); - printf (" fmt.pix.width : %d\n", f->fmt.pix.width); - printf (" fmt.pix.height : %d\n", f->fmt.pix.height); - printf (" fmt.fmt.pix.pixelformat : %c%c%c%c\n", ((char*)&fmt.fmt.pix.pixelformat)[0], - ((char*)&fmt.fmt.pix.pixelformat)[1], - ((char*)&fmt.fmt.pix.pixelformat)[2], - ((char*)&fmt.fmt.pix.pixelformat)[3]); - printf (" fmt.pix.field : %d\n", f->fmt.pix.field); - printf (" fmt.pix.bytesperline : %d\n", f->fmt.pix.bytesperline); - printf (" fmt.pix.sizeimage : %d\n", f->fmt.pix.sizeimage); -} - - - -int VideoDev::Grab(VideoFrame *vf) { - struct v4l2_buffer buf; - int len; - - if (vf == NULL) return VDEV_STATUS_ERROR; - - switch (io) { - case IOMODE_READ: - if ((len = read (fd, inbuffer[0].data, fmt.fmt.pix.sizeimage)) == -1) { - switch (errno) { - case EAGAIN: - return VDEV_STATUS_AGAIN; - case EIO: - default: - printf ("v4l2_grab IOM_READ: %s dest:%p size:%d\n", strerror (errno), vf[vf_rec].data, fmt.fmt.pix.sizeimage); - return VDEV_STATUS_ERROR; - } - } - else { - LockMutex(); - Convert(vf, inbuffer[0].data, len, fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height); - UnLockMutex(); - } - break; - - case IOMODE_MMAP: - CLEAR(buf); - - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_MMAP; - - if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { - switch (errno) { - case EAGAIN: - return VDEV_STATUS_AGAIN; - - case EIO: - printf ( "%s:%d error on VIDIOC_DQBUF EIO %s\n", __FILE__, __LINE__, strerror(errno)); - default: - printf ( "%s:%d error on VIDIOC_DQBUF %s\n", __FILE__, __LINE__, strerror(errno)); - return VDEV_STATUS_ERROR; - } - } - - if (buf.index >= 0 && buf.index < VDEV_INBUFFERS) { - LockMutex(); - Convert(vf, inbuffer[buf.index].data, buf.bytesused, fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height); - UnLockMutex(); - } - - if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { - printf ( "%s:%d error on VIDIOC_QBUF %s\n", __FILE__, __LINE__, strerror(errno)); - return VDEV_STATUS_ERROR; } - - if (++inbuffer_idx >= VDEV_INBUFFERS) inbuffer_idx = 0; - break; - - default: - printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__); - return VDEV_STATUS_ERROR; - } - - return VDEV_STATUS_OK; -} - -/* - * Open Device - */ -int VideoDev::OpenInit() { - int i; - struct v4l2_capability vcap; - VideoDevCtrl vctl; - char txt[32]; - - printf ("%s:%d %s Device: '%s'\n", __FILE__, __LINE__, __FUNCTION__, conf_device.c_str()); - if (fd != -1) return VDEV_STATUS_ERROR; - - // - // open device and get device name and capabilities | O_NONBLOCK - if((fd = open(conf_device.c_str(), O_RDWR)) == -1){ - return VDEV_STATUS_ERROR; - } - - if(ioctl(fd, VIDIOC_QUERYCAP, &vcap) == -1) - strncpy ((char*)&vcap.card, "unknown", sizeof(vcap.card)); - conf_devicename = (char*) vcap.card; - - printf ("%s:%d %s Capabilities: %u\n", __FILE__, __LINE__, __FUNCTION__, vcap.capabilities); - PrintCaps(vcap.capabilities); - printf ("%s:%d %s Device Capabilities: %u\n", __FILE__, __LINE__, __FUNCTION__, vcap.device_caps); - PrintCaps(vcap.device_caps); - - if (!(vcap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { - printf ("%s:%d %s device has no video capture capabilities\n", __FILE__, __LINE__, __FUNCTION__); - return VDEV_STATUS_ERROR; - Close(); - } - - // - // query controls - struct v4l2_queryctrl queryctrl; - uint32_t min; - - vidctrls.clear(); - memset (&queryctrl, 0, sizeof (queryctrl)); - for (i = V4L2_CID_BASE; i < V4L2_CID_DETECT_CLASS_BASE+0x1000; i++) { - queryctrl.id = i; - if (0 == ioctl (fd, VIDIOC_QUERYCTRL, &queryctrl)) { - 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); - } - } - - // - // check for cropping.. if we have it setup default - CLEAR (cropcap); - cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - - if (0 == xioctl (fd, VIDIOC_CROPCAP, &cropcap)) { - crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - crop.c = cropcap.defrect; // reset to default - - if (-1 == xioctl (fd, VIDIOC_S_CROP, &crop)) { - printf ("%s:%d %s VIDEOC_S_CROP Errorcode: %s\n", __FILE__, __LINE__, __FUNCTION__, strerror(errno)); - } - } + printf ("%s:%d %s Exit Thread\n", __FILE__, __LINE__, __FUNCTION__); - // - // prepare resolution and pixelformat - CLEAR (fmt); - fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (conf_height != -1 && conf_width != -1) { // resolution - fmt.fmt.pix.width = conf_width; - fmt.fmt.pix.height = conf_height; - } - else { - fmt.fmt.pix.width = 1920; - fmt.fmt.pix.height = 1080; - } - - if (conf_format.length() > 0) { // pixelformat - if (conf_format.compare("MJPG") == 0) fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; - else if (conf_format.compare("YUYV") == 0) fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; - else if (conf_format.compare("RGB4") == 0) fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; - else fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; - } - else { - // fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; - fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; - } - fmt.fmt.pix.field = V4L2_FIELD_NONE; - if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt)) { - fprintf (stderr, "VIDIOC_S_FMT : %s", strerror (errno)); - return VDEV_STATUS_ERROR; - } - - // Note VIDIOC_S_FMT may change width and height. - // Buggy driver paranoia. - as written in the v4l2 api documentation - min = fmt.fmt.pix.width * 2; - if (fmt.fmt.pix.bytesperline < min) - fmt.fmt.pix.bytesperline = min; - min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; - if (fmt.fmt.pix.sizeimage < min) - fmt.fmt.pix.sizeimage = min; - conf_width = fmt.fmt.pix.width; - conf_height = fmt.fmt.pix.height; - snprintf (txt, 32, "%c%c%c%c", ((char*)&fmt.fmt.pix.pixelformat)[0], - ((char*)&fmt.fmt.pix.pixelformat)[1], - ((char*)&fmt.fmt.pix.pixelformat)[2], - ((char*)&fmt.fmt.pix.pixelformat)[3]); - conf_format = txt; - PrintFmt (&fmt); - - // init buffers - switch (io) { - case IOMODE_MMAP: - if (InitMMap() == VDEV_STATUS_ERROR) - Close(); - break; - case IOMODE_READ: - default: - break; - } - - return VDEV_STATUS_OK; + return; }; -/* - * 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() { - struct v4l2_requestbuffers bufreq; - struct v4l2_buffer bufinfo; - printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); - - int i; - - CLEAR(bufreq); - CLEAR(bufinfo); - - bufreq.count = VDEV_INBUFFERS; - bufreq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - bufreq.memory = V4L2_MEMORY_MMAP; - - if (-1 == xioctl(fd, VIDIOC_REQBUFS, &bufreq)) { - if (EINVAL == errno) { - printf("%s does not support " - "memory mappingn", conf_device.c_str()); - return VDEV_STATUS_ERROR; - } else { - printf ("%s:%d %s Error %s\n", __FILE__, __LINE__, __FUNCTION__, strerror(errno)); - return VDEV_STATUS_ERROR; - } - } - - if (bufreq.count < 1) { - printf ( "Insufficient buffer memory on %s\n", conf_device.c_str()); - return VDEV_STATUS_ERROR; - } - - for (i = 0; i < VDEV_INBUFFERS; i++) { - CLEAR(bufinfo); - bufinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - bufinfo.memory = V4L2_MEMORY_MMAP; - bufinfo.index = i; - - if(ioctl(fd, VIDIOC_QUERYBUF, &bufinfo) < 0){ - perror("VIDIOC_QUERYBUF"); - exit(1); - } - - inbuffer[i].size = bufinfo.length; - inbuffer[i].data = (unsigned char*)mmap(NULL, bufinfo.length, PROT_READ | PROT_WRITE, - MAP_SHARED, fd, bufinfo.m.offset); - if (inbuffer[i].data == MAP_FAILED) { - printf ( "%s:%d error on mmap %s\n", __FILE__, __LINE__, strerror(errno)); - exit (1); - } - } - return VDEV_STATUS_OK; -} - - -/* - * send the start capture signal to the cam - */ -int VideoDev::CaptureStart() { - enum v4l2_buf_type type; - +void VideoDev::Stop() { printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); - - switch (io) { - case IOMODE_READ: - /* Nothing to do. */ - break; - - case IOMODE_MMAP: - struct v4l2_buffer buf; - - for (inbuffer_idx = 0; inbuffer_idx < VDEV_INBUFFERS; inbuffer_idx++) { - CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_MMAP; - buf.index = inbuffer_idx; - - if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { - printf ( "%s:%d error on VIDIOC_QBUF %s\n", __FILE__, __LINE__, strerror(errno)); - return VDEV_STATUS_ERROR; - } - } - - inbuffer_idx = 0; - type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) { - printf ( "%s:%d VIDIOC_STREAMON %s\n", __FILE__, __LINE__, strerror(errno)); - return VDEV_STATUS_ERROR; - } - break; - - default: - printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__); - return VDEV_STATUS_ERROR; - } - - if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) { - jpeg_create_decompress(&cinfo); - } - - return VDEV_STATUS_OK; -}; - - -int VideoDev::CaptureStop() { - enum v4l2_buf_type type; - printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); - - if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) { - jpeg_destroy_decompress(&cinfo); - } - - switch (io) { - case IOMODE_READ: - /* Nothing to do. */ - break; - case IOMODE_MMAP: - type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) { - fprintf(stderr, "%s:%d VIDIOC_STREAMOFF Error:%s\n", __FILE__, __LINE__, strerror(errno)); - return VDEV_STATUS_ERROR; - } - break; - default: - printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__); - return VDEV_STATUS_ERROR; - } - return VDEV_STATUS_OK; -}; - - -int VideoDev::UnInit() { - printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); - - switch (io) { - case IOMODE_READ: - break; - - case IOMODE_MMAP: - for (inbuffer_idx = 0; inbuffer_idx < VDEV_INBUFFERS; inbuffer_idx++) - if (inbuffer[inbuffer_idx].data) { - if (-1 == munmap(inbuffer[inbuffer_idx].data, inbuffer[inbuffer_idx].size)){ - fprintf(stderr, "Fatal Error @ %s:%d munmap Error:%s\n", __FILE__, __LINE__, strerror(errno)); - exit(1); - } - inbuffer[inbuffer_idx].data = NULL; - inbuffer[inbuffer_idx].size = 0; - } - break; - - default: - printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__); - return VDEV_STATUS_ERROR; - } - - - return VDEV_STATUS_OK; -}; - - -/* - * Close Device - * Free videobuffer - */ -int VideoDev::Close() { - printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); - - if (fd >= 0) { - close (fd); - fd = -1; - } - - conf_device = ""; - conf_devicename = ""; - - vf_rec = -1; - vf_get = -1; - - return VDEV_STATUS_OK; -}; - - -/* - * try to send ioctl command as long as EINTR is valid. But abort after 2 seconds. - */ -int VideoDev::xioctl(int fd, int request, void *arg) { - int r; - int errnoioctl; - struct timeval to1; - struct timeval to2; - float to; - - gettimeofday(&to1, NULL); - do { - r = ioctl(fd, request, arg); - errnoioctl = errno; - gettimeofday(&to2, NULL); - to = (float)(to2.tv_sec - to1.tv_sec) + ((to2.tv_usec - to1.tv_usec) / 1000000.0); - } while (r == -1 && errnoioctl == EINTR && to < 2.0); - - return r; + if (running == 1) running = 0; + return; }; -/* - * converts the video from input type to RGB24 type - 24Bit - */ -int VideoDev::Convert (VideoFrame *dest, unsigned char *ptrsrc, int srcsize, uint32_t pixelformat, int srcw, int srch) { - int xs, ys; - int xd, yd; - unsigned char r,g,b; - unsigned char cb, cr, y1; - unsigned char *ptrdst = NULL; - - // check if there is a destination and that the destination is large to keep - // the full image - if (dest == NULL || ptrsrc == NULL) - return VDEV_STATUS_ERROR; - - if (dest->data != NULL && dest->w != srcw && dest->h != srch) { - free (dest->data); - dest->data = NULL; - } - - if (dest->data == NULL) { - dest->w = srcw; - dest->h = srch; - dest->size = srcw * srch * 3; - dest->data = (unsigned char*) malloc (dest->size); - } - ptrdst = dest->data; - - switch (pixelformat) { - case (V4L2_PIX_FMT_RGB32): - for (ys = 0, yd = 0; ys < (signed int)srch; ys++) { - for (xs = 0, xd = 0; xs < (signed int)srcw; xs++) { - /* read the pixel */ - - ptrsrc++; - r = *(ptrsrc++); - g = *(ptrsrc++); - b = *(ptrsrc++); - - /* only paint the image if the source is within the destination */ - if (xd < dest->w) { - /* set the pixel */ - *(ptrdst++) = b; - *(ptrdst++) = g; - *(ptrdst++) = r; - xd++; - } - } - - /* if the source image is too small ignore the other places.. */ - if (xd < dest->w) - ptrdst += 3 * (dest->w - xd); - yd++; - } - break; - - case (V4L2_PIX_FMT_BGR32): - for (ys = 0, yd = 0; ys < (signed int)srch; ys++) { - for (xs = 0, xd = 0; xs < (signed int)srcw; xs++) { - /* read the pixel */ - - b = *(ptrsrc++); - g = *(ptrsrc++); - r = *(ptrsrc++); - ptrsrc++; - - /* only paint the image if the source is within the destination */ - if (xd < dest->w) { - /* set the pixel */ - *(ptrdst++) = b; - *(ptrdst++) = g; - *(ptrdst++) = r; - xd++; - } - } - - /* if the source image is too small ignore the other places.. */ - if (xd < dest->w) - ptrdst += 3 * (dest->w - xd); - yd++; - } - break; - - case (V4L2_PIX_FMT_UYVY): - for (ys = 0, yd = 0; ys < (signed int)srch; ys++) { - for (xs = 0, xd = 0; xs < (signed int)srcw; xs++) { - /* read the pixel */ - if (xs & 1) { - y1 = (unsigned char)*(ptrsrc + 1); - cr = (unsigned char)*(ptrsrc); - cb = (unsigned char)*(ptrsrc - 2); - } - else { - y1 = (unsigned char)*(ptrsrc + 1); - cr = (unsigned char)*(ptrsrc + 2); - cb = (unsigned char)*(ptrsrc); - } - - convert2rgb (y1, cr, cb, &r, &g, &b); - ptrsrc += 2; - - /* only paint the image if the source is within the destination */ - if (xd < dest->w) { - /* set the pixel */ - *(ptrdst++) = b; - *(ptrdst++) = g; - *(ptrdst++) = r; - xd++; - } - } - - /* if the source image is too small ignore the other places.. */ - if (xd < dest->w) - ptrdst += 3 * (dest->w - xd); - yd++; - } - break; - - case (V4L2_PIX_FMT_YUYV): - for (ys = 0, yd = 0; ys < (signed int)srch; ys++) { - if (yd < dest->h) { - for (xs = 0, xd = 0; xs < (signed int)srcw; xs++) { - /* read the pixel */ - if (xs & 1) { - y1 = *(ptrsrc); - cb = *(ptrsrc + 1); - cr = *(ptrsrc - 1); - } - else { - y1 = *(ptrsrc); - cb = *(ptrsrc + 3); - cr = *(ptrsrc + 1); - } - convert2rgb (y1, cr, cb, &r, &g, &b); - ptrsrc += 2; - - /* only paint the image if the source is within the destination */ - if (xd < dest->w) { - /* set the pixel */ - *(ptrdst++) = r; - *(ptrdst++) = g; - *(ptrdst++) = b; - xd++; - } - } - - /* if the source image is too small ignore the other places.. */ - if (xd < dest->w) - ptrdst += 3 * (dest->w - xd); - yd++; - } - } - break; - - case (V4L2_PIX_FMT_MJPEG): - cinfo.err = jpeg_std_error(&jerr.pub); - jerr.pub.error_exit = jpg_error_exit; - if (setjmp(jerr.setjmp_buffer)) { - // jpeg data and allocations will be destroyed via StopCapture. - printf ("%s:%d %s JPEG Error\n", __FILE__, __LINE__, __FUNCTION__); - return VDEV_STATUS_ERROR; - } - - jpeg_mem_src(&cinfo, ptrsrc, srcsize); - jpeg_read_header(&cinfo, TRUE); - jpeg_start_decompress(&cinfo); - - while (cinfo.output_scanline < cinfo.output_height) { - unsigned char *temp_array[] = {ptrdst + (cinfo.output_scanline) * srcw * 3}; - jpeg_read_scanlines(&cinfo, temp_array, 1); - } - - jpeg_finish_decompress(&cinfo); - - break; - default: - break; - } - - return VDEV_STATUS_OK; -}; +void VideoDev::GetVideoInfo(int *w, int *h, std::string *format) { + *format = conf_format; + *w = conf_width; + *h = conf_height; +} list VideoDev::GetCtrlsMinMaxValue() { @@ -970,4 +224,3 @@ list VideoDev::GetCtrlsMinMaxValue() { }; - diff --git a/videodev.h b/videodev.h new file mode 100644 index 0000000..6a4e457 --- /dev/null +++ b/videodev.h @@ -0,0 +1,149 @@ + +#ifndef _VIDEODEV_H_ +#define _VIDEODEV_H_ + +#include +#include +#include +#include + +#include +#include + +#include "json.h" +#include "gui.h" +#include "config.h" +#include "video.h" +#include "videoframe.h" + +enum { + VDEV_STATUS_UNKNOWN, + VDEV_STATUS_OK, + VDEV_STATUS_AGAIN, + VDEV_STATUS_ERROR +}; + +// +// jpeg error handling +// +struct jpg_error_mgr { + struct jpeg_error_mgr pub; /* "public" fields */ + + jmp_buf setjmp_buffer; /* for return to caller */ +}; +typedef struct jpg_error_mgr *jpg_error_ptr; + + +// callback status +enum { + VDEV_CBSTATUS_NOTHING = 1, + VDEV_CBSTATUS_NEWFRAME, + VDEV_CBSTATUS_ERROR +}; + + +struct { + unsigned int id; + int min; + int max; + int value; + std::string name; +} typedef VideoDevCtrl; + + +struct { + unsigned int size; + unsigned char* data; +} typedef VideoInBuffer; + + +/* + * data which we send to the main thread + */ +struct { + int running; + VideoFrame vf; +} typedef VideoDevThreadData; + +#ifndef CLEAR +#define CLEAR(x) memset (&(x), 0, sizeof (x)) +#endif + +/* + * the run Start(..) needs to be called from a created thread process. It will not return until + * another thread called the Stop() function or an error occured. + * + * New Devices will need to rewrite the following virtual functions: + * Grab(), Open(), Close(), SetDevCtrl(), GetDevGtrl(), GetDeviceList(), GetCtrlList() + */ +class VideoDev { +private: + std::string conf_device; // device or filename to connect to + std::string conf_devicename; // human friendly name of the device + std::string conf_format; // video or image format (should every device have) + std::string conf_parameter; // can hold additional parameters + int conf_height; + int conf_width; + + int running; // 0 ... not running + // 1 ... initialized (init, first frame) + // 2 ... running + + + GMutex mutex; + gboolean (*callback)(gpointer data); + + VideoDevThreadData threaddata; + + std::list vidctrls; + + /* grabs a single frame, writes the RGB24 converted frame to the VideoFrame pointer */ + virtual int Grab(VideoFrame *vf) { return VDEV_STATUS_AGAIN; }; + + /* opens the device, will need to fill the controls as well */ + virtual int Open() { return VDEV_STATUS_OK; }; + + /* close the device */ + virtual int Close() { return VDEV_STATUS_OK; }; + + /* close the device */ + virtual int CaptureStart() { return VDEV_STATUS_OK; }; + + /* close the device */ + virtual int CaptureStop() { return VDEV_STATUS_OK; }; + + /* set the control */ + virtual int SetDevCtrl(unsigned int id, int value) { return VDEV_STATUS_OK; }; + + /* read a value from the control */ + virtual int GetDevCtrl(unsigned int id, int *value) { return VDEV_STATUS_OK; }; + + friend class VideoDev_V4L2; +public: + VideoDev(); + virtual ~VideoDev(); + + void SetConfig(std::string dev, int w, int h, std::string format, std::string parameter, gboolean (*callback_func)(gpointer data)); + void ThreadProcess(); + void Stop(); + int IsRunning() { return running; }; + void LockMutex() { g_mutex_lock(&mutex); }; + void UnLockMutex() { g_mutex_unlock(&mutex); }; + + void GetVideoInfo(int *w, int *h, std::string *format); + std::string GetDevice() { return conf_device; }; + + int GetCtrlMinMaxValue(std::string name, int *min, int *max, int *value); + int SetCtrlValue(std::string name, int value); + + /* fills the list with all found device */ + virtual int GetDeviceList(std::list *list) { return VDEV_STATUS_OK; }; + + int GetCtrlList(std::list *list); + + /* returns a list with all control and thier parameters */ + list GetCtrlsMinMaxValue(); +}; + + +#endif