← Documents Documentation/userspace-api/media/v4l/capture.c.rst GitHub 원문 ↗

Linux 6.18.37 · Userspace API / Media / V4L

media/v4l/capture.c

READ, MMAP, USERPTR 세 방식으로 V4L2 장치를 초기화하고 프레임을 캡처한 뒤 정리하는 완전한 C 예제입니다.

Source pathDocumentation/userspace-api/media/v4l/capture.c.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

capture.c.rst:1-664

V4L2 캡처 애플리케이션의 기준 구현입니다. 장치 capability 검사, 형식 협상, 세 버퍼 모델의 할당과 큐잉, 비차단 select 루프, 오류 처리와 대칭적인 자원 해제를 한 파일에서 연결해 보여 줍니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. SPDX-License-Identifier: GFDL-1.1-no-invariants-or-later
2
3 file: media/v4l/capture.c
4 =========================
5
6 .. code-block:: c
7
8 /*
9 * V4L2 video capture example
10 *
11 * This program can be used and distributed without restrictions.
12 *
13 * This program is provided with the V4L2 API
14 * see https://linuxtv.org/docs.php for more information
15 */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <assert.h>
21
22 #include <getopt.h> /* getopt_long() */
23
24 #include <fcntl.h> /* low-level i/o */
25 #include <unistd.h>
26 #include <errno.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <sys/time.h>
30 #include <sys/mman.h>
31 #include <sys/ioctl.h>
32
33 #include <linux/videodev2.h>
34
35 #define CLEAR(x) memset(&(x), 0, sizeof(x))
36
37 enum io_method {
38 IO_METHOD_READ,
39 IO_METHOD_MMAP,
40 IO_METHOD_USERPTR,
41 };
42
43 struct buffer {
44 void *start;
45 size_t length;
46 };
47
48 static char *dev_name;
49 static enum io_method io = IO_METHOD_MMAP;
50 static int fd = -1;
51 struct buffer *buffers;
52 static unsigned int n_buffers;
53 static int out_buf;
54 static int force_format;
55 static int frame_count = 70;
56
57 static void errno_exit(const char *s)
58 {
59 fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
60 exit(EXIT_FAILURE);
61 }
62
63 static int xioctl(int fh, int request, void *arg)
64 {
65 int r;
66
67 do {
68 r = ioctl(fh, request, arg);
69 } while (-1 == r && EINTR == errno);
70
71 return r;
72 }
73
74 static void process_image(const void *p, int size)
75 {
76 if (out_buf)
77 fwrite(p, size, 1, stdout);
78
79 fflush(stderr);
80 fprintf(stderr, ".");
81 fflush(stdout);
82 }
83
84 static int read_frame(void)
85 {
86 struct v4l2_buffer buf;
87 unsigned int i;
88
89 switch (io) {
90 case IO_METHOD_READ:
91 if (-1 == read(fd, buffers[0].start, buffers[0].length)) {
92 switch (errno) {
93 case EAGAIN:
94 return 0;
95
96 case EIO:
97 /* Could ignore EIO, see spec. */
98
99 /* fall through */
100
101 default:
102 errno_exit("read");
103 }
104 }
105
106 process_image(buffers[0].start, buffers[0].length);
107 break;
108
109 case IO_METHOD_MMAP:
110 CLEAR(buf);
111
112 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
113 buf.memory = V4L2_MEMORY_MMAP;
114
115 if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {
116 switch (errno) {
117 case EAGAIN:
118 return 0;
119
120 case EIO:
121 /* Could ignore EIO, see spec. */
122
123 /* fall through */
124
125 default:
126 errno_exit("VIDIOC_DQBUF");
127 }
128 }
129
130 assert(buf.index < n_buffers);
131
132 process_image(buffers[buf.index].start, buf.bytesused);
133
134 if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
135 errno_exit("VIDIOC_QBUF");
136 break;
137
138 case IO_METHOD_USERPTR:
139 CLEAR(buf);
140
141 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
142 buf.memory = V4L2_MEMORY_USERPTR;
143
144 if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {
145 switch (errno) {
146 case EAGAIN:
147 return 0;
148
149 case EIO:
150 /* Could ignore EIO, see spec. */
151
152 /* fall through */
153
154 default:
155 errno_exit("VIDIOC_DQBUF");
156 }
157 }
158
159 for (i = 0; i < n_buffers; ++i)
160 if (buf.m.userptr == (unsigned long)buffers[i].start
161 && buf.length == buffers[i].length)
162 break;
163
164 assert(i < n_buffers);
165
166 process_image((void *)buf.m.userptr, buf.bytesused);
167
168 if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
169 errno_exit("VIDIOC_QBUF");
170 break;
171 }
172
173 return 1;
174 }
175
176 static void mainloop(void)
177 {
178 unsigned int count;
179
180 count = frame_count;
181
182 while (count-- > 0) {
183 for (;;) {
184 fd_set fds;
185 struct timeval tv;
186 int r;
187
188 FD_ZERO(&fds);
189 FD_SET(fd, &fds);
190
191 /* Timeout. */
192 tv.tv_sec = 2;
193 tv.tv_usec = 0;
194
195 r = select(fd + 1, &fds, NULL, NULL, &tv);
196
197 if (-1 == r) {
198 if (EINTR == errno)
199 continue;
200 errno_exit("select");
201 }
202
203 if (0 == r) {
204 fprintf(stderr, "select timeout\n");
205 exit(EXIT_FAILURE);
206 }
207
208 if (read_frame())
209 break;
210 /* EAGAIN - continue select loop. */
211 }
212 }
213 }
214
215 static void stop_capturing(void)
216 {
217 enum v4l2_buf_type type;
218
219 switch (io) {
220 case IO_METHOD_READ:
221 /* Nothing to do. */
222 break;
223
224 case IO_METHOD_MMAP:
225 case IO_METHOD_USERPTR:
226 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
227 if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))
228 errno_exit("VIDIOC_STREAMOFF");
229 break;
230 }
231 }
232
233 static void start_capturing(void)
234 {
235 unsigned int i;
236 enum v4l2_buf_type type;
237
238 switch (io) {
239 case IO_METHOD_READ:
240 /* Nothing to do. */
241 break;
242
243 case IO_METHOD_MMAP:
244 for (i = 0; i < n_buffers; ++i) {
245 struct v4l2_buffer buf;
246
247 CLEAR(buf);
248 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
249 buf.memory = V4L2_MEMORY_MMAP;
250 buf.index = i;
251
252 if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
253 errno_exit("VIDIOC_QBUF");
254 }
255 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
256 if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
257 errno_exit("VIDIOC_STREAMON");
258 break;
259
260 case IO_METHOD_USERPTR:
261 for (i = 0; i < n_buffers; ++i) {
262 struct v4l2_buffer buf;
263
264 CLEAR(buf);
265 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
266 buf.memory = V4L2_MEMORY_USERPTR;
267 buf.index = i;
268 buf.m.userptr = (unsigned long)buffers[i].start;
269 buf.length = buffers[i].length;
270
271 if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
272 errno_exit("VIDIOC_QBUF");
273 }
274 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
275 if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
276 errno_exit("VIDIOC_STREAMON");
277 break;
278 }
279 }
280
281 static void uninit_device(void)
282 {
283 unsigned int i;
284
285 switch (io) {
286 case IO_METHOD_READ:
287 free(buffers[0].start);
288 break;
289
290 case IO_METHOD_MMAP:
291 for (i = 0; i < n_buffers; ++i)
292 if (-1 == munmap(buffers[i].start, buffers[i].length))
293 errno_exit("munmap");
294 break;
295
296 case IO_METHOD_USERPTR:
297 for (i = 0; i < n_buffers; ++i)
298 free(buffers[i].start);
299 break;
300 }
301
302 free(buffers);
303 }
304
305 static void init_read(unsigned int buffer_size)
306 {
307 buffers = calloc(1, sizeof(*buffers));
308
309 if (!buffers) {
310 fprintf(stderr, "Out of memory\n");
311 exit(EXIT_FAILURE);
312 }
313
314 buffers[0].length = buffer_size;
315 buffers[0].start = malloc(buffer_size);
316
317 if (!buffers[0].start) {
318 fprintf(stderr, "Out of memory\n");
319 exit(EXIT_FAILURE);
320 }
321 }
322
323 static void init_mmap(void)
324 {
325 struct v4l2_requestbuffers req;
326
327 CLEAR(req);
328
329 req.count = 4;
330 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
331 req.memory = V4L2_MEMORY_MMAP;
332
333 if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {
334 if (EINVAL == errno) {
335 fprintf(stderr, "%s does not support "
336 "memory mapping\n", dev_name);
337 exit(EXIT_FAILURE);
338 } else {
339 errno_exit("VIDIOC_REQBUFS");
340 }
341 }
342
343 if (req.count < 2) {
344 fprintf(stderr, "Insufficient buffer memory on %s\n",
345 dev_name);
346 exit(EXIT_FAILURE);
347 }
348
349 buffers = calloc(req.count, sizeof(*buffers));
350
351 if (!buffers) {
352 fprintf(stderr, "Out of memory\n");
353 exit(EXIT_FAILURE);
354 }
355
356 for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
357 struct v4l2_buffer buf;
358
359 CLEAR(buf);
360
361 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
362 buf.memory = V4L2_MEMORY_MMAP;
363 buf.index = n_buffers;
364
365 if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
366 errno_exit("VIDIOC_QUERYBUF");
367
368 buffers[n_buffers].length = buf.length;
369 buffers[n_buffers].start =
370 mmap(NULL /* start anywhere */,
371 buf.length,
372 PROT_READ | PROT_WRITE /* required */,
373 MAP_SHARED /* recommended */,
374 fd, buf.m.offset);
375
376 if (MAP_FAILED == buffers[n_buffers].start)
377 errno_exit("mmap");
378 }
379 }
380
381 static void init_userp(unsigned int buffer_size)
382 {
383 struct v4l2_requestbuffers req;
384
385 CLEAR(req);
386
387 req.count = 4;
388 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
389 req.memory = V4L2_MEMORY_USERPTR;
390
391 if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {
392 if (EINVAL == errno) {
393 fprintf(stderr, "%s does not support "
394 "user pointer i/o\n", dev_name);
395 exit(EXIT_FAILURE);
396 } else {
397 errno_exit("VIDIOC_REQBUFS");
398 }
399 }
400
401 buffers = calloc(4, sizeof(*buffers));
402
403 if (!buffers) {
404 fprintf(stderr, "Out of memory\n");
405 exit(EXIT_FAILURE);
406 }
407
408 for (n_buffers = 0; n_buffers < 4; ++n_buffers) {
409 buffers[n_buffers].length = buffer_size;
410 buffers[n_buffers].start = malloc(buffer_size);
411
412 if (!buffers[n_buffers].start) {
413 fprintf(stderr, "Out of memory\n");
414 exit(EXIT_FAILURE);
415 }
416 }
417 }
418
419 static void init_device(void)
420 {
421 struct v4l2_capability cap;
422 struct v4l2_cropcap cropcap;
423 struct v4l2_crop crop;
424 struct v4l2_format fmt;
425 unsigned int min;
426
427 if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) {
428 if (EINVAL == errno) {
429 fprintf(stderr, "%s is no V4L2 device\n",
430 dev_name);
431 exit(EXIT_FAILURE);
432 } else {
433 errno_exit("VIDIOC_QUERYCAP");
434 }
435 }
436
437 if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
438 fprintf(stderr, "%s is no video capture device\n",
439 dev_name);
440 exit(EXIT_FAILURE);
441 }
442
443 switch (io) {
444 case IO_METHOD_READ:
445 if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
446 fprintf(stderr, "%s does not support read i/o\n",
447 dev_name);
448 exit(EXIT_FAILURE);
449 }
450 break;
451
452 case IO_METHOD_MMAP:
453 case IO_METHOD_USERPTR:
454 if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
455 fprintf(stderr, "%s does not support streaming i/o\n",
456 dev_name);
457 exit(EXIT_FAILURE);
458 }
459 break;
460 }
461
462
463 /* Select video input, video standard and tune here. */
464
465
466 CLEAR(cropcap);
467
468 cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
469
470 if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) {
471 crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
472 crop.c = cropcap.defrect; /* reset to default */
473
474 if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) {
475 switch (errno) {
476 case EINVAL:
477 /* Cropping not supported. */
478 break;
479 default:
480 /* Errors ignored. */
481 break;
482 }
483 }
484 } else {
485 /* Errors ignored. */
486 }
487
488
489 CLEAR(fmt);
490
491 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
492 if (force_format) {
493 fmt.fmt.pix.width = 640;
494 fmt.fmt.pix.height = 480;
495 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
496 fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
497
498 if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))
499 errno_exit("VIDIOC_S_FMT");
500
501 /* Note VIDIOC_S_FMT may change width and height. */
502 } else {
503 /* Preserve original settings as set by v4l2-ctl for example */
504 if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt))
505 errno_exit("VIDIOC_G_FMT");
506 }
507
508 /* Buggy driver paranoia. */
509 min = fmt.fmt.pix.width * 2;
510 if (fmt.fmt.pix.bytesperline < min)
511 fmt.fmt.pix.bytesperline = min;
512 min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
513 if (fmt.fmt.pix.sizeimage < min)
514 fmt.fmt.pix.sizeimage = min;
515
516 switch (io) {
517 case IO_METHOD_READ:
518 init_read(fmt.fmt.pix.sizeimage);
519 break;
520
521 case IO_METHOD_MMAP:
522 init_mmap();
523 break;
524
525 case IO_METHOD_USERPTR:
526 init_userp(fmt.fmt.pix.sizeimage);
527 break;
528 }
529 }
530
531 static void close_device(void)
532 {
533 if (-1 == close(fd))
534 errno_exit("close");
535
536 fd = -1;
537 }
538
539 static void open_device(void)
540 {
541 struct stat st;
542
543 if (-1 == stat(dev_name, &st)) {
544 fprintf(stderr, "Cannot identify '%s': %d, %s\n",
545 dev_name, errno, strerror(errno));
546 exit(EXIT_FAILURE);
547 }
548
549 if (!S_ISCHR(st.st_mode)) {
550 fprintf(stderr, "%s is no device\n", dev_name);
551 exit(EXIT_FAILURE);
552 }
553
554 fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);
555
556 if (-1 == fd) {
557 fprintf(stderr, "Cannot open '%s': %d, %s\n",
558 dev_name, errno, strerror(errno));
559 exit(EXIT_FAILURE);
560 }
561 }
562
563 static void usage(FILE *fp, int argc, char **argv)
564 {
565 fprintf(fp,
566 "Usage: %s [options]\n\n"
567 "Version 1.3\n"
568 "Options:\n"
569 "-d | --device name Video device name [%s]\n"
570 "-h | --help Print this message\n"
571 "-m | --mmap Use memory mapped buffers [default]\n"
572 "-r | --read Use read() calls\n"
573 "-u | --userp Use application allocated buffers\n"
574 "-o | --output Outputs stream to stdout\n"
575 "-f | --format Force format to 640x480 YUYV\n"
576 "-c | --count Number of frames to grab [%i]\n"
577 "",
578 argv[0], dev_name, frame_count);
579 }
580
581 static const char short_options[] = "d:hmruofc:";
582
583 static const struct option
584 long_options[] = {
585 { "device", required_argument, NULL, 'd' },
586 { "help", no_argument, NULL, 'h' },
587 { "mmap", no_argument, NULL, 'm' },
588 { "read", no_argument, NULL, 'r' },
589 { "userp", no_argument, NULL, 'u' },
590 { "output", no_argument, NULL, 'o' },
591 { "format", no_argument, NULL, 'f' },
592 { "count", required_argument, NULL, 'c' },
593 { 0, 0, 0, 0 }
594 };
595
596 int main(int argc, char **argv)
597 {
598 dev_name = "/dev/video0";
599
600 for (;;) {
601 int idx;
602 int c;
603
604 c = getopt_long(argc, argv,
605 short_options, long_options, &idx);
606
607 if (-1 == c)
608 break;
609
610 switch (c) {
611 case 0: /* getopt_long() flag */
612 break;
613
614 case 'd':
615 dev_name = optarg;
616 break;
617
618 case 'h':
619 usage(stdout, argc, argv);
620 exit(EXIT_SUCCESS);
621
622 case 'm':
623 io = IO_METHOD_MMAP;
624 break;
625
626 case 'r':
627 io = IO_METHOD_READ;
628 break;
629
630 case 'u':
631 io = IO_METHOD_USERPTR;
632 break;
633
634 case 'o':
635 out_buf++;
636 break;
637
638 case 'f':
639 force_format++;
640 break;
641
642 case 'c':
643 errno = 0;
644 frame_count = strtol(optarg, NULL, 0);
645 if (errno)
646 errno_exit(optarg);
647 break;
648
649 default:
650 usage(stderr, argc, argv);
651 exit(EXIT_FAILURE);
652 }
653 }
654
655 open_device();
656 init_device();
657 start_capturing();
658 mainloop();
659 stop_capturing();
660 uninit_device();
661 close_device();
662 fprintf(stderr, "\n");
663 return 0;
664 }
665

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

