// %flair:license{ // This file is part of the Flair framework distributed under the // CECILL-C License, Version 1.0. // %flair:license} // created: 2014/07/17 // filename: V4LCamera.cpp // // author: Guillaume Sanahuja // Copyright Heudiasyc UMR UTC/CNRS 7253 // // version: $Id: $ // // purpose: base class for V4l camera // // /*********************************************************************/ #include "V4LCamera.h" #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_V4L_BUFFERS 4 using std::string; using namespace flair::core; using namespace flair::gui; namespace flair { namespace sensor { V4LCamera::V4LCamera(string name, uint8_t camera_index, uint16_t width, uint16_t height, cvimage::Type::Format format, uint8_t priority) : Thread(getFrameworkManager(), name, priority), Camera(name, width, height, format) { string deviceName="/dev/video"+std::to_string(camera_index); device = open(deviceName.c_str(), O_RDWR | O_NONBLOCK); if (device == -1) { Thread::Err("Cannot open %s\n"); } //get v4l2_format struct v4l2_format form; form.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; xioctl (device, VIDIOC_G_FMT,&form); //set width, height and format if (format == cvimage::Type::Format::UYVY) { form.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; } else if (format == cvimage::Type::Format::YUYV) { form.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; } else { Thread::Err("format not supported\n"); } form.fmt.pix.width = width; form.fmt.pix.height = height; form.fmt.win.chromakey = 0; form.fmt.win.field = V4L2_FIELD_ANY; form.fmt.win.clips = 0; form.fmt.win.clipcount = 0; form.fmt.pix.field = V4L2_FIELD_ANY; xioctl (device, VIDIOC_S_FMT, &form); /* This is just a technicality, but all buffers must be filled up before any staggered SYNC is applied. SO, filler up. (see V4L HowTo) */ AllocBuffers(); for (int bufferIndex = 0; bufferIndex < ((int)requestbuffers.count);++bufferIndex) { struct v4l2_buffer buf; memset(&buf, 0, sizeof (v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = (unsigned long)bufferIndex; if (-1 == xioctl (device, VIDIOC_QBUF, &buf)) { Thread::Err("VIDIOC_QBUF xioctl\n"); break; } } // enable the streaming v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl (device, VIDIOC_STREAMON,&type)) { Thread::Err("VIDIOC_STREAMON xioctl\n"); } // skip first frame. it is often bad -- this is unnotied in traditional apps, // but could be fatal if bad jpeg is enabled GrabFrame(); // station sol gain = new DoubleSpinBox(GetGroupBox()->NewRow(), "gain:", 0, 1, 0.1); exposure = new DoubleSpinBox(GetGroupBox()->LastRowLastCol(), "exposure:", 0, 1, 0.1); bright = new DoubleSpinBox(GetGroupBox()->LastRowLastCol(), "bright:", 0, 1, 0.1); contrast = new DoubleSpinBox(GetGroupBox()->LastRowLastCol(), "contrast:", 0, 1, 0.1); hue = new DoubleSpinBox(GetGroupBox()->LastRowLastCol(), "hue:", 0, 1, 0.1); sharpness = new DoubleSpinBox(GetGroupBox()->LastRowLastCol(), "sharpness:", 0, 1, 0.1); sat = new DoubleSpinBox(GetGroupBox()->LastRowLastCol(), "saturation:", 0, 1, 0.1); autogain = new CheckBox(GetGroupBox()->NewRow(), "autogain:"); autoexposure = new CheckBox(GetGroupBox()->LastRowLastCol(), "autoexposure:"); awb = new CheckBox(GetGroupBox()->LastRowLastCol(), "awb:"); fps = new Label(GetGroupBox()->NewRow(), "fps"); hasProblems=false; } V4LCamera::~V4LCamera() { SafeStop(); Join(); } void V4LCamera::Run(void) { Time cam_time, new_time, fpsNow, fpsPrev; char* buffer; // raw image int fpsCounter = 0; // init image old GrabFrame(); cam_time = GetTime(); fpsPrev = cam_time; while (!ToBeStopped()) { //check for ps3eye deconnection in hds uav if(hasProblems==false) { struct v4l2_format form; form.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; xioctl(device, VIDIOC_G_FMT,&form); if(xioctl (device, VIDIOC_G_FMT,&form)<0) { Thread::Warn("camera disconnected\n"); hasProblems=true; } } // fps counter fpsCounter++; if (GetTime() > (fpsPrev + 5 * (Time)1000000000)) { // toute les 5 secondes fpsNow = GetTime(); fps->SetText("fps: %.1f", fpsCounter / ((float)(fpsNow - fpsPrev) / 1000000000.)); fpsCounter = 0; fpsPrev = fpsNow; } // cam properties if (gain->ValueChanged() == true && autogain->Value() == false) SetGain(gain->Value()); if (exposure->ValueChanged() == true && autoexposure->Value() == false) SetExposure(exposure->Value()); if (bright->ValueChanged() == true) SetBrightness(bright->Value()); if (sat->ValueChanged() == true) SetSaturation(sat->Value()); if (contrast->ValueChanged() == true) SetContrast(contrast->Value()); if (hue->ValueChanged() == true) SetHue(hue->Value()); if (sharpness->ValueChanged() == true) SetProperty(V4L2_CID_SHARPNESS, sharpness->Value()); if (autogain->ValueChanged() == true) { if (autogain->Value() == true) { gain->setEnabled(false); } else { gain->setEnabled(true); SetGain(gain->Value()); } SetAutoGain(autogain->Value()); } if (autoexposure->ValueChanged() == true) { if (autoexposure->Value() == true) { exposure->setEnabled(false); } else { exposure->setEnabled(true); SetExposure(exposure->Value()); } SetAutoExposure(autoexposure->Value()); } if (awb->ValueChanged() == true) SetProperty(V4L2_CID_AUTO_WHITE_BALANCE, awb->Value()); // cam pictures buffer = RetrieveRawFrame(); GrabFrame(); new_time = GetTime(); //check for ps3eye deconnection in hds uav if(new_time-cam_time>100*1000*1000) { Thread::Warn("delta trop grand\n"); hasProblems=true; } output->GetMutex(); output->buffer = buffer; output->ReleaseMutex(); output->SetDataTime(cam_time); ProcessUpdate(output); cam_time = new_time; } close(device); } void V4LCamera::GrabFrame(void) { unsigned int count; count = 1; while (count-- > 0) { for (;;) { fd_set fds; struct timeval tv; int r; FD_ZERO (&fds); FD_SET (device, &fds); /* Timeout. */ tv.tv_sec = 2; tv.tv_usec = 0; r = select (device+1, &fds, NULL, NULL, &tv); if (-1 == r) { if (EINTR == errno) continue; Thread::Err("select\n"); } if (0 == r) { Thread::Err("select timeout\n"); /* end the infinite loop */ break; } if (read_frame_v4l2 ()) break; } } } int V4LCamera::read_frame_v4l2(void) { struct v4l2_buffer buf; memset(&buf, 0, sizeof (v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl (device, VIDIOC_DQBUF, &buf)) { switch (errno) { case EAGAIN: return 0; case EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: /* display the error and stop processing */ Thread::Err("VIDIOC_DQBUF xioctl\n"); return 1; } } if(buf.index >= requestbuffers.count) { Thread::Err("buf.index >= requestbuffers.count\n"); } #ifdef USE_TEMP_BUFFER memcpy(capture->buffers[MAX_V4L_BUFFERS].start, capture->buffers[buf.index].start, capture->buffers[MAX_V4L_BUFFERS].length ); capture->bufferIndex = MAX_V4L_BUFFERS; //printf("got data in buff %d, len=%d, flags=0x%X, seq=%d, used=%d)\n", // buf.index, buf.length, buf.flags, buf.sequence, buf.bytesused); #else bufferIndex = buf.index; #endif if (-1 == xioctl (device, VIDIOC_QBUF, &buf)) { Thread::Err ("VIDIOC_QBUF xioctl\n"); } return 1; } int V4LCamera::AllocBuffers(void) { memset(&requestbuffers, 0, sizeof (v4l2_requestbuffers)); unsigned int buffer_number = DEFAULT_V4L_BUFFERS; try_again: requestbuffers.count = buffer_number; requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; requestbuffers.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl (device, VIDIOC_REQBUFS, &requestbuffers)) { if (EINVAL == errno) { Thread::Err("not support memory mapping not supportted\n"); } else { Thread::Err ("VIDIOC_REQBUFS xioctl\n"); } return -1; } if (requestbuffers.count < buffer_number) { if (buffer_number == 1) { Thread::Err("Insufficient buffer memory\n"); return -1; } else { buffer_number--; Thread::Warn ("Insufficient buffer memory, decreasing buffers\n"); goto try_again; } } for (int n_buffers = 0; n_buffers < requestbuffers.count; ++n_buffers) { struct v4l2_buffer buf; memset(&buf, 0, sizeof (v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = n_buffers; if (-1 == xioctl (device, VIDIOC_QUERYBUF, &buf)) { Thread::Err("VIDIOC_QUERYBUF xioctl\n"); return -1; } buffers[n_buffers].length = buf.length; buffers[n_buffers].start = mmap (NULL /* start anywhere */, buf.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, device, buf.m.offset); if (MAP_FAILED == buffers[n_buffers].start) { Thread::Err("mmap\n"); return -1; } } //todo: verifier cette alloc, pas de double buffeinrg? //peut on initialiser l'image dans le constrcteur de la camera? output->buffer=output->allocFunction(output->dataType.GetSize()); return 1; }; char *V4LCamera::RetrieveRawFrame(void) { /* [FD] this really belongs here */ if (ioctl(device, VIDIOCSYNC, &mmaps[bufferIndex].frame) == -1) { Thread::Err("Could not SYNC to video stream. %s\n", strerror(errno)); } /* Now get what has already been captured as a IplImage return */ if (output->dataType.GetFormat() == cvimage::Type::Format::YUYV || output->dataType.GetFormat() == cvimage::Type::Format::UYVY) { #ifdef USE_TEMP_BUFFER capture->frame.imageData=(char*)capture->buffers[capture->bufferIndex].start; #else Printf("frame is not allocated\n"); memcpy((char *)frame,(char *)buffers[bufferIndex].start,output->GetDataType().GetSize()); #endif } else { Thread::Err("palette %d not supported for raw output\n",output->dataType.GetFormat()); } return(frame); } bool V4LCamera::HasProblems(void) { return hasProblems; } void V4LCamera::SetAutoGain(bool value) { SetProperty(V4L2_CID_AUTOGAIN, value); } void V4LCamera::SetAutoExposure(bool value) { Thread::Warn("not implemented in opencv\n"); } void V4LCamera::SetGain(float value) { SetProperty(V4L2_CID_GAIN, value); } void V4LCamera::SetExposure(float value) { SetProperty(V4L2_CID_EXPOSURE, value); } void V4LCamera::SetBrightness(float value) { SetProperty(V4L2_CID_BRIGHTNESS, value); } void V4LCamera::SetSaturation(float value) { SetProperty(V4L2_CID_SATURATION, value); } void V4LCamera::SetHue(float value) { SetProperty(V4L2_CID_HUE, value); } void V4LCamera::SetContrast(float value) { SetProperty(V4L2_CID_CONTRAST, value); } float V4LCamera::GetProperty(int property) { //get min and max value struct v4l2_queryctrl queryctrl; queryctrl.id = property; if(xioctl (device, VIDIOC_QUERYCTRL,&queryctrl)==-1) return -1; int min = queryctrl.minimum; int max = queryctrl.maximum; //set value struct v4l2_control control; memset (&control, 0, sizeof (v4l2_control)); control.id = property; if(xioctl (device,VIDIOC_G_CTRL, &control)==-1) return -1; return ((float)control.value - min + 1) / (max - min); } void V4LCamera::SetProperty(int property,float value) { //get min and max value struct v4l2_queryctrl queryctrl; queryctrl.id = property; xioctl (device, VIDIOC_QUERYCTRL,&queryctrl); int min = queryctrl.minimum; int max = queryctrl.maximum; //set value struct v4l2_control control; memset (&control, 0, sizeof (v4l2_control)); control.id = property; control.value = (int)(value * (max - min) + min); xioctl (device,VIDIOC_S_CTRL, &control); } int V4LCamera::xioctl( int fd, int request, void *arg) { int r; do r = ioctl (fd, request, arg); while (-1 == r && EINTR == errno); return r; } } // end namespace sensor } // end namespace flair