#define _LARGEFILE64_SOURCE 1 #define _FILE_OFFSET_BITS 64 #include "miniwebcam.h" #include "video.h" #include "convert.h" #include #include #include #include #include #include #ifdef BUILD_WINDOWS #include #include #include #else #include #endif #include VideoDevice::VideoDevice() { debug (""); }; VideoDevice::~VideoDevice() { debug (""); }; /*********************************************************************************************************/ VideoDevice_V4L2::VideoDevice_V4L2() { debug (""); conf_height = 0; conf_width = 0; conf_videodev = ""; conf_iomode = 0; conf_videofmt = 0; fd = -1; }; VideoDevice_V4L2::~VideoDevice_V4L2() { debug (""); if (fd != 0) Stop(); }; int VideoDevice_V4L2::SetDevice(std::string newdevice, int nwidth, int nheight, uint32_t pixfmt) { int isrunning = 0; if (fd != -1) { Stop(); isrunning = 1; } conf_videodev = newdevice; conf_width = nwidth; conf_height = nheight; conf_videofmt = pixfmt; if (isrunning) { if (Start() == 0) return 0; } return 1; }; int VideoDevice_V4L2::SetIOMode(int newiomode) { int isrunning = 0; if (fd != -1) { Stop(); isrunning = 1; } conf_iomode = newiomode; if (isrunning) { if (Start() == 0) return 0; } return 1; }; int VideoDevice_V4L2::Open() { int i; struct v4l2_capability vcap; VideoDevCtrl vctl; debug ("Device: '%s'", conf_videodev.c_str()); if (fd != -1) return 0; // // open device and get device name and capabilities | O_NONBLOCK if((fd = open(conf_videodev.c_str(), O_RDWR)) == -1){ debug ("could not open device error: %s", strerror(errno)); return 0; } if(ioctl(fd, VIDIOC_QUERYCAP, &vcap) == -1) strncpy ((char*)&vcap.card, "unknown", sizeof(vcap.card)); debug ("VideoDevice_V4L2::Open Devicefile:%s Card:%s fd:%d", conf_videodev.c_str(), vcap.card, fd); debug ("Capabilities: %u", vcap.capabilities); PrintCaps(vcap.capabilities); debug ("Device Capabilities: %u", vcap.device_caps); PrintCaps(vcap.device_caps); if (!(vcap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { debug ("Error: device has no video capture capabilities"); return 0; Close(); } // // query controls struct v4l2_queryctrl queryctrl; uint32_t min; debug ("Get Controls"); 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); } } debug ("got %d controls.", vidctrls.size()); // // check for cropping.. if we have it setup default debug ("setup cropping"); 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)) { debug ("VIDEOC_S_CROP Errorcode: %s", strerror(errno)); } } // // prepare resolution and pixelformat debug ("setup video resolution and pixeltype"); CLEAR (fmt); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (conf_width != -1 && conf_height != -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; } // fixme: hardcoded video format????? fmt.fmt.pix.pixelformat = conf_videofmt; fmt.fmt.pix.field = V4L2_FIELD_NONE; if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt)) { debug ("VIDIOC_S_FMT : %s", strerror (errno)); close (fd); fd = -1; return 0; } // 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; conf_videofmt = fmt.fmt.pix.pixelformat; PrintFmt (&fmt); // init buffers switch (conf_iomode) { case IOMODE_MMAP: if (InitMMap() == 0) { debug ("could not setup MMap (memory mapping)"); Close(); } break; case IOMODE_READ: for (i = 0; i < VDEV_INBUFFERS; i++) { inbuffer[i].size = fmt.fmt.pix.sizeimage; inbuffer[i].data = (unsigned char*) malloc (fmt.fmt.pix.sizeimage); } default: break; } return 1; }; int VideoDevice_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 mapping", conf_videodev.c_str()); return 0; } else { printf ("%s:%d %s Error %s\n", __FILE__, __LINE__, __FUNCTION__, strerror(errno)); return 0; } } if (bufreq.count < 1) { printf ( "Insufficient buffer memory on %s\n", conf_videodev.c_str()); return 0; } 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 1; } int VideoDevice_V4L2::Close() { printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); if (fd >= 0) { UnInit(); close (fd); fd = -1; } return 1; }; int VideoDevice_V4L2::UnInit() { printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); switch (conf_iomode) { case IOMODE_READ: for (inbuffer_idx = 0; inbuffer_idx < VDEV_INBUFFERS; inbuffer_idx++) { if (inbuffer[inbuffer_idx].data) { free (inbuffer[inbuffer_idx].data); inbuffer[inbuffer_idx].data = NULL; inbuffer[inbuffer_idx].size = 0; } } 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 0; } return 1; }; int VideoDevice_V4L2::Start() { enum v4l2_buf_type type; debug ("V4L2"); if (Open() == 0) { debug ("VideoDevice_V4L2::Start Open Device Failed."); return 0; } switch (conf_iomode) { 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)) { debug ("error on VIDIOC_QBUF %s", strerror(errno)); return 0; } } inbuffer_idx = 0; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) { debug ("VIDIOC_STREAMON %s", strerror(errno)); return 0; } break; default: debug ("wrong IOMODE?"); return 0; } ConvertStart(&cdata, fmt.fmt.pix.pixelformat); return 1; }; int VideoDevice_V4L2::Stop() { enum v4l2_buf_type type; printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); ConvertStop(&cdata, fmt.fmt.pix.pixelformat); switch (conf_iomode) { 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 0; } break; default: printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__); return 0; } Close(); return 1; }; int VideoDevice_V4L2::GetFrame(VideoFrame *destframe) { struct v4l2_buffer buf; int len; if (destframe == NULL) return 0; switch (conf_iomode) { case IOMODE_READ: if ((len = read (fd, inbuffer[0].data, fmt.fmt.pix.sizeimage)) == -1) { switch (errno) { case EAGAIN: return -1; case EIO: default: debug ("v4l2_grab IOM_READ: %s dest:%p size:%d", strerror (errno), destframe, fmt.fmt.pix.sizeimage); return 0; } } else { Convert(&cdata, destframe, inbuffer[0].data, len, fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height); } 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 -1; case EIO: debug ("error on VIDIOC_DQBUF EIO %s", strerror(errno)); return 0; default: debug ("error on VIDIOC_DQBUF %s", strerror(errno)); return 0; } } if (buf.index >= 0 && buf.index < VDEV_INBUFFERS) { Convert(&cdata, destframe, inbuffer[buf.index].data, buf.bytesused, fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height); } if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { debug ("error on VIDIOC_QBUF %s", strerror(errno)); return 0; } if (++inbuffer_idx >= VDEV_INBUFFERS) inbuffer_idx = 0; break; default: debug ("wrong IOMODE?"); return 0; } return 1; }; int VideoDevice_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; }; int VideoDevice_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 0; } return 1; }; int VideoDevice_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 0; } *value = ctrl.value; return 1; }; void VideoDevice_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"); }; void VideoDevice_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*)&f->fmt.pix.pixelformat)[0], ((char*)&f->fmt.pix.pixelformat)[1], ((char*)&f->fmt.pix.pixelformat)[2], ((char*)&f->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); }; /*********************************************************************************************************/ VideoDevice_Dump::VideoDevice_Dump() { debug (""); filesize = 0; filepos = 0; fd = -1; w = 0; h = 0; pixformat = 0; inframe = NULL; inframe_size = 0; inframe_maxsize = 0; inframe_nexttime = 0; fixedframesize = 0; }; VideoDevice_Dump::~VideoDevice_Dump() { debug (""); Stop(); Close(); }; // // Open Device // prepare the buffer, InitMMAP and read all controls // int VideoDevice_Dump::Open() { debug ("OPEN"); if (config.vdev_device == "") return 0; VideoDevCtrl vctl; uint32_t inbuf[3]; int i; struct stat s; std::string fname = config.vdev_dumpfile; printf ("%s:%d %s file %s\n", __FILE__, __LINE__, __FUNCTION__, fname.c_str()); if (fd >= 0) close (fd); fd = -1; // // read filesize if (stat (fname.c_str(), &s) != 0) { printf ("%s:%d %s could not read stat of file '%s'. Error:%s\n", __FILE__, __LINE__, __FUNCTION__, fname.c_str(), strerror(errno)); Close(); return 0; } filesize = s.st_size; #ifdef BUILD_WINDOWS if ((fd = open(fname.c_str(), O_RDONLY | O_BINARY)) == -1) #else if ((fd = open(fname.c_str(), O_RDONLY)) == -1) #endif { printf ("%s:%d could not open file '%s' error:%s\n", __FILE__, __LINE__, fname.c_str(), strerror(errno)); return 0; } // // read header if (read (fd, inbuf, 12) != 12) { printf ("%s:%d could not read all header data.\n", __FILE__, __LINE__); close (fd); fd = -1; return 0; } i = 0; filepos = 12; conf_width = w = ntohl(inbuf[i++]); conf_height = h = ntohl(inbuf[i++]); conf_videofmt = ntohl(inbuf[i++]); vidctrls.clear(); vctl.name = "FilePosition"; vctl.id = 1; vctl.min = 0; vctl.max = 255; vctl.value = 0; vidctrls.push_back(vctl); return 1; }; // // Close Device // Free videobuffer // int VideoDevice_Dump::Close() { printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); if (fd >= 0) { close(fd); fd = -1; filesize = 0; filepos = 0; } return 1; }; // // send the start capture signal to the cam // int VideoDevice_Dump::Start() { printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); Close(); if (Open() != 1) return 0; gettimeofday(&starttv, NULL); return 1; }; int VideoDevice_Dump::Stop() { printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); if (fd >= 0) { close(fd); fd = -1; } if (inframe != NULL) { free (inframe); inframe_size = 0; inframe = NULL; } return 1; }; // // 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. // #define SIZE_FRAMEHEADER 8 #define SIZE_DUMPHEADER 12 int VideoDevice_Dump::GetFrame(VideoFrame *destframe) { struct timeval curtv; unsigned int diff = 0; std::list::iterator ctrl; if (fd == -1) return 0; // // is it time? do { gettimeofday(&curtv, NULL); diff = 1000 * (curtv.tv_sec - starttv.tv_sec) + (curtv.tv_usec - starttv.tv_usec) / 1000; if (diff < inframe_nexttime) usleep ((inframe_nexttime-diff)*1000); } while (diff < inframe_nexttime); ReadFrame(); Convert(&cdata, destframe, inframe, inframe_size, conf_videofmt, w, h); ctrl = vidctrls.begin(); if (ctrl->value == 0) { // fixed framesize -> calculate frames switch (pixformat) { case(V4L2_PIX_FMT_RGB24): case(V4L2_PIX_FMT_BGR24): fixedframesize = 3 * w * h; break; case(V4L2_PIX_FMT_RGB32): case(V4L2_PIX_FMT_BGR32): fixedframesize = 4 * w * h; break; case(V4L2_PIX_FMT_SGRBG8): fixedframesize = 1 * w * h; break; case(V4L2_PIX_FMT_SGRBG16): fixedframesize = 2 * w * h; break; default: ctrl->max = 0; ctrl->value = -1; fixedframesize = 0; break; } if (fixedframesize > 0) ctrl->max = (filesize-SIZE_DUMPHEADER) / (fixedframesize + SIZE_FRAMEHEADER); } if (ctrl->value != -1) ctrl->value++; ReadFrame(); return 1; } // // Read Frame // int VideoDevice_Dump::ReadFrame() { uint32_t inbuf[2]; if (fd < 0) return 0; // // check position, if end of file restart from beginning if (filepos == filesize) { printf ("%s:%d end of file start with first frame\n", __FILE__, __LINE__); std::list::iterator ctrl; ctrl = vidctrls.begin(); ctrl->value = 0; if (lseek(fd, 12, SEEK_SET) != 12) { printf ("%s:%d %s lseek returned: %s\n", __FILE__, __LINE__, __FUNCTION__, strerror(errno)); Close(); } else { // reset filepos and starttime filepos = 12; gettimeofday(&starttv, NULL); } } // // read frame if (read (fd, inbuf, 4*2) != 4*2) { printf ("%s:%d could not read frame header: %s\n", __FILE__, __LINE__, strerror(errno)); Close(); } filepos += (4*2); inframe_size = ntohl(inbuf[0]); inframe_nexttime = ntohl(inbuf[1]); // read header if (inframe == NULL) { inframe = (unsigned char*) malloc (inframe_size); inframe_maxsize = inframe_size; } else if (inframe_maxsize < inframe_size) { inframe = (unsigned char*) realloc (inframe, inframe_size); inframe_maxsize = inframe_size; } // allocate memory and read frame if (inframe == NULL) { Close(); printf ("%s:%d could not allocate enough memory\n", __FILE__, __LINE__); return 0; } if (read (fd, inframe, inframe_size) != inframe_size) { printf ("%s:%d could not read frame: %s\n", __FILE__, __LINE__, strerror(errno)); Close(); } filepos += inframe_size; return 1; } // // set video control identified by id // int VideoDevice_Dump::SetDevCtrl(unsigned int id, int value) { std::list::iterator ctrl; int framerest; off_t newfilepos = 0; printf ("%s:%d VideoDev_Dumpfile::SetDevCtrl Set Offset to %d id:%d fixedframesize:%d\n", __FILE__, __LINE__, value, id, fixedframesize); struct timeval curtv; if (id == 1) { ctrl = vidctrls.begin(); if (value != ctrl->value && value < ctrl->max) { filepos = SIZE_DUMPHEADER + ((off_t)value * (SIZE_FRAMEHEADER + (off_t)fixedframesize)); printf ("%s:%d filepos:%ld\n", __FILE__, __LINE__, filepos); if ((newfilepos = lseek (fd, filepos, SEEK_SET)) < 0) { printf ("%s:%d ******* lseek error:%s\n", __FILE__, __LINE__, strerror(errno)); } else { framerest = (newfilepos - SIZE_DUMPHEADER)%(SIZE_FRAMEHEADER + fixedframesize); ctrl->value = (newfilepos - SIZE_DUMPHEADER)/(SIZE_FRAMEHEADER + fixedframesize); if (ctrl->value != value || framerest != 0) { printf ("%s:%d could not set file to correct position. ctrl->value:%d value:%d Framerest:%d\n", __FILE__, __LINE__, ctrl->value, value, framerest); } else { // // read first frame ReadFrame(); gettimeofday(&curtv, NULL); starttv.tv_sec = curtv.tv_sec - (inframe_nexttime/1000); } } } // else printf ("%s:%d could not set video position (ctrl->value:%d value:%d max:%d)\n", // __FILE__, __LINE__, ctrl->value, value, ctrl->max); } return 1; }; // // get video control identified by id // int VideoDevice_Dump::GetDevCtrl(unsigned int id, int *value) { return 1; };