라이선스, 헤더와 기본 선언

1-35

이 문서는 `media/v4l/capture.c` 파일 전체를 C 코드 블록으로 제공합니다. 머리말은 이 프로그램을 제한 없이 사용하고 배포할 수 있으며 V4L2 API와 함께 제공된 예제임을 밝히고, 추가 정보는 LinuxTV 문서를 참조하도록 안내합니다.

표준 입출력, 메모리, 문자열과 단언 기능에 더해 명령행 옵션, 파일 제어, 오류 번호, 파일 상태, 시간, 메모리 매핑과 ioctl 인터페이스를 포함합니다. V4L2 ABI 정의는 `<linux/videodev2.h>`에서 가져옵니다.

`CLEAR(x)`는 `memset()`으로 구조체 전체를 0으로 초기화합니다. V4L2 ioctl에 전달하는 구조체의 예약 필드를 0으로 유지하기 위한 핵심 준비 단계입니다.

포함 헤더의 역할
항목설명
stdio.h, stdlib.h, string.h, assert.h출력, 메모리 관리, 문자열 처리와 런타임 단언
getopt.h`getopt_long()`을 이용한 긴 명령행 옵션 처리
fcntl.h, unistd.h, errno.h저수준 I/O, POSIX 호출과 오류 번호
sys/stat.h, sys/types.h, sys/time.h장치 파일 검사, 시스템 형식과 시간 구조체
sys/mman.h, sys/ioctl.h메모리 매핑과 장치 제어 호출
linux/videodev2.hV4L2 구조체, 열거형, 기능 비트와 ioctl 번호

