├── .gitignore ├── Makefile ├── README.md └── capturev4l2.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.jpg 3 | capturev4l2 4 | .vscode/ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: capturev4l2 2 | 3 | capturev4l2: capturev4l2.o 4 | g++ -o capturev4l2 capturev4l2.o `pkg-config --libs opencv4 libjpeg` 5 | 6 | capturev4l2.o: capturev4l2.cpp 7 | g++ -I /usr/include/opencv4 -c capturev4l2.cpp -fpermissive 8 | 9 | clean: 10 | rm -rf capturev4l2.o capturev4l2 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CaptureV4L2 2 | 3 | This program captures an image from the Linux video device (/dev/video0), and converts it to the OpenCV Mat format, then displays and saves the image. 4 | 5 | ``` 6 | $ sudo apt install libopencv-dev 7 | $ git clone https://github.com.wuhanstudio/capturev4l2 && cd capturev4l2 8 | $ make 9 | $ ./capturev4l2 -v /dev/video0 10 | ``` 11 | 12 | Check out all supported pixel formats before running this program: 13 | 14 | ``` 15 | $ v4l2-ctl --list-formats -d /dev/video0 16 | ioctl: VIDIOC_ENUM_FMT 17 | Type: Video Capture 18 | 19 | [0]: 'MJPG' (Motion-JPEG, compressed) 20 | [1]: 'YUYV' (YUYV 4:2:2) 21 | ``` 22 | 23 | All available options: 24 | 25 | ``` 26 | $ ./capturev4l2 -h 27 | Usage: ./capturev4l2 [options] 28 | Available options are 29 | -f Select frame format 30 | 0 = V4L2_PIX_FMT_YUYV 31 | 1 = V4L2_PIX_FMT_MJPEG 32 | 2 = V4L2_PIX_FMT_RGB24 33 | -r Select frame resolution: 34 | 0 = 360p, VGA (640x360) 35 | 1 = 720p, (1280x720) 36 | -v device V4L2 Video Capture device 37 | -h Print this help screen and exit 38 | ``` 39 | -------------------------------------------------------------------------------- /capturev4l2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #define IMAGE_WIDTH_720P 1280 24 | #define IMAGE_HEIGHT_720P 720 25 | 26 | #define IMAGE_WIDTH_360P 640 27 | #define IMAGE_HEIGHT_360P 360 28 | 29 | int image_width = IMAGE_WIDTH_360P; 30 | int image_height = IMAGE_HEIGHT_360P; 31 | 32 | uint8_t *buffer; 33 | 34 | /** 35 | * converts a YUYV raw buffer to a JPEG buffer. 36 | * input is in YUYV (YUV 422). output is JPEG binary. 37 | * from https://linuxtv.org/downloads/v4l-dvb-apis/V4L2-PIX-FMT-YUYV.html: 38 | * Each four bytes is two pixels. 39 | * Each four bytes is two Y's, a Cb and a Cr. 40 | * Each Y goes to one of the pixels, and the Cb and Cr belong to both pixels. 41 | * 42 | * inspired by: http://stackoverflow.com/questions/17029136/weird-image-while-trying-to-compress-yuv-image-to-jpeg-using-libjpeg 43 | */ 44 | uint64_t YUYVtoJPEG(const uint8_t* input, const int width, const int height, uint8_t* &outbuffer) { 45 | struct jpeg_compress_struct cinfo; 46 | struct jpeg_error_mgr jerr; 47 | JSAMPROW row_ptr[1]; 48 | int row_stride; 49 | 50 | // uint8_t* outbuffer = NULL; 51 | uint64_t outlen = 0; 52 | 53 | cinfo.err = jpeg_std_error(&jerr); 54 | jpeg_create_compress(&cinfo); 55 | jpeg_mem_dest(&cinfo, &outbuffer, &outlen); 56 | 57 | // jrow is a libjpeg row of samples array of 1 row pointer 58 | cinfo.image_width = width & -1; 59 | cinfo.image_height = height & -1; 60 | cinfo.input_components = 3; 61 | cinfo.in_color_space = JCS_YCbCr; //libJPEG expects YUV 3bytes, 24bit, YUYV --> YUV YUV YUV 62 | 63 | jpeg_set_defaults(&cinfo); 64 | jpeg_set_quality(&cinfo, 92, TRUE); 65 | jpeg_start_compress(&cinfo, TRUE); 66 | 67 | std::vector tmprowbuf(width * 3); 68 | 69 | JSAMPROW row_pointer[1]; 70 | row_pointer[0] = &tmprowbuf[0]; 71 | while (cinfo.next_scanline < cinfo.image_height) { 72 | unsigned i, j; 73 | unsigned offset = cinfo.next_scanline * cinfo.image_width * 2; //offset to the correct row 74 | for (i = 0, j = 0; i < cinfo.image_width * 2; i += 4, j += 6) { //input strides by 4 bytes, output strides by 6 (2 pixels) 75 | tmprowbuf[j + 0] = input[offset + i + 0]; // Y (unique to this pixel) 76 | tmprowbuf[j + 1] = input[offset + i + 1]; // U (shared between pixels) 77 | tmprowbuf[j + 2] = input[offset + i + 3]; // V (shared between pixels) 78 | tmprowbuf[j + 3] = input[offset + i + 2]; // Y (unique to this pixel) 79 | tmprowbuf[j + 4] = input[offset + i + 1]; // U (shared between pixels) 80 | tmprowbuf[j + 5] = input[offset + i + 3]; // V (shared between pixels) 81 | } 82 | jpeg_write_scanlines(&cinfo, row_pointer, 1); 83 | } 84 | 85 | jpeg_finish_compress(&cinfo); 86 | jpeg_destroy_compress(&cinfo); 87 | 88 | return outlen; 89 | } 90 | 91 | uint64_t YV12toJPEG(const uint8_t* input, const int width, const int height, uint8_t* &outbuffer) { 92 | struct jpeg_compress_struct cinfo; 93 | struct jpeg_error_mgr jerr; 94 | JSAMPROW row_ptr[1]; 95 | int row_stride; 96 | 97 | // uint8_t* outbuffer = NULL; 98 | uint64_t outlen = 0; 99 | 100 | cinfo.err = jpeg_std_error(&jerr); 101 | jpeg_create_compress(&cinfo); 102 | jpeg_mem_dest(&cinfo, &outbuffer, &outlen); 103 | 104 | // jrow is a libjpeg row of samples array of 1 row pointer 105 | cinfo.image_width = width & -1; 106 | cinfo.image_height = height & -1; 107 | cinfo.input_components = 3; 108 | cinfo.in_color_space = JCS_YCbCr; //libJPEG expects YUV 3bytes, 24bit, YUYV --> YUV YUV YUV 109 | 110 | jpeg_set_defaults(&cinfo); 111 | jpeg_set_quality(&cinfo, 92, TRUE); 112 | jpeg_start_compress(&cinfo, TRUE); 113 | 114 | std::vector tmprowbuf(width * 3); 115 | 116 | JSAMPROW row_pointer[1]; 117 | row_pointer[0] = &tmprowbuf[0]; 118 | while (cinfo.next_scanline < cinfo.image_height) { 119 | unsigned i, j; 120 | for (i = 0, j = 0; i < cinfo.image_width; i += 1, j += 3) { //input strides by 4 bytes, output strides by 6 (2 pixels) 121 | tmprowbuf[j + 0] = input[i + cinfo.image_width * cinfo.next_scanline]; // Y (unique to this pixel) 122 | tmprowbuf[j + 1] = input[i / 2 + cinfo.image_width * cinfo.image_height + (cinfo.image_width / 2) * (cinfo.next_scanline / 2)]; // U (shared between pixels) 123 | tmprowbuf[j + 2] = input[i / 2 + cinfo.image_width * cinfo.image_height + (cinfo.image_width / 2) * (cinfo.image_height / 2) + (cinfo.image_width / 2) * (cinfo.next_scanline / 2)]; // V (shared between pixels) 124 | } 125 | jpeg_write_scanlines(&cinfo, row_pointer, 1); 126 | } 127 | 128 | jpeg_finish_compress(&cinfo); 129 | jpeg_destroy_compress(&cinfo); 130 | 131 | return outlen; 132 | } 133 | 134 | static int xioctl(int fd, int request, void *arg) 135 | { 136 | int r; 137 | 138 | do 139 | { 140 | r = ioctl(fd, request, arg); 141 | } 142 | while (-1 == r && EINTR == errno); 143 | 144 | return r; 145 | } 146 | 147 | int print_caps(int fd, int pixel_format) 148 | { 149 | struct v4l2_capability caps = {}; 150 | if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &caps)) 151 | { 152 | perror("Querying Capabilities"); 153 | return 1; 154 | } 155 | 156 | printf( "Driver Caps:\n" 157 | " Driver: \"%s\"\n" 158 | " Card: \"%s\"\n" 159 | " Bus: \"%s\"\n" 160 | " Version: %d.%d\n" 161 | " Capabilities: %08x\n", 162 | caps.driver, 163 | caps.card, 164 | caps.bus_info, 165 | (caps.version>>16)&&0xff, 166 | (caps.version>>24)&&0xff, 167 | caps.capabilities); 168 | 169 | int support_grbg10 = 0; 170 | 171 | struct v4l2_fmtdesc fmtdesc = {0}; 172 | fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 173 | char fourcc[5] = {0}; 174 | char c, e; 175 | printf(" FMT : CE Desc\n--------------------\n"); 176 | while (0 == xioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) 177 | { 178 | strncpy(fourcc, (char *)&fmtdesc.pixelformat, 4); 179 | if (fmtdesc.pixelformat == V4L2_PIX_FMT_SGRBG10) 180 | support_grbg10 = 1; 181 | c = fmtdesc.flags & 1? 'C' : ' '; 182 | e = fmtdesc.flags & 2? 'E' : ' '; 183 | printf(" %s: %c%c %s\n", fourcc, c, e, fmtdesc.description); 184 | fmtdesc.index++; 185 | } 186 | 187 | struct v4l2_format fmt = {0}; 188 | fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 189 | fmt.fmt.pix.width = image_width; 190 | fmt.fmt.pix.height = image_height; 191 | 192 | if(pixel_format == 0) fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; 193 | if(pixel_format == 1) fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; 194 | if(pixel_format == 2) fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; 195 | 196 | fmt.fmt.pix.field = V4L2_FIELD_NONE; 197 | 198 | if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) 199 | { 200 | perror("Setting Pixel Format"); 201 | return 1; 202 | } 203 | 204 | strncpy(fourcc, (char *)&fmt.fmt.pix.pixelformat, 4); 205 | printf( "Selected Camera Mode:\n" 206 | " Width: %d\n" 207 | " Height: %d\n" 208 | " PixFmt: %s\n" 209 | " Field: %d\n", 210 | fmt.fmt.pix.width, 211 | fmt.fmt.pix.height, 212 | fourcc, 213 | fmt.fmt.pix.field); 214 | return 0; 215 | } 216 | 217 | int init_mmap(int fd) 218 | { 219 | struct v4l2_requestbuffers req = {0}; 220 | req.count = 1; 221 | req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 222 | req.memory = V4L2_MEMORY_MMAP; 223 | 224 | if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) 225 | { 226 | perror("Requesting Buffer"); 227 | return 1; 228 | } 229 | 230 | struct v4l2_buffer buf = {0}; 231 | buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 232 | buf.memory = V4L2_MEMORY_MMAP; 233 | buf.index = 0; 234 | if(-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) 235 | { 236 | perror("Querying Buffer"); 237 | return 1; 238 | } 239 | 240 | buffer = mmap (NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); 241 | printf("Length: %d\nAddress: %p\n", buf.length, buffer); 242 | // printf("Image Length: %d\n", buf.bytesused); 243 | 244 | return 0; 245 | } 246 | 247 | int capture_image(int fd, int format) 248 | { 249 | struct v4l2_buffer buf = {0}; 250 | buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 251 | buf.memory = V4L2_MEMORY_MMAP; 252 | buf.index = 0; 253 | 254 | if(-1 == xioctl(fd, VIDIOC_QBUF, &buf)) 255 | { 256 | perror("Query Buffer"); 257 | return 1; 258 | } 259 | 260 | if(-1 == xioctl(fd, VIDIOC_STREAMON, &buf.type)) 261 | { 262 | perror("Start Capture"); 263 | return 1; 264 | } 265 | 266 | fd_set fds; 267 | FD_ZERO(&fds); 268 | FD_SET(fd, &fds); 269 | struct timeval tv = {0}; 270 | tv.tv_sec = 2; 271 | 272 | int r = select(fd+1, &fds, NULL, NULL, &tv); 273 | if(-1 == r) 274 | { 275 | perror("Waiting for Frame"); 276 | return 1; 277 | } 278 | 279 | if(-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) 280 | { 281 | perror("Retrieving Frame"); 282 | return 1; 283 | } 284 | 285 | cv::Mat cv_img; 286 | 287 | if (format == 0 ){ 288 | // Decode YUYV 289 | cv::Mat img = cv::Mat(cv::Size(image_width, image_height), CV_8UC2, buffer); 290 | cv::cvtColor(img, cv_img, cv::COLOR_YUV2RGB_YVYU); 291 | } 292 | 293 | if (format == 1) { 294 | // Decode MJPEG 295 | cv::_InputArray pic_arr(buffer, image_width * image_height * 3); 296 | cv_img = cv::imdecode(pic_arr, cv::IMREAD_UNCHANGED); 297 | } 298 | 299 | if (format == 2) { 300 | // Decode RGB3 301 | cv_img = cv::Mat(cv::Size(image_width, image_height), CV_8UC3, buffer); 302 | } 303 | 304 | cv::imshow("view", cv_img); 305 | 306 | // You may do image processing here 307 | // Begin OpenCV Code 308 | // .......... 309 | // End OpenCV Code 310 | 311 | cv::Mat raw_img; 312 | 313 | // RGB to YV12 314 | cv::cvtColor(cv_img, raw_img, cv::COLOR_RGB2YUV_YV12); 315 | 316 | // YV12 to JPEG 317 | uint8_t* outbuffer = NULL; 318 | cv::Mat input = raw_img.reshape(1, raw_img.total() * raw_img.channels()); 319 | std::vector vec = raw_img.isContinuous()? input : input.clone(); 320 | uint64_t outlen = YV12toJPEG(vec.data(), image_width, image_height, outbuffer); 321 | 322 | // printf("libjpeg produced %ld bytes\n", outlen); 323 | 324 | // Write JPEG to file 325 | std::vector output = std::vector(outbuffer, outbuffer + outlen); 326 | std::ofstream ofs("output.jpg", std::ios_base::binary); 327 | ofs.write((const char*) &output[0], output.size()); 328 | ofs.close(); 329 | 330 | cv::waitKey(1); 331 | 332 | return 0; 333 | } 334 | 335 | static void usage(const char *argv0) 336 | { 337 | fprintf(stderr, "Usage: %s [options]\n", argv0); 338 | fprintf(stderr, "Available options are\n"); 339 | fprintf(stderr, 340 | " -f Select frame format\n\t" 341 | "0 = V4L2_PIX_FMT_YUYV\n\t" 342 | "1 = V4L2_PIX_FMT_MJPEG\n\t" 343 | "2 = V4L2_PIX_FMT_RGB24\n"); 344 | fprintf(stderr, 345 | " -r Select frame resolution:\n\t" 346 | "0 = 360p, VGA (640x360)\n\t" 347 | "1 = 720p, (1280x720)\n"); 348 | fprintf(stderr, " -v device V4L2 Video Capture device\n"); 349 | fprintf(stderr, " -h Print this help screen and exit\n"); 350 | } 351 | 352 | int main(int argc, char* argv[]) 353 | { 354 | int fd, opt; 355 | 356 | int pixel_format = 0; /* V4L2_PIX_FMT_YUYV */ 357 | char *v4l2_devname = "/dev/video0"; 358 | 359 | while ((opt = getopt(argc, argv, "f:hv:r:")) != -1) { 360 | switch (opt) { 361 | case 'f': 362 | if (atoi(optarg) < 0 || atoi(optarg) > 2) { 363 | usage(argv[0]); 364 | return 1; 365 | } 366 | 367 | pixel_format = atoi(optarg); 368 | break; 369 | 370 | case 'h': 371 | usage(argv[0]); 372 | return 1; 373 | 374 | case 'v': 375 | v4l2_devname = optarg; 376 | printf("Use video device: %s\n", v4l2_devname); 377 | break; 378 | 379 | case 'r': 380 | if (atoi(optarg) < 0 || atoi(optarg) > 1) { 381 | usage(argv[0]); 382 | return 1; 383 | } 384 | 385 | if (atoi(optarg) == 0) { 386 | image_width = IMAGE_WIDTH_360P; 387 | image_height = IMAGE_HEIGHT_360P; 388 | } else { 389 | image_width = IMAGE_WIDTH_720P; 390 | image_height = IMAGE_HEIGHT_720P; 391 | } 392 | break; 393 | 394 | default: 395 | printf("Invalid option '-%c'\n", opt); 396 | usage(argv[0]); 397 | return 1; 398 | } 399 | } 400 | 401 | fd = open(v4l2_devname, O_RDWR); 402 | if (fd == -1) 403 | { 404 | perror("Opening video device"); 405 | return 1; 406 | } 407 | 408 | if(print_caps(fd, pixel_format)) 409 | { 410 | return 1; 411 | } 412 | 413 | if(init_mmap(fd)) 414 | { 415 | return 1; 416 | } 417 | 418 | for(; ; ) 419 | { 420 | if(capture_image(fd, pixel_format)) 421 | { 422 | return 1; 423 | } 424 | } 425 | close(fd); 426 | 427 | return 0; 428 | } 429 | --------------------------------------------------------------------------------