├── README.md ├── grab.cpp ├── LICENSE ├── webcam.h └── webcam.cpp /README.md: -------------------------------------------------------------------------------- 1 | Minimalistic C++ wrapper around V4L2 2 | ==================================== 3 | 4 | Build the demo 5 | -------------- 6 | 7 | You need the V4L2 development package. On Debian/Ubuntu: 8 | ``` 9 | $ sudo apt install libv4l2-dev 10 | ``` 11 | 12 | Then: 13 | ``` 14 | $ g++ -std=c++11 grab.cpp webcam.cpp -ograb -lv4l2 15 | ``` 16 | 17 | Calling `./grab` acquires one image from `/dev/video0` and saves it as 18 | `frame.ppm`. 19 | 20 | 21 | -------------------------------------------------------------------------------- /grab.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "webcam.h" 5 | 6 | #define XRES 640 7 | #define YRES 480 8 | 9 | using namespace std; 10 | 11 | int main(int argc, char** argv) 12 | { 13 | 14 | Webcam webcam("/dev/video0", XRES, YRES); 15 | auto frame = webcam.frame(); 16 | 17 | ofstream image; 18 | image.open("frame.ppm"); 19 | image << "P6\n" << XRES << " " << YRES << " 255\n"; 20 | image.write((char *) frame.data, frame.size); 21 | image.close(); 22 | 23 | return 0; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (c) 2014-2020 Séverin Lemaignan 2 | (c) 2008 Hans de Goede for yuyv_to_rgb24 3 | 4 | 5 | This program is free software; you can redistribute it and/or modify it 6 | under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation; either version 2.1 of the License, or (at 8 | your option) any later version. 9 | This program is distributed in the hope that it will be useful, but WITHOUT 10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 12 | License for more details. 13 | You should have received a copy of the GNU Lesser General Public License 14 | along with this program; if not, write to the Free Software Foundation, 15 | Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA 16 | -------------------------------------------------------------------------------- /webcam.h: -------------------------------------------------------------------------------- 1 | /** Small C++ wrapper around V4L example code to access the webcam 2 | **/ 3 | 4 | #include 5 | #include // unique_ptr 6 | 7 | struct buffer { 8 | void *data; 9 | size_t size; 10 | }; 11 | 12 | struct RGBImage { 13 | unsigned char *data; // RGB888 <=> RGB24 14 | size_t width; 15 | size_t height; 16 | size_t size; // width * height * 3 17 | }; 18 | 19 | 20 | class Webcam { 21 | 22 | public: 23 | Webcam(const std::string& device = "/dev/video0", 24 | int width = 640, 25 | int height = 480); 26 | 27 | ~Webcam(); 28 | 29 | /** Captures and returns a frame from the webcam. 30 | * 31 | * The returned object contains a field 'data' with the image data in RGB888 32 | * format (ie, RGB24), as well as 'width', 'height' and 'size' (equal to 33 | * width * height * 3) 34 | * 35 | * This call blocks until a frame is available or until the provided 36 | * timeout (in seconds). 37 | * 38 | * Throws a runtime_error if the timeout is reached. 39 | */ 40 | const RGBImage& frame(int timeout = 1); 41 | 42 | private: 43 | void init_mmap(); 44 | 45 | void open_device(); 46 | void close_device(); 47 | 48 | void init_device(); 49 | void uninit_device(); 50 | 51 | void start_capturing(); 52 | void stop_capturing(); 53 | 54 | bool read_frame(); 55 | 56 | std::string device; 57 | int fd; 58 | 59 | RGBImage rgb_frame; 60 | struct buffer *buffers; 61 | unsigned int n_buffers; 62 | 63 | size_t xres, yres; 64 | size_t stride; 65 | 66 | bool force_format = true; 67 | }; 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /webcam.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | (c) 2014 Séverin Lemaignan 4 | (c) 2008 Hans de Goede for yuyv_to_rgb24 5 | 6 | This program is free software; you can redistribute it and/or modify it 7 | under the terms of the GNU Lesser General Public License as published by 8 | the Free Software Foundation; either version 2.1 of the License, or (at 9 | your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, but WITHOUT 12 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14 | License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public License 17 | along with this program; if not, write to the Free Software Foundation, 18 | Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA 19 | 20 | */ 21 | 22 | #include 23 | #include 24 | #include /* low-level i/o */ 25 | #include 26 | #include 27 | #include // strerrno 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | #include 37 | 38 | #include "webcam.h" 39 | 40 | #define CLEAR(x) memset(&(x), 0, sizeof(x)) 41 | 42 | using namespace std; 43 | 44 | static int xioctl(int fh, unsigned long int request, void *arg) 45 | { 46 | int r; 47 | 48 | do { 49 | r = ioctl(fh, request, arg); 50 | } while (-1 == r && EINTR == errno); 51 | 52 | return r; 53 | } 54 | 55 | /***** 56 | * Taken from libv4l2 (in v4l-utils) 57 | * 58 | * (C) 2008 Hans de Goede 59 | * 60 | * Released under LGPL 61 | */ 62 | #define CLIP(color) (unsigned char)(((color) > 0xFF) ? 0xff : (((color) < 0) ? 0 : (color))) 63 | 64 | static void v4lconvert_yuyv_to_rgb24(const unsigned char *src, 65 | unsigned char *dest, 66 | int width, int height, 67 | int stride) 68 | { 69 | int j; 70 | 71 | while (--height >= 0) { 72 | for (j = 0; j + 1 < width; j += 2) { 73 | int u = src[1]; 74 | int v = src[3]; 75 | int u1 = (((u - 128) << 7) + (u - 128)) >> 6; 76 | int rg = (((u - 128) << 1) + (u - 128) + 77 | ((v - 128) << 2) + ((v - 128) << 1)) >> 3; 78 | int v1 = (((v - 128) << 1) + (v - 128)) >> 1; 79 | 80 | *dest++ = CLIP(src[0] + v1); 81 | *dest++ = CLIP(src[0] - rg); 82 | *dest++ = CLIP(src[0] + u1); 83 | 84 | *dest++ = CLIP(src[2] + v1); 85 | *dest++ = CLIP(src[2] - rg); 86 | *dest++ = CLIP(src[2] + u1); 87 | src += 4; 88 | } 89 | src += stride - (width * 2); 90 | } 91 | } 92 | /*******************************************************************/ 93 | 94 | 95 | Webcam::Webcam(const string& device, int width, int height) : 96 | device(device), 97 | xres(width), 98 | yres(height) 99 | { 100 | open_device(); 101 | init_device(); 102 | // xres and yres are set to the actual resolution provided by the cam 103 | 104 | // frame stored as RGB888 (ie, RGB24) 105 | rgb_frame.width = xres; 106 | rgb_frame.height = yres; 107 | rgb_frame.size = xres * yres * 3; 108 | rgb_frame.data = (unsigned char *) malloc(rgb_frame.size * sizeof(char)); 109 | 110 | start_capturing(); 111 | } 112 | 113 | Webcam::~Webcam() 114 | { 115 | stop_capturing(); 116 | uninit_device(); 117 | close_device(); 118 | 119 | free(rgb_frame.data); 120 | } 121 | 122 | const RGBImage& Webcam::frame(int timeout) 123 | { 124 | for (;;) { 125 | fd_set fds; 126 | struct timeval tv; 127 | int r; 128 | 129 | FD_ZERO(&fds); 130 | FD_SET(fd, &fds); 131 | 132 | /* Timeout. */ 133 | tv.tv_sec = timeout; 134 | tv.tv_usec = 0; 135 | 136 | r = select(fd + 1, &fds, NULL, NULL, &tv); 137 | 138 | if (-1 == r) { 139 | if (EINTR == errno) 140 | continue; 141 | throw runtime_error("select"); 142 | } 143 | 144 | if (0 == r) { 145 | throw runtime_error(device + ": select timeout"); 146 | } 147 | if (read_frame()) { 148 | return rgb_frame; 149 | } 150 | /* EAGAIN - continue select loop. */ 151 | } 152 | 153 | } 154 | 155 | bool Webcam::read_frame() 156 | { 157 | 158 | struct v4l2_buffer buf; 159 | unsigned int i; 160 | 161 | CLEAR(buf); 162 | 163 | buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 164 | buf.memory = V4L2_MEMORY_MMAP; 165 | 166 | if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { 167 | switch (errno) { 168 | case EAGAIN: 169 | return false; 170 | 171 | case EIO: 172 | /* Could ignore EIO, see spec. */ 173 | 174 | /* fall through */ 175 | 176 | default: 177 | throw runtime_error("VIDIOC_DQBUF"); 178 | } 179 | } 180 | 181 | assert(buf.index < n_buffers); 182 | 183 | v4lconvert_yuyv_to_rgb24((unsigned char *) buffers[buf.index].data, 184 | rgb_frame.data, 185 | xres, 186 | yres, 187 | stride); 188 | 189 | if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) 190 | throw runtime_error("VIDIOC_QBUF"); 191 | 192 | return true; 193 | } 194 | 195 | void Webcam::open_device(void) 196 | { 197 | struct stat st; 198 | 199 | if (-1 == stat(device.c_str(), &st)) { 200 | throw runtime_error(device + ": cannot identify! " + to_string(errno) + ": " + strerror(errno)); 201 | } 202 | 203 | if (!S_ISCHR(st.st_mode)) { 204 | throw runtime_error(device + " is no device"); 205 | } 206 | 207 | fd = open(device.c_str(), O_RDWR /* required */ | O_NONBLOCK, 0); 208 | 209 | if (-1 == fd) { 210 | throw runtime_error(device + ": cannot open! " + to_string(errno) + ": " + strerror(errno)); 211 | } 212 | } 213 | 214 | 215 | void Webcam::init_mmap(void) 216 | { 217 | struct v4l2_requestbuffers req; 218 | 219 | CLEAR(req); 220 | 221 | req.count = 4; 222 | req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 223 | req.memory = V4L2_MEMORY_MMAP; 224 | 225 | if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { 226 | if (EINVAL == errno) { 227 | throw runtime_error(device + " does not support memory mapping"); 228 | } else { 229 | throw runtime_error("VIDIOC_REQBUFS"); 230 | } 231 | } 232 | 233 | if (req.count < 2) { 234 | throw runtime_error(string("Insufficient buffer memory on ") + device); 235 | } 236 | 237 | buffers = (buffer*) calloc(req.count, sizeof(*buffers)); 238 | 239 | if (!buffers) { 240 | throw runtime_error("Out of memory"); 241 | } 242 | 243 | for (n_buffers = 0; n_buffers < req.count; ++n_buffers) { 244 | struct v4l2_buffer buf; 245 | 246 | CLEAR(buf); 247 | 248 | buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 249 | buf.memory = V4L2_MEMORY_MMAP; 250 | buf.index = n_buffers; 251 | 252 | if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) 253 | throw runtime_error("VIDIOC_QUERYBUF"); 254 | 255 | buffers[n_buffers].size = buf.length; 256 | buffers[n_buffers].data = 257 | mmap(NULL /* start anywhere */, 258 | buf.length, 259 | PROT_READ | PROT_WRITE /* required */, 260 | MAP_SHARED /* recommended */, 261 | fd, buf.m.offset); 262 | 263 | if (MAP_FAILED == buffers[n_buffers].data) 264 | throw runtime_error("mmap"); 265 | } 266 | } 267 | 268 | void Webcam::close_device(void) 269 | { 270 | if (-1 == close(fd)) 271 | throw runtime_error("close"); 272 | 273 | fd = -1; 274 | } 275 | 276 | void Webcam::init_device(void) 277 | { 278 | struct v4l2_capability cap; 279 | struct v4l2_cropcap cropcap; 280 | struct v4l2_crop crop; 281 | struct v4l2_format fmt; 282 | unsigned int min; 283 | 284 | if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { 285 | if (EINVAL == errno) { 286 | throw runtime_error(device + " is no V4L2 device"); 287 | } else { 288 | throw runtime_error("VIDIOC_QUERYCAP"); 289 | } 290 | } 291 | 292 | if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { 293 | throw runtime_error(device + " is no video capture device"); 294 | } 295 | 296 | if (!(cap.capabilities & V4L2_CAP_STREAMING)) { 297 | throw runtime_error(device + " does not support streaming i/o"); 298 | } 299 | 300 | /* Select video input, video standard and tune here. */ 301 | 302 | 303 | CLEAR(cropcap); 304 | 305 | cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 306 | 307 | if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) { 308 | crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 309 | crop.c = cropcap.defrect; /* reset to default */ 310 | 311 | if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) { 312 | switch (errno) { 313 | case EINVAL: 314 | /* Cropping not supported. */ 315 | break; 316 | default: 317 | /* Errors ignored. */ 318 | break; 319 | } 320 | } 321 | } else { 322 | /* Errors ignored. */ 323 | } 324 | 325 | 326 | CLEAR(fmt); 327 | 328 | fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 329 | if (force_format) { 330 | fmt.fmt.pix.width = xres; 331 | fmt.fmt.pix.height = yres; 332 | fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; 333 | fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; 334 | 335 | if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) 336 | throw runtime_error("VIDIOC_S_FMT"); 337 | 338 | if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) 339 | // note that libv4l2 (look for 'v4l-utils') provides helpers 340 | // to manage conversions 341 | throw runtime_error("Webcam does not support YUYV format. Support for more format need to be added!"); 342 | 343 | /* Note VIDIOC_S_FMT may change width and height. */ 344 | xres = fmt.fmt.pix.width; 345 | yres = fmt.fmt.pix.height; 346 | 347 | stride = fmt.fmt.pix.bytesperline; 348 | 349 | 350 | } else { 351 | /* Preserve original settings as set by v4l2-ctl for example */ 352 | if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt)) 353 | throw runtime_error("VIDIOC_G_FMT"); 354 | } 355 | 356 | init_mmap(); 357 | } 358 | 359 | 360 | void Webcam::uninit_device(void) 361 | { 362 | unsigned int i; 363 | 364 | for (i = 0; i < n_buffers; ++i) 365 | if (-1 == munmap(buffers[i].data, buffers[i].size)) 366 | throw runtime_error("munmap"); 367 | 368 | free(buffers); 369 | } 370 | 371 | void Webcam::start_capturing(void) 372 | { 373 | unsigned int i; 374 | enum v4l2_buf_type type; 375 | 376 | for (i = 0; i < n_buffers; ++i) { 377 | struct v4l2_buffer buf; 378 | 379 | CLEAR(buf); 380 | buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 381 | buf.memory = V4L2_MEMORY_MMAP; 382 | buf.index = i; 383 | 384 | if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) 385 | throw runtime_error("VIDIOC_QBUF"); 386 | } 387 | type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 388 | if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) 389 | throw runtime_error("VIDIOC_STREAMON"); 390 | } 391 | 392 | void Webcam::stop_capturing(void) 393 | { 394 | enum v4l2_buf_type type; 395 | 396 | type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 397 | if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) 398 | throw runtime_error("VIDIOC_STREAMOFF"); 399 | } 400 | 401 | 402 | --------------------------------------------------------------------------------