예제가 사용하는 사용자 공간 인터페이스를 기능별로 묶었습니다.

.. SPDX-License-Identifier: GFDL-1.1-no-invariants-or-later

file: media/v4l/capture.c
=========================

.. code-block:: c

    /*
     *  V4L2 video capture example
     *
     *  This program can be used and distributed without restrictions.
     *
     *      This program is provided with the V4L2 API
     * see https://linuxtv.org/docs.php for more information
     */

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <assert.h>

    #include <getopt.h>             /* getopt_long() */

    #include <fcntl.h>              /* low-level i/o */
    #include <unistd.h>
    #include <errno.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <sys/mman.h>
    #include <sys/ioctl.h>

    #include <linux/videodev2.h>

    #define CLEAR(x) memset(&(x), 0, sizeof(x))

I/O 방식, 전역 상태와 공통 함수

36-83

`enum io_method`는 세 캡처 방식을 구분합니다. `IO_METHOD_READ`는 `read()`로 복사하고, `IO_METHOD_MMAP`은 드라이버가 할당한 버퍼를 매핑하며, `IO_METHOD_USERPTR`은 애플리케이션이 할당한 버퍼를 드라이버에 제공합니다.

`struct buffer`는 각 버퍼의 시작 주소와 길이를 저장합니다. 기본 장치는 뒤에서 `/dev/video0`으로 정하고, 기본 I/O 방식은 `MMAP`, 기본 프레임 수는 70입니다. `out_buf`와 `force_format`은 각각 표준 출력 전송과 강제 형식 설정의 활성화 횟수를 담습니다.

