diff --git a/ChangeLog b/ChangeLog index e69de29..a25389d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -0,0 +1,4 @@ + +2021-09-08: +- grab video from old type of cameras is working + diff --git a/gui.cc b/gui.cc index a3b9744..fca6062 100644 --- a/gui.cc +++ b/gui.cc @@ -25,11 +25,19 @@ gboolean cb_window_delete_event (GtkWidget *widget, GdkEvent *event, gpointer return FALSE; }; + + void cb_window_show (GtkWidget *widget, gpointer data) { -// GtkBuilder *builder = (GtkBuilder *) data; -// g_timeout_add(100, gui_loop, NULL); + 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__); + + gtk_widget_set_sensitive(btnstart, true); + gtk_widget_set_sensitive(btnstop, false); }; + void displayerror (std::string error) { GtkWidget *dialog; GtkWidget *window = GTK_WIDGET (gtk_builder_get_object (_builder_, "main-window")); @@ -44,3 +52,4 @@ void displayerror (std::string error) { }; + diff --git a/gui.h b/gui.h index 6a7ed5c..aed1b21 100644 --- a/gui.h +++ b/gui.h @@ -33,8 +33,12 @@ G_MODULE_EXPORT void cb_window_show (GtkWidget *widget, gpointer data); G_MODULE_EXPORT gboolean cb_window_delete_event (GtkWidget *widget, GdkEvent *event, gpointer data); -G_MODULE_EXPORT void cb_video_refreshlist (GtkWidget *widget, gpointer data); +// +// video and video devices +G_MODULE_EXPORT void cb_video_btnrefreshlist (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_video_btnrec (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_video_btnstop (GtkWidget *widget, gpointer data); // // thread handles @@ -45,5 +49,7 @@ G_MODULE_EXPORT gboolean cb_thread_video (gpointer data); #endif +extern float get_cycletime(struct timeval *t); + #endif // _GUI_H_ diff --git a/simpleskycam.ui b/simpleskycam.ui index 1234aff..f3be78e 100644 --- a/simpleskycam.ui +++ b/simpleskycam.ui @@ -23,7 +23,7 @@ True True - + True False @@ -41,10 +41,13 @@ True False vertical + 1 True False + 5 + 5 7 @@ -64,6 +67,11 @@ True False True + + + False + + False @@ -80,8 +88,8 @@ True True True - - + + False @@ -90,21 +98,44 @@ - - gtk-apply - btn-videodev-ok + + gtk-stop + btn-videodev-stop True + False True True True True + + False True + end 3 + + + gtk-media-record + btn-videodev-rec + True + True + True + True + True + + + + + False + True + end + 4 + + False diff --git a/video.cc b/video.cc index f112426..66144b4 100644 --- a/video.cc +++ b/video.cc @@ -10,8 +10,253 @@ #include "video.h" VideoDev videodev; +GtkWidget *video_da = NULL; +GdkPixbuf *video_pixbuf; -void cb_video_refreshlist (GtkWidget *widget, gpointer data) { + +// +// 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); +}; + + + +void video_convert (VideoFrame *vf, GdkPixbuf *pixbuf) { + int xs, ys; + int xd, yd; + int dst_w, dst_h; + unsigned char r,g,b; + unsigned char cb, cr, y1; + unsigned char *ptrdst = gdk_pixbuf_get_pixels(pixbuf); + unsigned char *ptrsrc = vf->data; + + /* check if there is a format and buffer for the current frame.. */ + if (vf == NULL || vf->data == NULL || ptrdst == NULL) + return; + + dst_w = gdk_pixbuf_get_width(pixbuf); + dst_h = gdk_pixbuf_get_height(pixbuf); + + switch (vf->format) { + case (V4L2_PIX_FMT_RGB32): + for (ys = 0, yd = 0; ys < (signed int)vf->h; ys++) { + for (xs = 0, xd = 0; xs < (signed int)vf->w; xs++) { + /* read the pixel */ + + ptrsrc++; + r = *(ptrsrc++); + g = *(ptrsrc++); + b = *(ptrsrc++); + + /* only paint the image if the source is within the destination */ + if (xd < dst_w) { + /* set the pixel */ + *(ptrdst++) = b; + *(ptrdst++) = g; + *(ptrdst++) = r; + xd++; + } + } + + /* if the source image is too small ignore the other places.. */ + if (xd < dst_w) + ptrdst += 3 * (dst_w - xd); + yd++; + } + break; + + case (V4L2_PIX_FMT_BGR32): + for (ys = 0, yd = 0; ys < (signed int)vf->h; ys++) { + for (xs = 0, xd = 0; xs < (signed int)vf->w; xs++) { + /* read the pixel */ + + b = *(ptrsrc++); + g = *(ptrsrc++); + r = *(ptrsrc++); + ptrsrc++; + + /* only paint the image if the source is within the destination */ + if (xd < dst_w) { + /* set the pixel */ + *(ptrdst++) = b; + *(ptrdst++) = g; + *(ptrdst++) = r; + xd++; + } + } + + /* if the source image is too small ignore the other places.. */ + if (xd < dst_w) + ptrdst += 3 * (dst_w - xd); + yd++; + } + break; + + case (V4L2_PIX_FMT_UYVY): + for (ys = 0, yd = 0; ys < (signed int)vf->h; ys++) { + for (xs = 0, xd = 0; xs < (signed int)vf->w; 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 < dst_w) { + /* set the pixel */ + *(ptrdst++) = b; + *(ptrdst++) = g; + *(ptrdst++) = r; + xd++; + } + } + + /* if the source image is too small ignore the other places.. */ + if (xd < dst_w) + ptrdst += 3 * (dst_w - xd); + yd++; + } + break; + + case (V4L2_PIX_FMT_YUYV): + for (ys = 0, yd = 0; ys < (signed int)vf->h; ys++) { + if (yd < dst_h) { + for (xs = 0, xd = 0; xs < (signed int)vf->w; 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 < dst_w) { + /* set the pixel */ + *(ptrdst++) = r; + *(ptrdst++) = g; + *(ptrdst++) = b; + xd++; + } + } + + /* if the source image is too small ignore the other places.. */ + if (xd < dst_w) + ptrdst += 3 * (dst_w - xd); + yd++; + } + } + break; + default: + break; + } +}; + + + + +void video_draw_image (VideoFrame *vf) { + int pix_w; + int pix_h; + + if (video_da == NULL) { + video_da = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "video-da")); + if (video_da == NULL) { + printf ("%s:%d could not find drawingarea.\n", __FILE__, __LINE__); + return; + } + } + + // check if the pixbuf is already allocated? + if (video_pixbuf) { + pix_h = gdk_pixbuf_get_height(video_pixbuf); + pix_w = gdk_pixbuf_get_width(video_pixbuf); + } + else pix_h = 0; + + // + // changes in resolution? + if (video_pixbuf == NULL || pix_h != vf->h || pix_w != vf->w) { + if (video_pixbuf != NULL) g_object_unref (video_pixbuf); + + printf ("%s:%d new pixbuf\n", __FILE__, __LINE__); + video_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, false, 8, vf->w, vf->h); + pix_w = vf->w; + pix_h = vf->h; + +// int channels = gdk_pixbuf_get_n_channels (video_pixbuf); +// int rowstride = gdk_pixbuf_get_rowstride (video_pixbuf); + } + + video_convert(vf, video_pixbuf); + + GdkWindow* window = gtk_widget_get_window(video_da); + cairo_t *cr = gdk_cairo_create(window); + + gdk_cairo_set_source_pixbuf(cr, video_pixbuf, 0, 0); + cairo_paint(cr); + cairo_fill (cr); + +// cairo_move_to(cr, 30, 30); +// cairo_set_font_size(cr,15); +// cairo_show_text(cr, "hello world"); + + cairo_destroy (cr); + + /* + // + // "convert" the G*t*kWidget to G*d*kWindow (no, it's not a GtkWindow!) + GdkWindow* window = gtk_widget_get_window(video_da); + cairo_region_t * cairoRegion = cairo_region_create(); + GdkDrawingContext * drawingContext = gdk_window_begin_draw_frame (window,cairoRegion); + cairo_t * cr = gdk_drawing_context_get_cairo_context (drawingContext); + + + gdk_window_end_draw_frame(window,drawingContext); + cairo_region_destroy(cairoRegion); + + */ +}; + + +// +// refresh the possible devices +void cb_video_btnrefreshlist (GtkWidget *widget, gpointer data) { GtkWidget *cbox = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "cb-videodev")); GtkListStore *model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(cbox))); @@ -34,4 +279,61 @@ void cb_video_refreshlist (GtkWidget *widget, gpointer data) { -1); } } -} +}; + + +// +// start recording from the videodev (will start a new thread) +void cb_video_btnrec (GtkWidget *widget, gpointer data) { + 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)); + + std::string device = gtk_entry_get_text(GTK_ENTRY(cbdevice)); + device = device.substr (0, device.find(' ')); + + printf ("%s:%d %s open device: '%s'\n", __FILE__, __LINE__, __FUNCTION__, device.c_str()); + gtk_widget_set_sensitive(btnstart, false); + gtk_widget_set_sensitive(btnstop, true); + + videodev.Start(device, cb_thread_video); +}; + + +// +// stop recording from the videodev (will stop the running thread) +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__); + + videodev.Stop(); + gtk_widget_set_sensitive(btnstart, true); + gtk_widget_set_sensitive(btnstop, false); +}; + + + +gboolean cb_thread_video (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")); + VideoFrame *vf; + + if (data == NULL) { + printf ("%s:%d %s something went wrong\n", __FILE__, __LINE__, __FUNCTION__); + videodev.Stop(); + gtk_widget_set_sensitive(btnstart, true); + gtk_widget_set_sensitive(btnstop, false); + } + else { + // printf ("%s:%d %s got video data\n", __FILE__, __LINE__, __FUNCTION__); + vf = videodev.FrameGet(); + if (vf != NULL) video_draw_image(vf); + videodev.FrameNext(); + } + + return false; +}; + diff --git a/video.h b/video.h index b25b6bb..a334781 100644 --- a/video.h +++ b/video.h @@ -9,32 +9,107 @@ #include #include + +#include + + #include "gui.h" #include "config.h" + +enum { + IOMODE_READ, + IOMODE_MMAP, + IOMODE_USERPTR +}; + + enum { VDEV_STATUS_UNKNOWN, VDEV_STATUS_OK, + VDEV_STATUS_AGAIN, VDEV_STATUS_ERROR }; + +// callback status +enum { + VDEV_CBSTATUS_NOTHING = 1, + VDEV_CBSTATUS_NEWFRAME, + VDEV_CBSTATUS_ERROR +}; + + +struct { + unsigned int size; + unsigned char* data; +} typedef VideoInBuffer; + + +struct { + int w; + int h; + uint32_t format; + uint32_t size; + uint32_t maxsize; + unsigned char *data; +} typedef VideoFrame; + + +#define VIDEOBUFFERS 3 class VideoDev { private: std::string conf_device; std::string conf_devicename; gboolean (*callback)(gpointer data); - int status; + int running; + GMutex mutex; + GThread *thread; + + VideoFrame vf[VIDEOBUFFERS]; + VideoInBuffer *inbuffers; + unsigned int inbuffers_cnt; + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + struct v4l2_format fmt; + + + int vf_rec; + int vf_get; + int io; // IO Mode + int fd; + + int InitMMap(); + int InitUserPtr(); + int CaptureStart(); + int CaptureStop(); + int UnInit(); + + int OpenInit (); + int Close (); + int Grab(); + int xioctl (int fd, int request, void *arg); public: VideoDev(); ~VideoDev(); + void Thread(); + int Start(std::string dev, gboolean (*callback_func)(gpointer data)); // will start a new thread int Stop(); // stop video processing and stop the pthread int GetDeviceList(std::list *list); + int IsRunning() { return running; }; - int GetStatus() { return status; }; + VideoFrame *FrameGet(); + void FrameNext(); + void PrintCaps (uint32_t caps); + void PrintFmt(struct v4l2_format *f); }; + +extern VideoDev videodev; + + #endif // _VIDEO_H_ diff --git a/videodev.cc b/videodev.cc index ee5d643..d5100b1 100644 --- a/videodev.cc +++ b/videodev.cc @@ -4,23 +4,49 @@ * *****************************************************************************************/ +#include #include #include #include #include -#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include "gui.h" #include "video.h" +#define CLEAR(x) memset (&(x), 0, sizeof (x)) + +// +// C / C++ Wrapper +gpointer _VideoDevThread (gpointer data) { + videodev.Thread (); + return NULL; +}; + + VideoDev::VideoDev() { - conf_device = ""; - conf_devicename = ""; - status = VDEV_STATUS_OK; + inbuffers = NULL; + inbuffers_cnt = 0; + running = 0; callback = NULL; + thread = NULL; + fd = -1; + io = IOMODE_MMAP; + CLEAR(cropcap); + CLEAR(crop); + CLEAR(fmt); + g_mutex_init (&mutex); + Close(); // will reset almost everything }; @@ -65,11 +91,660 @@ int VideoDev::GetDeviceList(std::list *list) { } +// +// start the video thread (on error, call cb_thread_videodev with NULL data) int VideoDev::Start(std::string dev, gboolean (*callback_func)(gpointer data)) { + if (running != 0 || thread != NULL) return VDEV_STATUS_ERROR; + + running = 1; + conf_device = dev; + callback = callback_func; + thread = g_thread_new("network thread", _VideoDevThread, NULL); + return VDEV_STATUS_ERROR; }; int VideoDev::Stop() { + if (running == 1) { + running = 0; + } + + if (thread) { + g_thread_join (thread); + thread = NULL; + } + return VDEV_STATUS_OK; +}; + + +// +// try to read a video every 0.05ms (25hz) +#define CYCLETIME 0.050 +void VideoDev::Thread() { + struct timeval cycle_timestamp; + int lastsec = 0; + float cycle_time, cycle_wait; + int i; + fd_set fds; + struct timeval tv; + + 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) { + printf ("%s:%d %s something went wrong on Open()\n", __FILE__, __LINE__, __FUNCTION__); + running = 0; + } + + // + // start capturing + CaptureStart(); + + // + // read untill something bad happens.. + while (running > 0) { + // + // read data + g_mutex_lock(&mutex); + if (vf_rec == -1) vf_rec = 0; + if (vf_rec != vf_get) { + g_mutex_unlock(&mutex); + + FD_ZERO (&fds); + FD_SET (fd, &fds); + tv.tv_sec = 2; + tv.tv_usec = 0; + i = select (fd + 1, &fds, NULL, NULL, &tv); + + /* error while select */ + if (i == -1 && EINTR == errno) // system interrupt. something went wrong + running = 0; + else if (i == 1) { + switch (Grab()) { + case VDEV_STATUS_OK: + // got valid data + g_mutex_lock(&mutex); + if (vf_get == -1) vf_get = vf_rec; + if ((++vf_rec) >= VIDEOBUFFERS) vf_rec = 0; + if (callback) gdk_threads_add_idle(callback, (void*)VDEV_CBSTATUS_NEWFRAME); + g_mutex_unlock(&mutex); + break; + + case VDEV_STATUS_AGAIN: + break; + + default: + running = 0; + } + } + } + else + g_mutex_unlock(&mutex); + + // + // keep 25fps, write every second a message + cycle_time = get_cycletime(&cycle_timestamp); + cycle_wait = (CYCLETIME - cycle_time) + cycle_wait; + if (lastsec != cycle_timestamp.tv_sec) { + printf ("%s:%d %s cycle_time:%f \r", __FILE__, __LINE__, __FUNCTION__, cycle_time); + lastsec = cycle_timestamp.tv_sec; + } + if (cycle_wait > 0.0 && cycle_wait < 1.0 ) usleep ((int)(cycle_wait * 1000000.0)); + } + + // + // stop capturing + CaptureStop(); + UnInit(); + + if (callback) gdk_threads_add_idle(callback, NULL); + 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"); + if (caps & V4L2_CAP_META_CAPTURE) printf (" V4L2_CAP_META_CAPTURE\n"); + 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() { + unsigned int i; + struct v4l2_buffer buf; + + if (vf_rec < 0 || vf_rec >= VIDEOBUFFERS) return VDEV_CBSTATUS_NOTHING; + + switch (io) { + case IOMODE_READ: + if (-1 == read (fd, vf[vf_rec].data, fmt.fmt.pix.sizeimage)) { + 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; + } + } + 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_CBSTATUS_NOTHING; + + case EIO: + /* Could ignore EIO, see spec. */ + /* fall through */ + default: + printf ( "%s:%d error on VIDIOC_DQBUF %s\n", __FILE__, __LINE__, strerror(errno)); + return VDEV_CBSTATUS_ERROR; + } + } + + if (buf.index < inbuffers_cnt) { + memcpy (vf[vf_rec].data, inbuffers[buf.index].data, buf.bytesused); + vf[vf_rec].size = buf.bytesused; + } + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { + printf ( "%s:%d error on VIDIOC_QBUF %s\n", __FILE__, __LINE__, strerror(errno)); + exit (1); + } + break; + + case IOMODE_USERPTR: + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + + if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { + switch (errno) { + case EAGAIN: + return VDEV_CBSTATUS_NOTHING; + + case EIO: + /* Could ignore EIO, see spec. */ + /* fall through */ + default: + printf ( "%s:%d error on VIDIOC_DQBUF %s\n", __FILE__, __LINE__, strerror(errno)); + exit (1); + } + } + + for (i = 0; i < inbuffers_cnt; ++i) + if (buf.m.userptr == (unsigned long)inbuffers[i].data && buf.length == inbuffers[i].size) + break; + + if (i < inbuffers_cnt) { + memcpy (vf[vf_rec].data, inbuffers[buf.index].data, buf.bytesused); + vf[vf_rec].size = buf.bytesused; + } + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { + printf ( "%s:%d error on VIDIOC_QBUF %s\n", __FILE__, __LINE__, strerror(errno)); + exit (1); + } + break; + } + + return VDEV_STATUS_OK; +} + +// +// Open Device +int VideoDev::OpenInit() { + int i; + struct v4l2_capability vcap; + + 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 + if((fd = open(conf_device.c_str(), O_RDWR | O_NONBLOCK)) == -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; + struct v4l2_control control; + uint32_t min; + + printf (" Controls: \n"); + memset (&queryctrl, 0, sizeof (queryctrl)); + for (i = V4L2_CID_BASE; i < V4L2_CID_LASTP1; 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; + } + } + printf ("\n"); + + // + // 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 (" 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; + fmt.fmt.pix.width = cropcap.defrect.width; + fmt.fmt.pix.height = cropcap.defrect.height; + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; + 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; + PrintFmt (&fmt); + + // allocate buffers for readin from other threads + for (i = 0; i < VIDEOBUFFERS; i++) { + vf[i].maxsize = fmt.fmt.pix.sizeimage; + vf[i].size = fmt.fmt.pix.sizeimage; + vf[i].format = fmt.fmt.pix.pixelformat; + vf[i].h = fmt.fmt.pix.height; + vf[i].w = fmt.fmt.pix.width; + vf[i].data = (unsigned char*) malloc (vf[i].size); + } + + // init buffers + switch (io) { + case IOMODE_MMAP: + if (InitMMap() == VDEV_STATUS_ERROR) + Close(); + break; + case IOMODE_USERPTR: + if (InitUserPtr() == VDEV_STATUS_ERROR) + Close(); + break; + case IOMODE_READ: + default: + break; + } + + return VDEV_STATUS_OK; +}; + + +// +// prepare memory mapped buffers +int VideoDev::InitMMap() { + struct v4l2_requestbuffers req; + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + + CLEAR(req); + + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { + 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 (req.count < 2) { + printf ( "Insufficient buffer memory on %s\n", conf_device.c_str()); + return VDEV_STATUS_ERROR; + } + + inbuffers = (VideoInBuffer*) calloc(req.count, sizeof(*inbuffers)); + if (!inbuffers) { + fprintf(stderr, "Out of memory\\n"); + exit(1); + } + + for (inbuffers_cnt = 0; inbuffers_cnt < req.count; ++inbuffers_cnt) { + struct v4l2_buffer buf; + + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = inbuffers_cnt; + + if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) { + printf ( "%s:%d Insufficient buffer memory on %s\n", __FILE__, __LINE__, conf_device.c_str()); + exit (1); + } + + inbuffers[inbuffers_cnt].size = buf.length; + inbuffers[inbuffers_cnt].data = (unsigned char*)mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); + + if (MAP_FAILED == inbuffers[inbuffers_cnt].data) { + printf ( "%s:%d error on mmap %s\n", __FILE__, __LINE__, strerror(errno)); + exit (1); + } + } + + return VDEV_STATUS_OK; +} + + +// +// prepare memory mapped buffers +int VideoDev::InitUserPtr() { + struct v4l2_requestbuffers req; + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + + CLEAR(req); + + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_USERPTR; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + printf("%s does not support " + "memory mappingn", conf_device.c_str()); + return VDEV_STATUS_ERROR; + } else { + printf ( "%s:%d error on UserPtr %s\n", __FILE__, __LINE__, strerror(errno)); + exit (1); + } + } + + inbuffers = (VideoInBuffer*)calloc(4, sizeof(*inbuffers)); + + if (!inbuffers) { + fprintf(stderr, "Out of memory\\n"); + exit(1); + } + + for (inbuffers_cnt = 0; inbuffers_cnt < 4; ++inbuffers_cnt) { + inbuffers[inbuffers_cnt].size = vf[0].maxsize; + inbuffers[inbuffers_cnt].data = (unsigned char*)malloc(inbuffers[inbuffers_cnt].size); + + if (!inbuffers[inbuffers_cnt].data) { + fprintf(stderr, "Out of memory\n"); + exit(1); + } + } + + return VDEV_STATUS_OK; +} + + +// +// send the start capture signal to the cam +int VideoDev::CaptureStart() { + unsigned int i; + 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: + for (i = 0; i < inbuffers_cnt; ++i) { + struct v4l2_buffer buf; + + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { + printf ( "%s:%d VIDIOC_QBUF %s\n", __FILE__, __LINE__, strerror(errno)); + return VDEV_STATUS_ERROR; + } + } + 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; + + case IOMODE_USERPTR: + for (i = 0; i < inbuffers_cnt; ++i) { + struct v4l2_buffer buf; + + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.index = i; + buf.m.userptr = (unsigned long)inbuffers[i].data; + buf.length = inbuffers[i].size; + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { + printf ( "%s:%d VIDIOC_QBUF %s\n", __FILE__, __LINE__, strerror(errno)); + return VDEV_STATUS_ERROR; + } + } + 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; + } + + return VDEV_STATUS_OK; +}; + + +int VideoDev::CaptureStop() { + 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: + case IOMODE_USERPTR: + 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; + } + return VDEV_STATUS_OK; +}; + + +int VideoDev::UnInit() { + unsigned int i; + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + + switch (io) { + case IOMODE_READ: + break; + + case IOMODE_MMAP: + for (i = 0; i < inbuffers_cnt; ++i) + if (-1 == munmap(inbuffers[i].data, inbuffers[i].size)){ + fprintf(stderr, "Fatal Error @ %s:%d munmap Error:%s\n", __FILE__, __LINE__, strerror(errno)); + exit(1); + } + break; + + case IOMODE_USERPTR: + for (i = 0; i < inbuffers_cnt; ++i) + free(inbuffers[i].data); + break; + } + + free(inbuffers); + inbuffers = NULL; + inbuffers_cnt = 0; + 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; + for (int i = 0; i < VIDEOBUFFERS; i++) { + vf[i].data = NULL; + vf[i].w = 0; + vf[i].h = 0; + vf[i].size = 0; + vf[i].format = 0; + } + + return VDEV_STATUS_OK; +}; + + +// +// get current frame, if no new frame is avaiable return NULL +VideoFrame *VideoDev::FrameGet() { + VideoFrame *res = NULL; + + g_mutex_lock(&mutex); + if (vf_get != -1) + res = &vf[vf_get]; + g_mutex_unlock(&mutex); + + return res; +}; + + +// +// release frame, and prepare next frame +void VideoDev::FrameNext() { + g_mutex_lock(&mutex); + vf_get++; + if (vf_get >= VIDEOBUFFERS) vf_get = 0; + if (vf_get == vf_rec) vf_get = -1; + g_mutex_unlock(&mutex); +}; + + + +// +// 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; +}; +