/*************************************************************************************** * * videodev.cc is part of SimpleSkyCam. * *****************************************************************************************/ #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() { 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 }; 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); 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(device); } return 1; } // // 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; };