`errno_exit()`는 실패한 작업 이름, `errno` 값과 설명을 표준 오류에 출력한 뒤 프로그램을 종료합니다. `xioctl()`은 시그널로 중단된 `ioctl()`이 `EINTR`을 반환하면 같은 요청을 다시 시도합니다.

`process_image()`는 `--output`이 지정됐을 때 프레임 payload를 `stdout`에 그대로 쓰고, 처리한 프레임마다 진행 표시인 마침표를 `stderr`에 출력합니다. 두 스트림을 명시적으로 flush하여 출력 지연을 줄입니다.

세 가지 캡처 메모리 방식
항목설명
IO_METHOD_READ단일 사용자 버퍼에 `read()`로 데이터를 복사
IO_METHOD_MMAP드라이버 버퍼를 `mmap()`하고 `DQBUF`/`QBUF`로 순환
IO_METHOD_USERPTR애플리케이션 버퍼 주소를 `QBUF`로 전달하고 다시 회수

동일한 프레임 처리 루틴 앞뒤에서 버퍼 소유권 전달 방식만 달라집니다.


    enum io_method {
	    IO_METHOD_READ,
	    IO_METHOD_MMAP,
	    IO_METHOD_USERPTR,
    };

    struct buffer {
	    void   *start;
	    size_t  length;
    };

    static char            *dev_name;
    static enum io_method   io = IO_METHOD_MMAP;
    static int              fd = -1;
    struct buffer          *buffers;
    static unsigned int     n_buffers;
    static int              out_buf;
    static int              force_format;
    static int              frame_count = 70;

    static void errno_exit(const char *s)
    {
	    fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
	    exit(EXIT_FAILURE);
    }

    static int xioctl(int fh, int request, void *arg)
    {
	    int r;

	    do {
		    r = ioctl(fh, request, arg);
	    } while (-1 == r && EINTR == errno);

	    return r;
    }

    static void process_image(const void *p, int size)
    {
	    if (out_buf)
		    fwrite(p, size, 1, stdout);

	    fflush(stderr);
	    fprintf(stderr, ".");
	    fflush(stdout);
    }

프레임 한 개 읽기

84-174

`read_frame()`은 선택한 I/O 방식에 따라 프레임 하나를 얻습니다. 성공하면 1, 비차단 호출에서 아직 데이터가 없는 `EAGAIN`이면 0을 반환하여 상위 루프가 다시 기다리게 합니다.

READ 방식은 첫 번째 사용자 버퍼 전체 길이만큼 `read()`하고 그 메모리를 처리합니다. 코드 주석은 `EIO`를 무시할 수도 있다고 설명하지만, 이 예제 구현은 `fall through`하여 다른 치명적 오류와 같이 `errno_exit()`로 종료합니다.

MMAP 방식은 `struct v4l2_buffer`를 초기화하고 `V4L2_BUF_TYPE_VIDEO_CAPTURE`와 `V4L2_MEMORY_MMAP`을 지정합니다. `VIDIOC_DQBUF`로 완료된 버퍼를 꺼낸 뒤 인덱스 범위를 단언하고, 실제 payload 길이인 `buf.bytesused`만 처리한 다음 `VIDIOC_QBUF`로 같은 버퍼를 재등록합니다.

USERPTR 방식도 `VIDIOC_DQBUF`를 사용하지만 메모리 형식은 `V4L2_MEMORY_USERPTR`입니다. 반환된 `buf.m.userptr`와 `buf.length`가 애플리케이션의 어느 버퍼와 일치하는지 찾고, 일치 항목이 있음을 단언한 후 payload를 처리하고 재등록합니다.

read_frame() 경로 비교
방식취득처리 주소반환
READ`read()``buffers[0].start`별도 재등록 없음
MMAP`VIDIOC_DQBUF``buffers[buf.index].start``VIDIOC_QBUF`
USERPTR`VIDIOC_DQBUF``buf.m.userptr``VIDIOC_QBUF`

각 방식의 취득 호출, 데이터 주소와 반환 절차입니다.

스트리밍 버퍼 한 회전
v4l2_buffer 초기화와 type/memory 지정VIDIOC_DQBUF로 완료 버퍼 회수EAGAIN이면 준비 이벤트를 다시 대기bytesused 길이의 프레임 처리VIDIOC_QBUF로 버퍼 재등록

MMAP과 USERPTR이 공유하는 dequeue/queue 순환입니다.

    static int read_frame(void)
    {
	    struct v4l2_buffer buf;
	    unsigned int i;

	    switch (io) {
	    case IO_METHOD_READ:
		    if (-1 == read(fd, buffers[0].start, buffers[0].length)) {
			    switch (errno) {
			    case EAGAIN:
				    return 0;

			    case EIO:
				    /* Could ignore EIO, see spec. */

				    /* fall through */

			    default:
				    errno_exit("read");
			    }
		    }

		    process_image(buffers[0].start, buffers[0].length);
		    break;

	    case IO_METHOD_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 0;

			    case EIO:
				    /* Could ignore EIO, see spec. */

				    /* fall through */

			    default:
				    errno_exit("VIDIOC_DQBUF");
			    }
		    }

		    assert(buf.index < n_buffers);

		    process_image(buffers[buf.index].start, buf.bytesused);

		    if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
			    errno_exit("VIDIOC_QBUF");
		    break;

	    case IO_METHOD_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 0;

			    case EIO:
				    /* Could ignore EIO, see spec. */

				    /* fall through */

			    default:
				    errno_exit("VIDIOC_DQBUF");
			    }
		    }

		    for (i = 0; i < n_buffers; ++i)
			    if (buf.m.userptr == (unsigned long)buffers[i].start
				&& buf.length == buffers[i].length)
				    break;

		    assert(i < n_buffers);

		    process_image((void *)buf.m.userptr, buf.bytesused);

		    if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
			    errno_exit("VIDIOC_QBUF");
		    break;
	    }

	    return 1;
    }

select 기반 캡처 루프

175-213

`mainloop()`는 `frame_count`만큼 프레임을 처리합니다. 각 프레임마다 장치 파일 디스크립터를 읽기 집합에 넣고 `select()`로 준비 상태를 기다립니다.

대기 제한은 2초입니다. `select()`가 `EINTR`로 중단되면 다시 기다리고, 그 밖의 오류는 종료합니다. 2초 안에 준비되지 않으면 `select timeout`을 출력하고 실패로 끝냅니다.

장치가 준비되어도 비차단 `read()`나 `VIDIOC_DQBUF`가 `EAGAIN`을 반환할 수 있으므로 `read_frame()`이 0이면 내부 select 루프를 반복합니다. 프레임을 실제로 처리해 1을 반환할 때만 다음 프레임 횟수로 넘어갑니다.

프레임 대기 루프
fd_set에 캡처 장치 등록select()로 최대 2초 대기EINTR이면 select() 재시도read_frame()이 EAGAIN이면 준비 상태 재대기프레임 성공 시 남은 횟수 감소

비차단 장치를 안정적으로 소비하는 제어 흐름입니다.


    static void mainloop(void)
    {
	    unsigned int count;

	    count = frame_count;

	    while (count-- > 0) {
		    for (;;) {
			    fd_set fds;
			    struct timeval tv;
			    int r;

			    FD_ZERO(&fds);
			    FD_SET(fd, &fds);

			    /* Timeout. */
			    tv.tv_sec = 2;
			    tv.tv_usec = 0;

			    r = select(fd + 1, &fds, NULL, NULL, &tv);

			    if (-1 == r) {
				    if (EINTR == errno)
					    continue;
				    errno_exit("select");
			    }

			    if (0 == r) {
				    fprintf(stderr, "select timeout\n");
				    exit(EXIT_FAILURE);
			    }

			    if (read_frame())
				    break;
			    /* EAGAIN - continue select loop. */
		    }
	    }
    }

스트리밍 시작과 정지

214-279

`stop_capturing()`에서 READ 방식은 별도 스트림 상태가 없으므로 수행할 작업이 없습니다. MMAP과 USERPTR 방식은 `V4L2_BUF_TYPE_VIDEO_CAPTURE`에 대해 `VIDIOC_STREAMOFF`를 호출합니다.

`start_capturing()`도 READ 방식에는 작업이 없습니다. MMAP은 각 인덱스의 버퍼에 type, memory와 index를 채워 `VIDIOC_QBUF`로 모두 등록한 뒤 `VIDIOC_STREAMON`을 호출합니다.

USERPTR은 각 버퍼의 `m.userptr`와 `length`까지 구조체에 넣어 등록합니다. 모든 버퍼가 드라이버 큐에 들어간 다음에만 `VIDIOC_STREAMON`으로 캡처를 시작합니다.

스트리밍 시작 순서
모든 버퍼에 type과 memory 설정인덱스 또는 userptr/length 지정각 버퍼를 VIDIOC_QBUF로 등록VIDIOC_STREAMON으로 캡처 시작종료 시 VIDIOC_STREAMOFF 호출

버퍼 기반 I/O에서 큐가 먼저 준비되어야 합니다.


    static void stop_capturing(void)
    {
	    enum v4l2_buf_type type;

	    switch (io) {
	    case IO_METHOD_READ:
		    /* Nothing to do. */
		    break;

	    case IO_METHOD_MMAP:
	    case IO_METHOD_USERPTR:
		    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		    if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))
			    errno_exit("VIDIOC_STREAMOFF");
		    break;
	    }
    }

    static void start_capturing(void)
    {
	    unsigned int i;
	    enum v4l2_buf_type type;

	    switch (io) {
	    case IO_METHOD_READ:
		    /* Nothing to do. */
		    break;

	    case IO_METHOD_MMAP:
		    for (i = 0; i < n_buffers; ++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))
				    errno_exit("VIDIOC_QBUF");
		    }
		    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		    if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
			    errno_exit("VIDIOC_STREAMON");
		    break;

	    case IO_METHOD_USERPTR:
		    for (i = 0; i < n_buffers; ++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)buffers[i].start;
			    buf.length = buffers[i].length;

			    if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
				    errno_exit("VIDIOC_QBUF");
		    }
		    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		    if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
			    errno_exit("VIDIOC_STREAMON");
		    break;
	    }
    }

자원 해제와 READ 버퍼 초기화

280-321

`uninit_device()`는 I/O 방식에 맞게 메모리를 정리합니다. READ는 단일 힙 버퍼를 `free()`하고, MMAP은 모든 매핑을 `munmap()`하며, USERPTR은 애플리케이션이 할당한 각 버퍼를 `free()`합니다. 마지막에는 버퍼 설명 배열 자체도 해제합니다.

`init_read()`는 설명 구조체 하나를 `calloc()`하고 `sizeimage`에 해당하는 데이터 버퍼를 `malloc()`합니다. 두 할당 중 하나라도 실패하면 `Out of memory`를 알리고 즉시 종료합니다.

메모리 해제 책임
방식확보해제
READ애플리케이션 `malloc()``free(buffers[0].start)`
MMAP드라이버 할당 후 사용자 공간 매핑각 매핑에 `munmap()`
USERPTR애플리케이션 `malloc()`각 데이터 버퍼에 `free()`
공통버퍼 설명 배열 `calloc()``free(buffers)`

할당 방식과 대칭을 이루는 정리 호출입니다.


    static void uninit_device(void)
    {
	    unsigned int i;

	    switch (io) {
	    case IO_METHOD_READ:
		    free(buffers[0].start);
		    break;

	    case IO_METHOD_MMAP:
		    for (i = 0; i < n_buffers; ++i)
			    if (-1 == munmap(buffers[i].start, buffers[i].length))
				    errno_exit("munmap");
		    break;

	    case IO_METHOD_USERPTR:
		    for (i = 0; i < n_buffers; ++i)
			    free(buffers[i].start);
		    break;
	    }

	    free(buffers);
    }

    static void init_read(unsigned int buffer_size)
    {
	    buffers = calloc(1, sizeof(*buffers));

	    if (!buffers) {
		    fprintf(stderr, "Out of memory\n");
		    exit(EXIT_FAILURE);
	    }

	    buffers[0].length = buffer_size;
	    buffers[0].start = malloc(buffer_size);

	    if (!buffers[0].start) {
		    fprintf(stderr, "Out of memory\n");
		    exit(EXIT_FAILURE);
	    }
    }

MMAP 버퍼 초기화

322-379

`init_mmap()`은 `struct v4l2_requestbuffers`를 0으로 초기화한 뒤 네 개의 VIDEO_CAPTURE/MMAP 버퍼를 `VIDIOC_REQBUFS`로 요청합니다. 드라이버가 이 방식을 지원하지 않아 `EINVAL`을 반환하면 메모리 매핑 미지원 진단을 출력합니다.

드라이버가 두 개 미만의 버퍼만 제공하면 연속 캡처에 부족하다고 판단해 종료합니다. 실제로 제공된 `req.count`만큼 설명 배열을 할당합니다.

각 인덱스에 `VIDIOC_QUERYBUF`를 호출하여 길이와 장치 메모리 오프셋을 얻고, 해당 구간을 `PROT_READ | PROT_WRITE`, `MAP_SHARED`로 매핑합니다. 원문의 주석대로 시작 주소는 `NULL`로 두어 임의 위치를 허용하고, 읽기·쓰기 권한은 필수이며 공유 매핑은 권장됩니다.

mmap() 인자 의미
항목설명
NULL매핑 시작 주소를 커널이 선택
buf.lengthVIDIOC_QUERYBUF가 반환한 버퍼 크기
PROT_READ | PROT_WRITE캡처 버퍼 접근에 필요한 권한
MAP_SHARED드라이버와 같은 저장소를 공유하는 권장 매핑
fd, buf.m.offset장치와 해당 버퍼의 오프셋

V4L2 드라이버 버퍼를 사용자 공간에 연결하는 설정입니다.


    static void init_mmap(void)
    {
	    struct v4l2_requestbuffers req;

	    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) {
			    fprintf(stderr, "%s does not support "
				     "memory mapping\n", dev_name);
			    exit(EXIT_FAILURE);
		    } else {
			    errno_exit("VIDIOC_REQBUFS");
		    }
	    }

	    if (req.count < 2) {
		    fprintf(stderr, "Insufficient buffer memory on %s\n",
			     dev_name);
		    exit(EXIT_FAILURE);
	    }

	    buffers = calloc(req.count, sizeof(*buffers));

	    if (!buffers) {
		    fprintf(stderr, "Out of memory\n");
		    exit(EXIT_FAILURE);
	    }

	    for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
		    struct v4l2_buffer buf;

		    CLEAR(buf);

		    buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		    buf.memory      = V4L2_MEMORY_MMAP;
		    buf.index       = n_buffers;

		    if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
			    errno_exit("VIDIOC_QUERYBUF");

		    buffers[n_buffers].length = buf.length;
		    buffers[n_buffers].start =
			    mmap(NULL /* start anywhere */,
				  buf.length,
				  PROT_READ | PROT_WRITE /* required */,
				  MAP_SHARED /* recommended */,
				  fd, buf.m.offset);

		    if (MAP_FAILED == buffers[n_buffers].start)
			    errno_exit("mmap");
	    }
    }

USERPTR 버퍼 초기화

380-417

`init_userp()`은 네 개의 VIDEO_CAPTURE/USERPTR 버퍼 사용 의사를 `VIDIOC_REQBUFS`로 드라이버에 알립니다. `EINVAL`이면 사용자 포인터 I/O를 지원하지 않는 장치로 처리합니다.

데이터 메모리는 드라이버가 아니라 애플리케이션이 확보합니다. 버퍼 설명 배열 네 개를 만든 뒤 각 항목에 `buffer_size`를 기록하고 같은 크기의 메모리를 `malloc()`합니다.

이 예제는 반환된 `req.count` 대신 고정된 네 개를 할당합니다. 이후 `start_capturing()`이 각 주소와 길이를 `v4l2_buffer.m.userptr`와 `length`에 넣어 드라이버 큐로 전달합니다.


    static void init_userp(unsigned int buffer_size)
    {
	    struct v4l2_requestbuffers req;

	    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) {
			    fprintf(stderr, "%s does not support "
				     "user pointer i/o\n", dev_name);
			    exit(EXIT_FAILURE);
		    } else {
			    errno_exit("VIDIOC_REQBUFS");
		    }
	    }

	    buffers = calloc(4, sizeof(*buffers));

	    if (!buffers) {
		    fprintf(stderr, "Out of memory\n");
		    exit(EXIT_FAILURE);
	    }

	    for (n_buffers = 0; n_buffers < 4; ++n_buffers) {
		    buffers[n_buffers].length = buffer_size;
		    buffers[n_buffers].start = malloc(buffer_size);

		    if (!buffers[n_buffers].start) {
			    fprintf(stderr, "Out of memory\n");
			    exit(EXIT_FAILURE);
		    }
	    }
    }

장치 기능과 기본 크롭 설정

418-486

`init_device()`는 먼저 `VIDIOC_QUERYCAP`으로 장치 기능을 조회합니다. ioctl 자체가 `EINVAL`이면 해당 파일은 V4L2 장치가 아니며, `V4L2_CAP_VIDEO_CAPTURE`가 없으면 비디오 캡처 장치가 아닙니다.

READ 방식에는 `V4L2_CAP_READWRITE`가 필요하고, MMAP과 USERPTR에는 `V4L2_CAP_STREAMING`이 필요합니다. 선택한 I/O 방식과 장치 기능이 맞지 않으면 명시적인 진단과 함께 종료합니다.

비디오 입력, 비디오 표준과 튜너 선택은 주석으로 확장 지점만 남겨 둡니다. 이어 `VIDIOC_CROPCAP`으로 기본 크롭 사각형을 얻고 `VIDIOC_S_CROP`으로 적용을 시도합니다.

크롭은 선택 기능이므로 지원하지 않는 `EINVAL`과 기타 설정 오류를 모두 무시합니다. `crop.c = cropcap.defrect`의 주석은 크롭 영역을 장치 기본값으로 되돌린다는 뜻입니다.

I/O 방식별 필수 기능
대상기능 비트의미
모든 방식V4L2_CAP_VIDEO_CAPTURE비디오 캡처 장치인지 확인
READV4L2_CAP_READWRITEread() I/O 지원 확인
MMAP / USERPTRV4L2_CAP_STREAMING스트리밍 I/O 지원 확인

QUERYCAP 결과에서 확인하는 capability 비트입니다.


    static void init_device(void)
    {
	    struct v4l2_capability cap;
	    struct v4l2_cropcap cropcap;
	    struct v4l2_crop crop;
	    struct v4l2_format fmt;
	    unsigned int min;

	    if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) {
		    if (EINVAL == errno) {
			    fprintf(stderr, "%s is no V4L2 device\n",
				     dev_name);
			    exit(EXIT_FAILURE);
		    } else {
			    errno_exit("VIDIOC_QUERYCAP");
		    }
	    }

	    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
		    fprintf(stderr, "%s is no video capture device\n",
			     dev_name);
		    exit(EXIT_FAILURE);
	    }

	    switch (io) {
	    case IO_METHOD_READ:
		    if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
			    fprintf(stderr, "%s does not support read i/o\n",
				     dev_name);
			    exit(EXIT_FAILURE);
		    }
		    break;

	    case IO_METHOD_MMAP:
	    case IO_METHOD_USERPTR:
		    if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
			    fprintf(stderr, "%s does not support streaming i/o\n",
				     dev_name);
			    exit(EXIT_FAILURE);
		    }
		    break;
	    }


	    /* Select video input, video standard and tune here. */


	    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)) {
			    switch (errno) {
			    case EINVAL:
				    /* Cropping not supported. */
				    break;
			    default:
				    /* Errors ignored. */
				    break;
			    }
		    }
	    } else {
		    /* Errors ignored. */
	    }

픽셀 형식과 버퍼 크기 결정

487-529

형식 구조체의 type은 `V4L2_BUF_TYPE_VIDEO_CAPTURE`입니다. `--format`이 지정되면 640x480, `V4L2_PIX_FMT_YUYV`, `V4L2_FIELD_INTERLACED`를 요청하고 `VIDIOC_S_FMT`으로 적용합니다.

드라이버는 `VIDIOC_S_FMT` 과정에서 폭과 높이를 조정할 수 있으므로 반환된 구조체를 최종값으로 사용해야 합니다. 강제 형식이 아니면 `v4l2-ctl` 등으로 미리 설정된 값을 보존하기 위해 `VIDIOC_G_FMT`만 호출합니다.

결함이 있는 드라이버에 대비해 `bytesperline`이 폭의 두 배보다 작지 않도록 하고, `sizeimage`가 `bytesperline * height`보다 작지 않도록 보정합니다. YUYV가 픽셀당 2바이트라는 전제에 맞춘 방어 코드입니다.

최종 `sizeimage`를 기준으로 READ는 `init_read()`, MMAP은 `init_mmap()`, USERPTR은 `init_userp()`를 호출하여 선택한 메모리 모델의 버퍼를 준비합니다.

형식과 버퍼 초기화
VIDEO_CAPTURE 형식 구조체 초기화강제 모드면 S_FMT, 아니면 G_FMT드라이버가 반환한 폭·높이와 stride 확인bytesperline과 sizeimage 하한 보정I/O 방식에 맞는 버퍼 초기화

최종 형식이 결정된 뒤 메모리를 준비합니다.



	    CLEAR(fmt);

	    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	    if (force_format) {
		    fmt.fmt.pix.width       = 640;
		    fmt.fmt.pix.height      = 480;
		    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
		    fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

		    if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))
			    errno_exit("VIDIOC_S_FMT");

		    /* Note VIDIOC_S_FMT may change width and height. */
	    } else {
		    /* Preserve original settings as set by v4l2-ctl for example */
		    if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt))
			    errno_exit("VIDIOC_G_FMT");
	    }

	    /* Buggy driver paranoia. */
	    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;

	    switch (io) {
	    case IO_METHOD_READ:
		    init_read(fmt.fmt.pix.sizeimage);
		    break;

	    case IO_METHOD_MMAP:
		    init_mmap();
		    break;

	    case IO_METHOD_USERPTR:
		    init_userp(fmt.fmt.pix.sizeimage);
		    break;
	    }
    }

장치 파일 열기와 닫기

530-561

`open_device()`는 먼저 `stat()`으로 지정 경로를 확인합니다. 식별할 수 없거나 문자 장치가 아니면 실패하며, 실제 V4L2 기능 확인은 이후 `VIDIOC_QUERYCAP`이 담당합니다.

장치는 필수인 `O_RDWR`와 비차단 동작을 위한 `O_NONBLOCK`으로 엽니다. 비차단 플래그 덕분에 아직 프레임이 없을 때 `read()`와 `VIDIOC_DQBUF`가 `EAGAIN`을 반환하고, 상위 select 루프가 다시 대기할 수 있습니다.

`close_device()`는 파일 디스크립터를 닫고 전역 `fd`를 -1로 되돌립니다. 닫기 실패도 다른 시스템 호출과 동일하게 치명적 오류로 처리합니다.

진단 메시지 번역
원문 메시지한국어 의미
%s error %d, %s지정한 작업이 오류 번호와 설명을 반환함
select timeout2초 동안 장치가 준비되지 않음
Out of memory버퍼 또는 설명 배열 메모리 할당 실패
does not support memory mapping장치가 MMAP I/O를 지원하지 않음
Insufficient buffer memory드라이버가 제공한 버퍼가 두 개 미만임
does not support user pointer i/o장치가 USERPTR I/O를 지원하지 않음
is no V4L2 device파일이 V4L2 장치가 아님
is no video capture device장치에 비디오 캡처 기능이 없음
does not support read i/o장치가 read() I/O를 지원하지 않음
does not support streaming i/o장치가 스트리밍 I/O를 지원하지 않음
Cannot identify지정 경로의 파일 상태를 조회할 수 없음
is no device지정 경로가 문자 장치가 아님
Cannot open지정 장치 파일을 열 수 없음

예제가 사용자에게 출력하는 주요 영어 메시지의 의미입니다.


    static void close_device(void)
    {
	    if (-1 == close(fd))
		    errno_exit("close");

	    fd = -1;
    }

    static void open_device(void)
    {
	    struct stat st;

	    if (-1 == stat(dev_name, &st)) {
		    fprintf(stderr, "Cannot identify '%s': %d, %s\n",
			     dev_name, errno, strerror(errno));
		    exit(EXIT_FAILURE);
	    }

	    if (!S_ISCHR(st.st_mode)) {
		    fprintf(stderr, "%s is no device\n", dev_name);
		    exit(EXIT_FAILURE);
	    }

	    fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);

	    if (-1 == fd) {
		    fprintf(stderr, "Cannot open '%s': %d, %s\n",
			     dev_name, errno, strerror(errno));
		    exit(EXIT_FAILURE);
	    }
    }

사용법과 명령행 옵션

562-595

`usage()`는 프로그램 버전 1.3과 옵션 도움말을 출력합니다. 기본 장치 이름과 기본 프레임 수는 현재 전역 변수 값을 문자열에 삽입합니다.

짧은 옵션 문자열 `d:hmruofc:`에서 콜론은 `-d`와 `-c`가 인자를 요구한다는 뜻입니다. `long_options`는 각 긴 옵션을 같은 단일 문자 분기로 연결합니다.

캡처 예제 옵션
옵션한국어 설명
-d, --device name사용할 비디오 장치 이름 지정
-h, --help도움말 출력
-m, --mmap메모리 매핑 버퍼 사용, 기본값
-r, --readread() 호출 사용
-u, --userp애플리케이션이 할당한 USERPTR 버퍼 사용
-o, --output캡처 스트림을 표준 출력으로 전송
-f, --format640x480 YUYV 형식 강제
-c, --count캡처할 프레임 수 지정

도움말 문구를 한국어로 옮긴 표입니다.


    static void usage(FILE *fp, int argc, char **argv)
    {
	    fprintf(fp,
		     "Usage: %s [options]\n\n"
		     "Version 1.3\n"
		     "Options:\n"
		     "-d | --device name   Video device name [%s]\n"
		     "-h | --help          Print this message\n"
		     "-m | --mmap          Use memory mapped buffers [default]\n"
		     "-r | --read          Use read() calls\n"
		     "-u | --userp         Use application allocated buffers\n"
		     "-o | --output        Outputs stream to stdout\n"
		     "-f | --format        Force format to 640x480 YUYV\n"
		     "-c | --count         Number of frames to grab [%i]\n"
		     "",
		     argv[0], dev_name, frame_count);
    }

    static const char short_options[] = "d:hmruofc:";

    static const struct option
    long_options[] = {
	    { "device", required_argument, NULL, 'd' },
	    { "help",   no_argument,       NULL, 'h' },
	    { "mmap",   no_argument,       NULL, 'm' },
	    { "read",   no_argument,       NULL, 'r' },
	    { "userp",  no_argument,       NULL, 'u' },
	    { "output", no_argument,       NULL, 'o' },
	    { "format", no_argument,       NULL, 'f' },
	    { "count",  required_argument, NULL, 'c' },
	    { 0, 0, 0, 0 }
    };

옵션 처리와 프로그램 수명 주기

596-664

`main()`은 기본 장치를 `/dev/video0`으로 지정하고 `getopt_long()`으로 옵션을 반복 해석합니다. `-m`, `-r`, `-u`는 마지막에 지정된 I/O 방식을 선택하고, `-o`와 `-f`는 해당 기능을 활성화합니다.

`-c`의 인자는 `strtol()`로 변환합니다. 호출 전에 `errno`를 0으로 만들고 변환 후 오류가 설정되면 해당 인자 문자열을 작업 이름으로 하여 종료합니다. 인식하지 못한 옵션은 사용법을 표준 오류에 출력합니다.

옵션 처리가 끝나면 장치를 열고 초기화하며, 캡처를 시작해 지정한 프레임 수만큼 루프를 수행합니다. 그 뒤 스트리밍 정지, 버퍼 해제, 장치 닫기를 역순으로 실행하고 표준 오류에 줄바꿈을 출력한 뒤 성공으로 반환합니다.

`case 0`의 주석은 `getopt_long()`의 flag 기반 옵션을 위한 분기라는 뜻입니다. 이 예제의 모든 실제 옵션은 문자 값을 반환하므로 해당 분기에서는 추가 작업을 하지 않습니다.

프로그램 전체 수명 주기
명령행 옵션 해석open_device()init_device()start_capturing()mainloop()stop_capturing()uninit_device()close_device()

main()이 호출하는 준비, 실행과 정리 순서입니다.

    int main(int argc, char **argv)
    {
	    dev_name = "/dev/video0";

	    for (;;) {
		    int idx;
		    int c;

		    c = getopt_long(argc, argv,
				    short_options, long_options, &idx);

		    if (-1 == c)
			    break;

		    switch (c) {
		    case 0: /* getopt_long() flag */
			    break;

		    case 'd':
			    dev_name = optarg;
			    break;

		    case 'h':
			    usage(stdout, argc, argv);
			    exit(EXIT_SUCCESS);

		    case 'm':
			    io = IO_METHOD_MMAP;
			    break;

		    case 'r':
			    io = IO_METHOD_READ;
			    break;

		    case 'u':
			    io = IO_METHOD_USERPTR;
			    break;

		    case 'o':
			    out_buf++;
			    break;

		    case 'f':
			    force_format++;
			    break;

		    case 'c':
			    errno = 0;
			    frame_count = strtol(optarg, NULL, 0);
			    if (errno)
				    errno_exit(optarg);
			    break;

		    default:
			    usage(stderr, argc, argv);
			    exit(EXIT_FAILURE);
		    }
	    }

	    open_device();
	    init_device();
	    start_capturing();
	    mainloop();
	    stop_capturing();
	    uninit_device();
	    close_device();
	    fprintf(stderr, "\n");
	    return 0;
    }